Be precise about "arguments", "parameters", and "variables"
The reference manual and the compiler source use the terms "arguments", "parameters", and "variables" interchangeably, sometimes in a way inconsistent with most other programming languages. There is a clear distinction between the three terms: (quotes taken from Wikipedia)
The argument in computer science is the actual input expression passed/supplied to a function, procedure, or routine in the invocation/call statement, whereas the parameter is the variable inside the implementation of the subroutine. For example, if one defines the
addsubroutine asdef add(x, y): return x + y, thenx, yare parameters, while if this is called asadd(2, 3), then2, 3are the arguments.
A parameter is an (unbound) variable, while the argument can be a literal or variable or more complex expression involving literals and variables.
We should follow the same terminology in Crystal to minimize chances of confusion. (The Eiffel convention of "formal arguments" and "actual arguments" seems rather verbose so I'll use the usual meanings.) An example in the reference manual is Default values and named arguments:
Default ~~values~~ arguments
A method can specify default ~~values~~ arguments for the last ~~arguments~~ parameters: ...
Named arguments
All arguments can also be specified, in addition to their position, by their name. For example: ...
When there are many arguments, the order of the names in the invocation doesn't matter, as long as all required ~~arguments~~ parameters are covered: ...
When a method specifies a splat parameter (explained in the next section), named arguments can't be used for positional parameters. The reason is that understanding how arguments are matched becomes very difficult; positional arguments are easier to reason about in this case.
Another place of misuse is break:
breakcan also take ~~a parameter~~ an argument which will then be the value that gets returned:
Whereas in the source code, there is #type_vars, which represents all three concepts:
Crystal::GenericType#type_varscorresponds to the generic type parameters (T, NforStaticArrayandT, RforProc);Crystal::GenericInstanceType#type_varsis the one that really means variables (T => Int32, N => 4forStaticArray(Int32, 4)andT => Tuple(Int32, Bool), R => StringforProc(Int32, Bool, String));Crystal::Macros::TypeNode#type_varsreturns the parameters on an uninstantiated generic, but arguments on an instantiated generic (after splat collection):{% StaticArray.type_vars %} # => [T, N] of MacroId {% StaticArray(Int32, 4).type_vars %} # => [Int32, 4] of TypeNode {% Proc.type_vars %} # => [T, R] of MacroId {% Proc(Int32, Bool, String).type_vars %} # => [Tuple(Int32, Bool), String] of TypeNodeCrystal::Macros::Generic#type_varsalso corresponds to type arguments: (note that uninstantiated generics passed in the same manner result in aPathinstead)macro foo(t) {% t.type_vars %} end foo StaticArray(Int32, 4) # => [Int32, 4] foo Proc(Int32, Bool, String) # => [Int32, Bool, String]
The docs changes are relatively easy, followed by compiler error messages and internal names in the compiler. Names that appear in the macro land are the hardest to replace, and require proper deprecation, but IMO we should do this in the future even if they don't make it for 1.0. For the particular case of #type_vars, perhaps we could:
- Rename
Crystal::GenericType#type_varsto#type_params; - Rename
Crystal::Macros::Generic#type_varsto#type_args; - Make
Crystal::Macros::TypeNode#type_paramsalways return the generic type parameters, even on instantiated generics; - Make
Crystal::Macros::TypeNode#type_varsnot return anything on uninstantiated generics, or expose#type_argshere to return aHashLiteral. (This would also allow us to remove the hacks inTuple.newandSlice#[].)
And Crystal::Macros::Arg should become Param. This affects #class_name directly and I don't know if a deprecation route is possible there.
Sounds good.
However, I'm not sure about the particular first example:
A method can specify default arguments for the last parameters.
This feels odd to me. It should be "default values for the last parameters". They're not arguments passed to the method. The parameters have a default value when the arguments are omitted.
The default arguments are arguments provided at the method definition site rather than at the invocation site. I don't see why these expressions aren't arguments.
They could be seen as arguments, yes. But the values are assigned at the parameter definition. To me it seems less confusing to just talk about values than to pick on the difference between parameters and arguments in this case.
"default argument" is a weird turn of phrase. It's not incorrect, but "default value" feels so much more common. "default argument values for the last parameters" is probably the most technically accurate, but that feels a bit verbose. I think "value" and "argument" might be interchangable?
So I tried to gather the reference manuals of a bunch of programming languages to see which terms they use:
- C++: default argument
- C#: default argument
- D: default argument, default value
- Dart: default value
- Delphi: default parameter value
- Elixir: default argument, default value
- JavaScript: default argument, default parameter value
- Kotlin: default argument, default parameter value
- PHP: default argument, default value
- Python: default parameter value
- Raku: default value
- Ruby: default value
- Scala: default argument
- Swift: default value (default-argument-clause in the grammar)
- Java, Lua, Objective-C, Rust: unsupported
"Default argument value" is redundant; the value (or the expression) is the argument.
Though it's a bit wordy, I think "default parameter value" strikes a good balance between accuracy and understandability.
Judging from those languages it seems Ruby's documentation is ironically the loosest one with regard to "arguments" and "parameters", though the Japanese reference is better (it also uses the Eiffel convention):
仮引数にデフォルト式が与えられた場合、メソッド呼び出しで実引数を省略したときのデフォルト値になります。
"If a default expression is supplied to a formal argument, it becomes that argument's default value when the actual argument is omitted in a method call."
Slight preference for "Default value" for me.
I can understand "default argument" as "argument that's being passed if nothing else is". But an argument is something that a user of the function actively passes, by writing it into parentheses, so to say. But here it's kind of always been there, you don't need to pass it.
It's certainly not wrong, just sounds weird indeed.
In Python, though... oh, this would definitely be wrong. Python has actual default values that just sit there attached to the function as a (mutable!) singleton.
I noticed an incorrect usage use of arg/param terms in the parser’s source code too.
I would suggest to update all variable names used to store param names and values from ‘arg_’ to ‘param_’.
Examples:
- block params
- block grouped params
- function definition params
- macro definition params
For going all the way, we should probably also rename the Crystal::Arg type to Crystal::Param. I'm not sure if that would have any implications that we want to avoid. Leaving Arg in as an alias should probably be good for tools that hook directly into the compiler API (for example ameba).
I was thinking the same and I was also wandering if Crystal has aliases for keeping the backward compatibility. I can publish a PR for renaming the class.
There are also a couple of struct fields that should be renamed.