-
Notifications
You must be signed in to change notification settings - Fork 176
Discussion: Goby's parameter syntax in method definitions #751
Description
TL;DR: I think we can simplify Goby's parameter syntax in method definition.
Of course the final decision should be @st0012's.
Terms
Before proceeding, let me define the followings to clarify the issues and to be concise:
- parameter: on method definitions
- argument: on method calling
Name as the following on Goby's current method parameters:
- pN: normal parameters like
def foo(a)- Unable to omit on method calling
- No type restrictions
- pND: normal (optional) parameters with default value like
def foo(a=1)- Can be omitted
- no type restrictions
- pK: keyword parameters like
def foo(key:)- Unable to omit on method calling
- No type restrictions
- pKD: (optional) keyword parameters with default values like
def foo(key: "value")- Can be omitted
- No type restriction
- pVA: variable array parameters like
def foo(*array)- Uses splat
*for indicating pVA - Assumes an array will be taken
- Can be defined only once in a method definition
- Can be omitted (empty array
[]will be passed when omitted) - No default value can be provided
- "Bare" arguments will be treated as elements of the array
- Uses splat
In Goby, the order of parameters are restricted as
- pN: zero or more
- pND: zero or more
- pK: zero or more
- pKD: zero or more
- pVA: zero or one
Note that the order of arguments for pK can be shuffled as far as they are grouped:
# Goby
def foo(key1:, key2:, key3:)
end
foo(key3: "foo", key2: "bar", key1: "baz")This is the same for pKD:
# Goby
def foo(key1: "foo", key2: 99, key3: [])
end
foo(key3: [55, 44, 33], key2: 88, key1: "bar")Ruby's parameters
In addition, Ruby has the following parameters:
- pVH: variable hash parameters like
def foo(**hash)- Uses double splat
**for indicating pVH - Only hash can be taken(!)
- Can be defined only once in a method definition
- Can be omitted (empty hash
{}will be passed when omitted) - No default value can be provided
- "Bare" keyword arguments will be treated as the elements of the hash
- Uses double splat
- pB: block parameters like
def foo(&block)- Uses ampersand
&for indicating pVH - Assumes
Procobject will be taken - Can be defined only once in a method definition
- No default value can be provided
- Uses ampersand
Ruby's current issues on method parameters/arguments
1. Ruby's keyword parameters/arguments are still not well-integrated
- Ruby's pND is now redundant because pKD covers the role and is preferable
- Ruby's pND like
option={}is now obsolete and should be avoided
See the recent Rubocop rules that indicates keyword parameters with default values are preferable over parameters with default values with =:
- keyword arguments for boolean arguments rubocop/ruby-style-guide#509
- rubocop/ruby-style-guide@508e506
2. Splat * or double-splat ** on calling methods are often puzzling
Just to pass a variable that holds an array or a hash, we still need to add splats to the variables. I think this is also redundant.
# Ruby
def foo(*array, **hash)
p array
p hash
end
a = [99, 88, 77]
h = {key1: :value1, key2: :value2}
foo(*a, **h)Just using pKD is sufficient:
# Ruby
def foo(array: [], hash: {})
p array
p hash
end
a = [99, 88, 77]
h = {key1: :value1, key2: :value2}
foo(array: a, hash: h)3. Ruby is trying to handle keyword parameters as hash key-values, but sometimes fails:
This is one of the critical issues in current Ruby.
Ref: https://hackmd.io/8EMYfZ8KQwCbYrNogtIDIg
# Ruby
# Example1: assume the method exists first
def foo(*args)
args.each {|v| puts v.inspect }
end
foo([1, 2, 3]) #=> [1, 2, 3]
foo(key: 1) #=> {:key=>1}
# but just adding keyword arguments breaks the existing method calling!
def foo(*args, out: $stdout)
args.each {|v| out.puts v.inspect }
end
foo([1, 2, 3]) #=> [1, 2, 3]
foo(key: 1) #=> unknown key: k !!!# Ruby
# Example2: assume the method exists first
def create_element(name, attrs={})
# do something
end
create_element("a", href: "URL") #=> works
# but just adding keyword arguments breaks the existing method calling!
def create_element(name, attrs={}, children: elements)
# do something
end
create_element("a", href: "URL") #=> unknown key: href !!!Ruby comitters are trying to resolve the issue, but they recognizes that some breaking-changes are required.
4. Ruby's pVH with ** is in fact restricting the type (only hash can be taken)
(This is just what I discovered and perhaps not an issue :-)
Propositions to improve Goby's parameters
Considering above, Goby is evolving in good way, so I'd propose the followings:
1. Remove pND from Goby
As described above, pND, optional parameters with default value with =, is redundant and can be removed from Goby.
Removing pND, we can still provide optional keyword parameters pKD in Goby (and Ruby as well).
2. Remove pVA * from Goby
I think pVA, variable-length array parameters with splat * can be removed as well.
Removing pVA, we can still provide variable-length arguments with array literal [] or hash literal {} as well as the ones in variables:
# Goby
def foo(array: [], hash: {})
p array
p hash
end
foo(array: [99, 88, 77], hash: {key1: :value1, key2: :value2})
a = [99, 88, 77]
h = {key1: :value1, key2: :value2}
foo(array: a, hash: h)This makes any splat operators unnecessary.
Final parameter syntax
So Goby's parameter syntax can be simple and concise:
- pN (zero or more)
- pK (zero or more)
- pKD (zero or more)
Of course the order of pNs/pKs/pKDs should be kept.
Well, now I feel that adding block parameters pB (
&block) might be good for Goby.
Correction: I should've remembered Goby already implementsget_block😅 .
Advantages
- Splat operators
*and**are unnecessary - Reducing confusions regarding parameters and argumets
- Especially in "bare" variable-length params such as
1, 2, 3...orhash1: "value1", hash2:, "value2:... - We can now distinguish keyword params/args and hash's key-value
- Especially in "bare" variable-length params such as
I recognize that keyword-params/args are different from hash's key-value pairs.
This makes us avoid confusions when adding parameters in the future.
new Sample
In other words, you should always use pK or pKD to pass variable-length arguments. I hope this does not annoy developers so much.
# Goby
def form_with(model: nil, scope: nil, url: nil, format: nil, opt: {})
...
end
form_with opt: {skip_enforcing_utf8: true}, model: Post.first do |form|
form.text_field :title
end# Goby
def camelize(term, uppercase_first_letter: true)
...
end
camelize('active_model')
camelize('active_model', uppercase_first_letter: false)# Goby
class KeyGenerator
def initialize(secret, opt: {})
@secret = secret
@iterations = opt[:iterations] || 2**16
end
end
KeyGenerator.new("secretkey")
KeyGenerator.new("secretkey", opt:{iteration: 2**16})3. Experimental: type-checking with [] or {}
This is just an experimental idea. If [] or {} are specified with pKD, the type (Array or Hash) will be restricted as that.
I believe this does not break duck-typings.
# Goby
def foo(array1: [] array2: [1, 2, 3], hash1: {}, hash2: {key1: :value1})
puts array1
puts array2
puts hash1
puts hash2
end
foo(array1: 1) # TypeError
foo(hash2: [1, 2, 3]) # TypeErrorI look forward to your comments.