crystal
crystal copied to clipboard
Procs with ->method() and named arguments
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)).
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.
For reference, the issue is still present in 1.13.1.