Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Procs with ->method() and named arguments #11099

Open
docelic opened this issue Aug 16, 2021 · 1 comment
Open

Procs with ->method() and named arguments #11099

docelic opened this issue Aug 16, 2021 · 1 comment

Comments

@docelic
Copy link
Contributor

docelic commented Aug 16, 2021

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)).

@HertzDevil
Copy link
Contributor

HertzDevil commented Aug 17, 2021

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.

@docelic docelic changed the title Procs with ->() and named arguments Procs with ->method() and named arguments Aug 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants