crystal icon indicating copy to clipboard operation
crystal copied to clipboard

Procs with ->method() and named arguments

Open docelic opened this issue 4 years ago • 2 comments
trafficstars

Discussion

Hello, asking first to see if this is a bug or an intended feature.

Consider the following where a Proc is created as a reference to a method. Creating it with positional arguments works, but not with named arguments:

def a(x)
end

# Works with positional args
p ->a(Int32)

# Doesn't work with named args
p ->a(x : Int32)

(https://play.crystal-lang.org/#/r/brpw)

This causes an inconvenience of always having to use positional method to create Procs, but moreover it makes it impossible to create Procs to methods that have required named args (e.g. def a(*, x)).

docelic avatar Aug 16 '21 20:08 docelic

Proc itself must have a double splat generic parameter for this to happen. Alternatively you can do some tricks with a trailing NamedTuple parameter:

# F = Proc(*T, NT, R)
struct NamedProc(F, T, NT, R)
  protected def initialize(*, __proc @proc : F)
  end
  
  def self.new(proc : *T, NT -> R) forall T, NT, R
    {% unless NT < NamedTuple %}
      {% raise "'proc' must end with a NamedTuple parameter" %}
    {% end %}
    NamedProc(Proc(*T, NT, R), T, NT, R).new(__proc: proc)
  end
  
  def call(*args : *T, **opts : **NT) : R
    @proc.call(*args, opts)
  end
end

macro named_proc(method, *args, **opts)
  NamedProc.new(->(
    {% for arg, i in args %}
      __arg{{ i }} : ({{ arg }}),
    {% end %}
    {% unless opts.empty? %}
      __named_args : {{ opts }}
    {% end %}
  ) do
    {{ method.id }}(
      {% for arg, i in args %}
        __arg{{ i }},
      {% end %}
      {% unless opts.empty? %}
        **__named_args
      {% end %}
    )
  end)
  {% debug %}
end

def a(x, *, y)
  x * y
end

f = named_proc(:a, Int32, y: Int32)
f.call(3, y: 4) # => 12

g = named_proc("a", x: Int32, y: Int32)
g.call(x: 5, y: 6)  # => 30
g.call(y: 10, x: 8) # => 80

Procs that have named parameters will never be C functions (but then not all C functions are Crystal Procs either). Another thing is they are needed for blocks with named parameters / yields with named arguments.

HertzDevil avatar Aug 17 '21 02:08 HertzDevil

For reference, the issue is still present in 1.13.1.

docelic avatar Jul 28 '24 07:07 docelic