Skip to content
This repository was archived by the owner on Jul 12, 2025. It is now read-only.
This repository was archived by the owner on Jul 12, 2025. It is now read-only.

Discussion: Goby's parameter syntax in method definitions #751

@hachi8833

Description

@hachi8833

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

In Goby, the order of parameters are restricted as

  1. pN: zero or more
  2. pND: zero or more
  3. pK: zero or more
  4. pKD: zero or more
  5. 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
  • pB: block parameters like def foo(&block)
    • Uses ampersand & for indicating pVH
    • Assumes Proc object will be taken
    • Can be defined only once in a method definition
    • No default value can be provided

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 =:

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:

  1. pN (zero or more)
  2. pK (zero or more)
  3. 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 implements get_block 😅 .

Advantages

  • Splat operators * and ** are unnecessary
  • Reducing confusions regarding parameters and argumets
    • Especially in "bare" variable-length params such as 1, 2, 3... or hash1: "value1", hash2:, "value2:...
    • We can now distinguish keyword params/args and hash's key-value

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]) # TypeError

I look forward to your comments.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions