Hi, everyone. I’m new to Crystal and I have two language-design-related questions:
(1)Why do we need tuple?
I think maybe it’s because of the speed, the same reason in Python and in Elixir. Is performance of tuple being the whole reason to make Crystal’s designers choose to add tuple to the language?
(2)Why do we need named tuple?
It’s just a hash in Ruby. Why not directly use Hash?
Short answer on the run, feel free to ask for follow ups.
Tuples (be they named or not) are not only more efficient than a hash, but they are also more precisely typed. In a hash, you need to assign the same type to all the entries (which in Crystal it can be a union of several types). With a tuple, each tuple field has its own, precise type. Perhaps a better sibling of a tuple is the struct. In a sense, you could say that a (named) tuple is like an anonymous struct.
The typical use case for tuples is for returning multiple values. With a hash that’s ugly and error-prone.
The main reason why named tuples exist is to capture named arguments in a type-safe anonymous data structure. This is particularly useful for forwarding parameters:
def foo(**args)
args.class # => NamedTuple(string_parameter: String)
bar(**args)
end
def bar(string_parameter : String)
string_parameter.upcase
end
foo(string_parameter: "foo") # => "FOO"
That’s also pretty much the single relevant use case. In all other cases it’s probably better to use a dedicated struct type instead.
A named tuple type is basically like a struct type except that it doesn’t need an explicit definition (it’s created ad hoc) and it can only hold properties, no methods, inheritance etc.
Except @beta-ziliani post above, for returning multiple values, I never read explicitely that named tuples c(sh)ould be used instead of tuples, yet using NamedTuple keys are much less error prone than integer indexes.
Isn’t that use another main reason of NamedTuple existence ?
Not really. At that point why not use a record ? You get the same named arguments and parameters, but you also get a specific type and the ability to define methods on it.
Yes, but for very simple cases, such as returning a terminal lines and columns for example, I think terminal[:columns] is much clearer than terminal[1] and defining a Struct to handle such a simple case seems to me to be overkill.
I think for this use case its assumed you’d return {lines, columns} then the caller would do like lines, columns = get_terminal_lines_and_columns. Such that you don’t really need to access them by index anyway.