Could you give an example of such an API?
This is by far the most important thing they do for me.
YES! Thank you for extracting thoughts directly from my brain and putting them in words.
Personally I do not like symbols at all. People invented hash with indifferent access just to bypass it. I would use enums instead of symbols.
I donât know if itâs used in any open source projects this way, of it those which those would be, but this here for example is something I use exactly like this or just with small modifications in many of my projects.
# here I use symbols as a second type of String
class X
class << self
def [] x, *y
x= case x
when String
{name: x}
when Symbol
{match: x}
when Integer
{id: x}
when Hash
x
else
raise UnknownParameterType
end
select x
end
end
#âŚ
end
class Customer < X
#âŚ
end
class Supplier < X
#âŚ
end
class Bank < X
# âŚ
end
# Customer["John Doe"] # their name
# Customer[:C024578] # their customer number
# Customer[3852] # internal number
# Bank[:SRLGGB2L] # SWIFT code
Itâs also, a string has to me a very different meaning than a symbol: a string is something I might translate or adjust depending on context. Like a name could be adjusted, probably not when addressing them in a letter, but when I talk about them for example. On a publication taking about which customers one has got, or when itâs about references in a CV, then one might translate them, or like âthe White Houseâ will likely get translated in any non-english news report. Or strings get adjusted grammatically for the individual use case ( singular/plural/etc) - but thatâs all not happening with symbols, because they only serve the developer. They might end up occasionally in strings, but thatâs not their main purpose; usually they stay in the code, end up in a database or maybe even some config file. A typo in a string (no matter if on one or all occasions) is a bug. A typo in a symbol is unfortunate (as it will lead to bugs) but is no bug as long as itâs written incorrectly the same way every single time.
The same way: even though they were introduced to ruby for performance reasons - I actually donât know any project which uses them in that way specifically. Like if there is a situation where the same string gets used over and over (and where this string never serves any other purpose than eventually getting used as output to the user) I canât remember even one occasion where this string would have been stored as a symbol in order to boost performance, like letâs say for example an email signature, it might get cached, but I havenât seen a symbol made of it even once (better example would be probably the publisherâs name of a website, even though it will be used over and over on that page). Might give some boost, but I havenât seen it once used this way for this purpose.
With syntax highlighting usually just symbols become relevant for me while Iâm development as the program flow depends on it - strings on the other hand can be as such completely ignored, they might affect later the actual output, but not the program flow. Granted, there are a few, but very specific exceptions: ARGV, file names and URIs. But even with URIs Iâm used to have the âfirst partâ (whatever it is called) for example than available as symbol, as itâs of its meaning a symbol type and not a string (in this specific case something which could be an enum value, like in many but not all cases of symbols). During the development, most strings are just lorem ipsums, or at least I take no care in typos or such (except if I have to take care of the layout already). Strings, including URIs and file names, usually donât get any of my attention until directly before the first preview is sent to a customer (to remove the worst placeholder texts, or to insert placeholders which help the customer to understand the intention of some text). Most of the texts (as long as itâs not just labels or error messages) will then anyway be sent by the customer.
Would we speak of some 3D game, then strings would be the responsibility of the guys with the wacom tablets and stylus and Adobe subscriptions. while those who deal with the symbols would write the actual code.
There are just situations in which an enum wouldnât fit (or not even work, as itâs something which might appear only at runtime) and which are just semantically very different from a string. Just one example, where the same can be found in Crystalâs own syntax: fun foo="Foo_foo"(âŚ) : âŚ
. foo
and Foo_foo
are somewhat similar in their meaning, but they follow different rules (like foo
couldnât use a capital letter) and each also serves a very different receiver (one has to make the Crystal compiler happy, the other a library), but so do usually strings, too.
TL;DR: Actually, and I guess I may speak there for all the other âsymbol admirersâ as well, it doesnât need to be implemented in Crystal in a proper symbol way (as in: just getâs created once). Iâm absolutely fine, if the handy :foo
syntax stays (which also leads to syntax highlighting) and if I can do class
, typeof
and is_a?
according to a Symbol
on it. If you go and just create a second pseudo String
class, and every time I do :foo
or "foo".to_sym
a new instance gets created, Iâm fine with it (and two symbols should be equal if their string representation is equal; a string and itâs symbol should not considered equal). Crystal is so lightning fast anyway that Iâm happy with the tiny performance toll I might have to pay (and everyone who doesnât want to pay this toll can just avoid symbols in their code).
Enums often donât fit (but I admit, often or at least sometimes they would), but even worse: they canât be created at runtime either.
No, canât agree with this. People invented hash with indifferent access as a convenience method for the specific usecase of symbols as hash keys and I can see why.
In untidy code-bases where no style guide exists or is ignored, you might have hashes with either.
Or 3rd party code might introduce one or the other into your application.
In that case, indifferent access makes total sense.
But you can absolutely not draw the conclusion from this that âit was invented to bypass symbolsâ as a general concept.
Enums are close, but enums imply by name and declaration a group of related things (although you can of course declare only one ).
Like Enums, symbols are, well, symbols.
But Enums can be translated to something.
Symbols are just themselves. Like a function name ist not a string, it is a symbol (by philosophy, if not code).
I find symbols insanely usefull in ruby and I use them all the time not by default, but with intent.
If I use a symbol, i say âthis thing is now identified by this :thingâ. Be it a state in a state machine, be it a flag, whatever.
as an example: my functions donât return { status: âsuccessâ } but {status: :success}.
Because they donât return a string. they return the information that something succeeded.
I think this distinction is valuable, and adds even more valuable differentialization when looking through code.
You can always use enums and strings to replace symbols. If you really have to use symbols, you can use a custom class instead.
I consider symbol to be a useless language primitive that causes more troubles than benefits so I would prefer removing it from Crystal Lang.
Python doesnât have a symbol type and works fine.
I wish it could be removed from Ruby someday.
Python in general works fine, why donât you just use that?
The answer is probably: because Crystal has features that i like.
So, using Python as an argument why Crystal should not have symbols is just null and void.
What troubles does the existence of symbols in crystal cause? Any examples?
So does brainfuck even without any characters - by that logic they could be removed as well. We donât need classes, objects, arrays, and 95% of the stdlib, or things like the interpreter, formater or doc generator either - but we are glad to have them, because they make our life a lot easier.
Ifully accept if someone weighs the pro and contra of symbols differently, but that something else can do without them is really no point (as long as no one says it would be impossible to go without them, which didnât happen).
Customer[:C024578] # their customer number
Thatâs a bit strange in my opinion. Wouldnât someone want to search a customer by their number? That means that the argument will be dynamic. How do you handle that in that case? Do you accept a string from users and turn it into a symbol?
I would prefer that to be:
Customr.find_by_customer_number(...)
Itâs a bit more verbose, but nobody on the team has to learn the implicit rule that Symbol means âcustomer numberâ, or âswift codeâ depending on the class you are using. Less things to remember is always better in my opinion!
Symbols are similar to a globally available Enum with a lot of values. Defining a symbol just means adding a new global Enum instance, itâs just a syntax sugar.
Creating dynamic symbols is the same as creating global values but crystal doesnât have GIL and itâs bad for multi threading.
You should always use a domain specific Enum instead of symbols when feasible.
Hash with indifferent access is the biggest trouble with symbols.
And the trouble of abusive use of symbols as a convenient Enum is another one.
In the past the talk has been to possibly not fully remove symbols, but instead relegate them to a more specific purpose of enum auto casting. As thatâs really all I personally use them for.
While this may make sense for you, would it also make sense for someone reading your code? Iâm kinda with @asterite on this one. Being a bit more verbose while making the purpose a bit more explicit can go a long way in having readable code. E.g. use a dedicated struct type to represent a unique identifier such as ID.new "C024578"
. Now you can document what an ID
is and such.
Wouldnât a Status
enum be better suited for this? Then the compiler can actually help prevent typos, gives you a place to document the possible values for that field, and/or add additional helper methods if needed.
The one thing I would love to have is string backed enums. In most cases an enum is used to represent some particular state where the actual value of the enum doesnât really matter. But there are also cases where a specific string value may also be expected. Being able to do something like:
enum Suit : String
Hearts = "H"
Diamonds = "D"
Clubs = "C"
Spades = "S"
end
Would be a great addition for the cases where you want to strongly type a string field with a set of allowed values.
To provide an opinion from a different angle - I like symbols because they are visually different than enums or strings, and are almost exclusively used to represent something important. When visually scanning a crystal file, enums can be indistinguishable from class names or constants, and keys represented as strings are indistinguishable from other strings in the file (e.g. exception messages). Having symbols represented differently visually makes it easy to pick them out, and they almost always represent something from the domain being worked on (and so almost always has a domain definition behind the word I can look up for more context).
Enums would be preferable in most cases where you have an important key, but defining an enum requires you to know all possible values of the enum ahead of time. Itâs not always appropriate or desirable to go through that exhaustive effort when 1) You might not need to use all values of the resulting enum anyways and 2) You donât know which values ahead of time youâll need either. Being able to devolve to symbols helps that from a developer perspective. Especially when you donât have a need to limit the possible values either.
I would love that feature. I come from a Java background and love how enums are essentially classes there - you can give them constructors and a list of arguments. A single enum could represent a bunch of relevant properties,and so rather than relying an a case statement to convert the enum to the relevant property individually (or a hash, I suppose), you can just get the property from the enum directly. NamedTuple backed enums I guess would be the closest to that idea (or struct backed enums? Probably too much)
Values that donât correspond to an enumâs constants are allowed.
https://crystal-lang.org/reference/1.5/syntax_and_semantics/enum.html#enums-from-integers
The âjustâ in the second part of that sentence basically dismisses the whole reason why I use and like Ruby and crystal.
I donât need nor asked for dynamic creation of symbols.
Is there a reason for this? (btw, enums are also not dynamicly created.)
No, Hash with indifferent access is no problem at all.
See? I too can just write down statements.
If something is convinient in a programming language, that sounds like a good thing for me.
Symbols make editing and reading and understanding code better.
You see at a glance that this is not some random string.
You donât have to type the whole module name like with enums, and :success is always == :success no matter what module you are currently in. or what context.
Symbols help distinguish possibly mutable string parameters or returns, from defined and immutable parameters and returns.
Enums can be a replacement for symbols, but they cover a different set of cases, and can be used in place of symbols in general, but only by losing some of the unique advantages of Symbols.
Crystal would be a poorer, less beautiful language without them.
@tsornson and @mavu and everyone else who misses symbols:
I wrote a shard to get symbols back. Itâs a bit wonky, not as good as something out of the box, you probably shouldnât run a bank or any life-supporting equipment with it, but imo itâs better than nothing.
Be warned: after 32768 self made symbols it might overwrite Crystalâs own symbols (if you had those already maxed). And should the amount of symbols ever exceed 65536 it wonât end well for sure. Thatâs very sad as this effectively means you better shouldnât use them as universally as you were able to use them in ruby (like as keys for whatever - symbolize_names: true
comes directly to my mind), but it already gives back a lot of comfort, and as long as you handle only your own stuff, I see no reason why you shouldnât use symbols in your config files either (as they hopefully wonât have more than 30k keys)
This limitation could be actually lifted, but it would have its price: your new symbols wouldnât be compatible with crystalâs symbols any longer (thatâs why I didnât go this route yet, but Iâm seriously considering it). While with the current approach they should be perfectly compatible as far as I have seen them used in crystal. If anyone of you thinks âscrew it, letâs go for unlimitedâ I would be in (we might have to monkeypatch a few things, but I donât think this would be more effort than what it is necessary currently in order to replace symbols of ruby code which shall be ported).
Edit: My bad. in all the excitement I got my numbers wrong. I guess, you donât need to worry much about exceeding the amount, although there is a limit: Create 2147483648 symbols without worries, and be worried for the same amount additional - and after that it will end badly.
So currently a traditional symbol :a
(which existed at compile-time) will compare equal to a symbol you might create dynamically at run-time by "a".to_sym
.
Link to the shard will be added to this reply within the next few hours (Iâm going to play a bit more with it first, but it seems to work absolutely fine so far). Using the new feature will only need a require "symbolx"
and nothing else
I have created now for an hour or so literally millions of symbols and itâs been pure joy with no surprises or issues, so it seems to be all fine.
And here it is: kjarex/symbolx.cr
Download it and require it accordingly, or if you wanna go the shards way, either do a rock add symbolx
once itâs indexed or add this to your shard.yml manually:
dependencies:
symbolx:
github: kjarex/symbolx.cr
Then in your code, just add require "symbolx"
and youâre all set!! Enjoy!
2 posts were split to a new topic: Restrict OptionParser
argument to enum values