Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Be precise about "arguments", "parameters", and "variables" #10374

Open
HertzDevil opened this issue Feb 8, 2021 · 11 comments
Open

Be precise about "arguments", "parameters", and "variables" #10374

HertzDevil opened this issue Feb 8, 2021 · 11 comments

Comments

@HertzDevil
Copy link
Contributor

HertzDevil commented Feb 8, 2021

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 add subroutine as def add(x, y): return x + y, then x, y are parameters, while if this is called as add(2, 3), then 2, 3 are 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:

break can 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_vars corresponds to the generic type parameters (T, N for StaticArray and T, R for Proc);
  • Crystal::GenericInstanceType#type_vars is the one that really means variables (T => Int32, N => 4 for StaticArray(Int32, 4) and T => Tuple(Int32, Bool), R => String for Proc(Int32, Bool, String));
  • Crystal::Macros::TypeNode#type_vars returns 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 TypeNode
  • Crystal::Macros::Generic#type_vars also corresponds to type arguments: (note that uninstantiated generics passed in the same manner result in a Path instead)
    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_vars to #type_params;
  • Rename Crystal::Macros::Generic#type_vars to #type_args;
  • Make Crystal::Macros::TypeNode#type_params always return the generic type parameters, even on instantiated generics;
  • Make Crystal::Macros::TypeNode#type_vars not return anything on uninstantiated generics, or expose #type_args here to return a HashLiteral. (This would also allow us to remove the hacks in Tuple.new and Slice#[].)

And Crystal::Macros::Arg should become Param. This affects #class_name directly and I don't know if a deprecation route is possible there.

@straight-shoota
Copy link
Member

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.

@HertzDevil
Copy link
Contributor Author

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.

@straight-shoota
Copy link
Member

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.

@Fryguy
Copy link
Contributor

Fryguy commented Feb 8, 2021

"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?

@HertzDevil
Copy link
Contributor Author

HertzDevil commented Feb 8, 2021

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.

@RespiteSage
Copy link

Though it's a bit wordy, I think "default parameter value" strikes a good balance between accuracy and understandability.

@HertzDevil
Copy link
Contributor Author

HertzDevil commented Feb 8, 2021

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."

@oprypin
Copy link
Member

oprypin commented Feb 8, 2021

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.

@potomak
Copy link
Contributor

potomak commented Jul 4, 2022

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

@straight-shoota
Copy link
Member

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).

@potomak
Copy link
Contributor

potomak commented Jul 9, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants