On Tue, Apr 26, 2011 at 7:57 AM, Yusuke ENDOH <mame / tsg.ne.jp> wrote:

> 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 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 = <fun>
>  # 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
# => #<CurriedProc(lambda)>
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