myst-lang / myst

A structured, dynamic, general-purpose language.
http://myst-lang.org
MIT License
119 stars 17 forks source link

Allow methods to match on block argument structures. #82

Open faultyserver opened 6 years ago

faultyserver commented 6 years ago

In Ruby, blocks are special-cased with how their parameters are matched. Unlike any other method in Ruby, blocks can be given more or fewer arguments than they are defined to accept, and the interpreter will be okay with it:

[1, 2, 3].each{ |a, b, c| puts a }
[1, 2, 3].each{ puts "hi" }

This is really more like javascript, and while I've definitely used/abused this feature before, I don't particularly like its implicit nature from a design perspective.

I would prefer allowing methods to match the structure of the block argument they are given, and define different clauses accordingly. Crystal supports this in a way that's fairly clean, but more verbose and restrictive than I think I would like:

def transform_int(start : Int32, &block : Int32 -> Int32)
  result = yield start
  result * 2
end

transform_int(3) { |x| x + 2 } #=> 10
transform_int(3) { |x| "foo" } # Error: expected block to return Int32, not String

In #57, I've already started describing method signatures using a more terse version of this syntax. Expanded into an actual method definition, it might looks something like this:

def map(&block(e))
  [1, 2, 3].each do |e|
    block(e)
  end
end

def map(&block(e, i))
  [1, 2, 3].each_with_index do |e, i|
    block(e, i)
  end
end

This would define two different clauses that each expect a different block structure, one accepting an index parameter, and the other not. Usage wise, this wouldn't look any different from Ruby:

map{ |e| IO.puts(e) }
map{ |e, i| IO.puts(e, i) }

Maybe this is unnecessary or unhelpful, but I've been thinking about how to more explicitly implement the variadic matching of Ruby blocks, and this is the best I've come up with.

faultyserver commented 6 years ago

A concern with this is how it would affect block parameters that are really captured functions with multiple clauses. Should it match if at least one of those clauses matches? Should it only allow one clause? Can the pattern define multiple clauses to match?