From: Joshua Ballanco Date: 2011-04-27T08:33:06+09:00 Subject: [ruby-core:35914] Re: [Ruby 1.9 - Feature #4610] Proc#curry behavior is inconsistent with lambdas containing default argument values --90e6ba6e83d8c6085c04a1dabe26 Content-Type: text/plain; charset=UTF-8 On Tue, Apr 26, 2011 at 7:57 AM, Yusuke ENDOH wrote: > Hello, > > 2011/4/26 Joshua Ballanco : > > Regarding the consistency argument, as I understand Currying (or at least > the way that it is implemented in most other languages), the result of a > Proc#curry call should be a chain of Proc's with arity 1 that return Proc's > with arity 1 until all arguments have been satisfied. It would be nice if > Ruby behaved similarly. > > How should Ruby handle *rest parameter? > > proc {|x, y, z, *rest| }.curry.(1).(2).(3).(4).(5)... ? I agree rest is a complication, but terminal rest seems to be ok. I would argue that, in the case you provide, the final proc yielded could have arity -1. proc {|x, y, z, *rest| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4) # => "1, 2, 3, 4" proc {|x, y, z, *rest| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4, 5) # => "1, 2, 3, 4, 5" proc {|x, y, z, *rest| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4).(5) # => "1, 2, 3, 4" # => NoMethodError: undefined method `call' for nil:NilClass The real problem is interstitial rest argument, but even here I would argue that at the point where the rest argument is encountered, the proc should have arity -1 (and keeping arity strictness between lambda and proc): proc {|x, y, *rest, z| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4) # => "1, 2, 4, 3" lambda {|x, y, *rest, z| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4, 5) # => ArgumentError: wrong number of arguments (2 for 1) proc {|x, y, *rest, z| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4, 5) # => "1, 2, 4, 3" Essentially, if you keep to the notion of currying pulling argument lists apart and creating new methods for each argument, I think this is still doable in Ruby. > For example, in OCaml (which auto-curries functions): > > If you quote OCaml, you should note that Ocaml also provides > optional arguments. > Actually, OCaml handles optional arguments as Ruby does. > IOW, OCaml function also fires as soon as all the required > arguments are given: > > > # let foo ?(a="ichi") ?(b="ni") ?(c="san") () = > print_endline (S(String.concat ", " [a; b; c]);; > val foo : ?a:string -> ?b:string -> ?c:string -> unit -> unit = > # foo ();; > ichi, ni, san > - : unit = () > # foo ~a:"first" ();; > first, ni, san > - : unit = () > # foo ~a:"first" ~b:"second" ();; > first, second, san > - : unit = () > # foo ~a:"first" ~b:"second" ~c:"third" ();; > first, second, third > - : unit = () > > > There are some differences between OCaml and Ruby: > > - OCaml function requires at least one mandatory argument. > (In this case, () is the only mandatory argument.) > > - Optional arguments always requires labels (= keywords). > > > I believe your concern (and #4601) will be solved by keyword > arguments. > > def foo(a:"ichi", b:"ni", c:"san") > puts "#{ a }, #{ b }, #{ c }" > end > > foo(b:"second") #=> ichi, second, san > > method(:foo).curry. > pass_option(a: "first"). > pass_option(b: "second"). > pass_option(c: "third"). > call() #=> first, second, third > > Unfortunately, a new method (Proc#pass_option) is needed > because Proc#call(key: val) passes a hash { key => val } as > a normal argument, unless we accept the incompatibility. > This is an interesting approach I hadn't considered. I agree that OCaml's approach works because of the requirement of naming optional arguments (so, for example, I can still pass just the second of three). This also makes me wonder if keyword arguments and currying might not be more related than I had thought. Forgive me for speculating, but what if a curried proc could remember the variable name for its argument? This could provide a mechanism for keyword arguments. In other words, assuming you could do: c = lambda {|first, second="bar"| puts "#{first}, #{second}" }.curry # => # c.argument_key # => "first" c = c.('foo') c.argument_key # => "second" c.default_value # => "bar" c.("baz") # => "foo, baz" Then you could conceptually implement keyword arguments like so: class KeywordProc def initialize(curried_proc) @curried = curried_proc end def call(args) c = @curried.dup while c.kind_of? Proc do arg = args[c.argument_key] if arg.nil? if c.default_value c = c.() else raise ArgumentError end end c = c.(arg) end end end l = lambda { |first, second="san", third="three"| puts "#{first}, #{second}, #{third}" } k = KeywordProc.new(l.curry) k.call(second: "dos", first: "bir") # => "bir, dos, three" > The future of keyword arguments is promised by matz > [ruby-core:32131]: > > > Keyword arguments will be available on 2.0. I look more and more forward to 2.0 every day, now! Cheers, Josh --90e6ba6e83d8c6085c04a1dabe26 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable On Tue, Apr 26, 2011 at 7:57 AM, Yusuke ENDOH=C2=A0= <mame@tsg.ne.jp&= gt;=C2=A0wrote:
Hello,

2011/4/26 Joshua Ballanco <jballanc@gmail.com>:
> Regarding the = consistency argument, as I understand Currying (or at least the way that it= is implemented in most other languages), the result of a Proc#curry call s= hould be a chain of Proc's with arity 1 that return Proc's with ari= ty 1 until all arguments have been satisfied. It would be nice if Ruby beha= ved similarly.

How should Ruby handle *rest parameter?

=C2=A0proc {|x, y,= z, *rest| }.curry.(1).(2).(3).(4).(5)... ?

I agree rest is a complication, but terminal rest seems to be ok. I would = argue that, in the case you provide, the final proc yielded could have arit= y -1.

proc {|x, y, z, *rest| puts "#{x}, #{y}, #{z}, #{r= est.join(',')}" }.curry.(1).(2).(3).(4)
# =3D> &q= uot;1, 2, 3, 4"
proc {|x, y, z, *rest| puts "#{x},= #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4, 5)
# =3D> "1, 2, 3, 4, 5"
proc {|x, y, = z, *rest| puts "#{x}, #{y}, #{z}, #{rest.join(',')}" }.cu= rry.(1).(2).(3).(4).(5)
# =3D> "1, 2, 3, 4"
# =3D>=C2=A0NoMethodError: undefined method `call' for nil:NilC= lass

The real problem is interstitial rest argumen= t, but even here I would argue that at the point where the rest argument is= encountered, the proc should have arity -1 (and keeping arity strictness b= etween lambda and proc):

proc {|x, y, *rest, z| puts "#{x}, #{y}, #{z}, #{r= est.join(',')}" }.curry.(1).(2).(3).(4)
# =3D> &q= uot;1, 2, 4, 3"
lambda {|x, y, *rest, z| puts "#{x= }, #{y}, #{z}, #{rest.join(',')}" }.curry.(1).(2).(3).(4, 5)
# =3D>=C2=A0ArgumentError: wrong number of arguments (2 for 1= )
proc {|x, y, *rest, z| puts "#{x}, #{y}, #{z}, #= {rest.join(',')}" }.curry.(1).(2).(3).(4, 5)
=
# =3D> "1, 2, 4, 3"

Essentially, if y= ou keep to the notion of currying pulling argument lists apart and creating= new methods for each argument, I think this is still doable in Ruby.

> For example, in OCaml (which auto-curries functions)= :

If you quote OCaml, you should note that Ocaml also provides=
optional arguments.
Actually, OCaml handles optional arguments as Ru= by does.
IOW, OCaml function also fires as soon as all the required
arguments are= given:


=C2=A0# let foo ?(a=3D"ichi") ?(b=3D"ni&q= uot;) ?(c=3D"san") () =3D
=C2=A0 =C2=A0 =C2=A0print_endline (S= (String.concat ", " [a; b; c]);;
=C2=A0val foo : ?a:string -> ?b:string -> ?c:string -> unit -> = unit =3D <fun>
=C2=A0# foo ();;
=C2=A0ichi, ni, san
=C2=A0- = : unit =3D ()
=C2=A0# foo ~a:"first" ();;
=C2=A0first, ni, = san
=C2=A0- : unit =3D ()
=C2=A0# foo ~a:"first" ~b:"s= econd" ();;
=C2=A0first, second, san
=C2=A0- : unit =3D ()
=C2=A0# foo ~a:"f= irst" ~b:"second" ~c:"third" ();;
=C2=A0first, = second, third
=C2=A0- : unit =3D ()


There are some difference= s between OCaml and Ruby:

=C2=A0- OCaml function requires at least one mandatory argument.
=C2= =A0 =C2=A0(In this case, () is the only mandatory argument.)

=C2=A0-= Optional arguments always requires labels (=3D keywords).


I bel= ieve your concern (and #4601) will be solved by keyword
arguments.

=C2=A0def foo(a:"ichi", b:"ni", c:&qu= ot;san")
=C2=A0 =C2=A0puts "#{ a }, #{ b }, #{ c }"
= =C2=A0end

=C2=A0foo(b:"second") =C2=A0#=3D> ichi, secon= d, san

=C2=A0method(:foo).curry.
=C2=A0 =C2=A0pass_option(a: "first").
=C2=A0 =C2=A0pass_option= (b: "second").
=C2=A0 =C2=A0pass_option(c: "third").=
=C2=A0 =C2=A0call() =C2=A0#=3D> first, second, third

Unfortun= ately, a new method (Proc#pass_option) is needed
because Proc#call(key: val) passes a hash { key =3D> val } as
a norma= l argument, unless we accept the incompatibility.

=
This is an interesting approach I hadn't considered. I agree= that OCaml's approach works because of the requirement of naming optio= nal arguments (so, for example, I can still pass just the second of three).= This also makes me wonder if keyword arguments and currying might not be m= ore related than I had thought.

Forgive me for speculating, but what if a curried proc = could remember the variable name for its argument? This could provide a mec= hanism for keyword arguments. In other words, assuming you could do:

c =3D lambda {|first, second=3D"bar"| puts &q= uot;#{first}, #{second}" }.curry
# =3D> #<CurriedProc(= lambda)>
c.argument_key
# =3D> "first"<= /div>
c =3D c.('foo')
c.argument_key
# =3D> &= quot;second"
c.default_value
# =3D> "bar&q= uot;
c.("baz")
# =3D> "foo, baz"=

Then you could conceptually implement keyword arguments= like so:

class KeywordProc
=C2=A0 def i= nitialize(curried_proc)
=C2=A0 =C2=A0 @curried =3D curried_proc
=C2=A0 end

=C2=A0 def call(args)
=C2=A0 =C2=A0 c =3D @cu= rried.dup
=C2=A0 =C2=A0 while c.kind_of? Proc do
=C2=A0= =C2=A0 =C2=A0 arg =3D args[c.argument_key]
=C2=A0 =C2=A0 =C2=A0 = if arg.nil?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if c.default_value
<= div> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 c =3D c.()
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 else
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 raise Argument= Error
=C2=A0 =C2=A0 =C2=A0 =C2=A0 end
=C2=A0 =C2=A0 =C2= =A0 end
=C2=A0 =C2=A0 =C2=A0 c =3D c.(arg)
=C2=A0 =C2= =A0 end
=C2=A0 end=C2=A0=C2=A0
end

l =3D lambda { |first, second=3D"san", third=3D"three&q= uot;| puts "#{first}, #{second}, #{third}" }
k =3D Keyw= ordProc.new(l.curry)
k.call(second: "dos", first: "= ;bir")
# =3D> "bir, dos, three"
=C2=A0
The future of keyword arguments is promised by matz
[ruby-core:32131]:
> Keyword arguments will be available on 2.0.
I look more and more forward to 2.0 every day, now!

Cheers,

Josh=C2=A0
--90e6ba6e83d8c6085c04a1dabe26--