Arc Forumnew | comments | leaders | submitlogin
could currying be introduced in Arc?
10 points by Loup 5907 days ago | 17 comments
If it can, quite some code could shrink.

(Edit: it seems it can't. Too bad. See comments below)

I'm not talking about making currying possible (it is anyway), I'm talking about making it the default. (Like it is in most static FPLs)

That is,

(fn (x y) body) <=> (fn (x) (fn (y) body))

and (provided "foo" takes at least 2 arguments)

(foo 42) <=> [foo x _] <=> (fn (x) (+ 42 x))

It could eliminate most of the need for the [] reader macro, making the square brackets and the underscore available for something else (we only have (), {}, and [] as delimiters, which is not much). The [] notation is a great idea, but it is quite redundant with currying.

If I need to make my partial application on the second argument instead of the first, like in: [- _ 42]

I still have an escape route: swapping the arguments:

(def swap (f y x) (f x y)) <=> (def swap (f ) (fn (x y) (f x y)))

Remember the currying semantics I use here. So,

[- _ 42] <=> ((swap -) 42) <=> (swap - 42) ; function application associates to the left

Without the unneeded inner parentheses, We end up with three more character, zero more tokens.

To sum it up,

[+ x _] => (+ x) ; 1 less token

[- _ 42] <=> (swap - 42) ; 3 more characters, 0 more tokens

If one wonders if currying is incompatible with optional arguments, Ocaml have both, and it works. The fact that optional arguments and the argument list are at the end makes things even easier.

To those who are not yet convinced, currying is (with lambdas and closures) the feature which completes the first class citizenship of functions: With lambdas and closures, passing functions as argument and return values was made easy. With currying, the latter is made trivial.



3 points by absz 5907 days ago | link

This has been discussed before, and the biggest problem is not optional arguments, but variadic functions. (+), (+ 42), (+ 42 10), (+ 42 10 7), etc. are all legal, so (+ x) would just be x. Currying is a very powerful feature without a doubt, but it's not clear that it works very well with variadic functions. Certainly it's might be powerful for functions of a fixed arity, but explicit partial application might make more sense with the Arc system. See also http://arclanguage.org/item?id=2205.

-----

1 point by drcode 5907 days ago | link

I think variadic functions tend to be library (general) functions, whereas actual applications are often full of fixed arity functions. For instance, I think you will find that news.arc is full of fixed arity functions.

I believe that looking at the arc libraries when judging the value of implicit currying may be misleading, since its greatest value is in code written for a single application...

-----

3 points by absz 5907 days ago | link

This is certainly true, at least to an extent, but it makes currying inconsistent, which is irritating. I'm not convinced that [] and par create substantially longer code than implicit partial application, which (as noted) has issues. And the other problem, as was pointed out, is optional parameters; since they are similar to rest parameters, the objection is similar, but the problem is more insidious.

-----

1 point by ecmanaut 5905 days ago | link

Since the arity information is lost for any complement ~f (for instance, but any general or composed function will do) of a function f:

  (def complement (f) (fn args (no (apply f args))))
automatic currying turns all composed functions into second degree citizens, which adds programmer burden to the use of any function, in keeping track of whether it will invoke or return a new function.

Such blessings are plagues in disguise.

-----

2 points by Loup 5907 days ago | link

Ahhhrg, too bad. I searched for the topic before posting, but I didn't found anything on it. Well, better live with [+ x _], I suppose. At least, it is compatible with variadic functions.

-----

1 point by absz 5907 days ago | link

You can also have a par function to curry a function:

  (def par (f . args)
    " Partially apply `f' to `args'; i.e., return a function which, when called,
      calls `f' with `args' and the arguments to the new function. "
    (fn newargs (apply f (join args newargs))))
so (par + 42) will do what you want. Also, a generalized swap (which I called flip):

  (def flip (fun)
    " Returns a function which is the same as `fun', but takes its arguments in
      the opposite order. "
    (fn args (apply fun (rev args))))
This works for n-ary functions, but is still probably most useful for functions of two arguments: (par (flip -) 42).

-----

2 points by Loup 5903 days ago | link

Didn't think of "par", cool idea. It could even be turned into a macro, like that:

(par foo x) => [foo x] ; Well maybe too much.

About flip, I'd rather rotate the arguments instead of reverse them. That way, you have access to more arguments orders by composing flip. You could also define flip2, flip3... in the library if they're used often.

-----

1 point by absz 5903 days ago | link

Don't you mean [foo x] => (par foo x)? []s aren't fundamental, they're sugar.

I'm not sure there is a sensible way to extend flip to functions taking more than two arguments. Reverse is one, rotate is another; you might as well have two functions for that. It seems like six of one, half a dozen of the other to me.

-----

3 points by almkglor 5907 days ago | link

How does Ocaml handle currying and optional arguments? How about "rest" arguments?

I liked currying in Haskell, the only problem I see in a Lisplike is macros. Since macros in Arc are simply functions, then currying must be disabled for them, since (def foo) is obviously an error.

-----

1 point by Loup 5907 days ago | link

Optional arguments in Ocaml are labelled (if I remember correctly). They're not used much.

About "rest" arguments, take a look at this (incomplete) definition of +, where prim+ is the primitive definition of +.

(def + (x y @rest) (foldl (prim+) (prim+ x y) rest))

(Is the "@" to denote "rest"?)

Then: (+) => +

(+ 42) => (fn (x) (foldl (prim+) (prim+ 42 x) ())) => (fn (x) (prim+ 42 x))

(+ 42 24) => (foldl (prim+) (prim+ 42 24) ()) => 66

(+ 42 24 34) => (foldl (prim+) (prim+ 42 24) '(66)) => 100

This principle can be generalized to any function with a "rest", and to any function with optional arguments (provided they are at the end of the argument list).

Yes, currying should be disabled with macros. But they already are. Macro aren't plain functions. They can't be, with their special evaluation scheme. What is true is that macros and functions from list to lists are isomorphic. So, if I define a macro whose result is an integer, I bet the compiler will kindly notify my blunder when trying to make a list from this integer (right?). Same thing if the macro yields a function. So, any partial application in macro will automatically result in an error, and that particular error should be caught at parse/compile time by any descent Arc compiler.

-----

3 points by absz 5907 days ago | link

Except the primitive definition of + is still variadic. Any function declared as

  (def foo args
    (munge (frob args)))
will be uncurriable, which is the problem.

-----

1 point by almkglor 5907 days ago | link

> So, if I define a macro whose result is an integer, I bet the compiler will kindly notify my blunder when trying to make a list from this integer (right?). Same thing if the macro yields a function.

No, it won't. Because a macro returns an object which is inserted into the code. It's usually a list, but not always. It can be an integer, it can be a function object, and unfortunately the underlying mzscheme doesn't allow tables.

-----

2 points by cchooper 5907 days ago | link

I've already come across multiple scenarios where automatic currying would have shortened my programs, and if the swap operator had been available, I'd have used it even more.

But I'm still not a fan of implicit currying. I think the behaviour would be too unpredicatable when mixing rest functions, optional arguments, apply and code generation together (for example, it's often useful to be able to pass 0 arguments to + when you are generating code, even if you'd never write that sort of thing manually).

My compromise: perhaps the [...] syntax could be adapted to do automatic currying. If you use _ then it has its normal behaviour, but if you don't then the function is curried automatically. Only required arguments can be passed to such a function, optional and rest arguments are ignored, so [+] simply evaluates to 0 (or a function that takes no arguments and returns 0).

-----

3 points by almkglor 5907 days ago | link

By this I think you mean:

  [foo bar] => (fn rest (apply foo bar rest))
...?

-----

2 points by cchooper 5907 days ago | link

I'd originally envisioned that the function returned would be more strongly typed (ie. it would expand to (fn (x y z) ...) if foo takes 4 arguments) and that the function would be truly curried, so ([foo bar] baz) would also be a curried function if foo requires more arguments.

Now I think about it, the solution you've given would be perfectly adequate. The function will still fail on the wrong number of arguments, and I almost never need the other feature at all. It would also play friendly with optional and rest arguments.

-----

1 point by almkglor 5907 days ago | link

Hmm. It would be possible to modify 'make-br-fn on the arc-wiki.git for this.

-----

2 points by tokipin 5906 days ago | link

in other languages i write a function that i call fix which does the same as the _ notation except that the _ represents a general "hole to be filled later" and can be present any number of times

take for example a function named 'print' which accepts string, r, g, and b arguments

  (= my-prn (fix print "message" _ _ _))
so that

  (my-prn 0 0 1)
would print "message" in blue

  (= my-prn (fix print _ 0 _ 0))
  (my-prn "test" 1)
would print "test" in green. if built into the language it could be done something like:

  (= my-prn1 (print "message" _ _ _))
  (= my-prn2 (print _ 0 _ 0))

-----