Currying: Elixir library to add currying capability to all your Elixir functions

Today I realized that it would be possible to implement currying-capability in Elixir, using some clever anonymous function creation. (‘continuation-style currying’).

There was already a library called curry, which required you to define your to-be-curried functions using a special macro. And it would then define 255(the max. arity in Elixir) different function heads for it.

Currying does not do that. Instead, when yo u call curry, the passed function is wrapped in an anonymous function accepting a single parameter. This anonymous function is constructed in a clever way that will re-construct a new anonymous function each time an argument is passed, until the original function’s arity is reached, in which case the original function is called and the result returned.

There’s also some niceties like curry_many which allows you to pass in a list of arguments to apply to a curried function at the same time, and an optional implementation of ~> as infix-variant of curry/2.

An example:

      iex> use Currying
      iex> enum_map = curry(&Enum.map/2)
      iex> partially_applied_enum_map = enum_map.([1,2,3])
      iex> results = partially_applied_enum_map.(fn x -> x*x end)
      [1,4,9]    

See the Currying library/hex package here!


I’d be very grateful for any feedback ;-).

10 Likes

Awesome! I created a library for currying php functions, inspired by the incredibile mind blowing thing that haskell has been for me. I really love the concept of partial application and curry. Going to check it out for sure!

1 Like

For the ignorant, what’s the point of currying functions? What’s a use case that it solves for?

2 Likes

Currying plus partial application is just awesome, and after having used some haskell you do miss it really hard everywhere else.

Since I haven’t take a look into @Qqwy’s currying package so far, I will give the examples in haskell.

Given the funtion foldr defined as this:

foldr f z []     = z 
foldr f z (x:xs) = f x (foldr f z xs) 

now your job is to implement map on top of this:

You can use either the very naïv approach to just write it down as this:

map f xs = foldr (\y ys -> f x : ys) [] xs

This is a fully applied function but can be further reduced (eta-reduced to be specific) to the following:

map f = foldr (\x xs -> f x : xs) []

After one gets used to it, it is just cool, but maybe, if you do not know it already, you will probably never miss it :wink:

Something in Elixir we could do with currying might look like this (untested):

use Currying

def const(a, _b), do: a

curried_const = curry(&const/2)
Enum.map([1,2,3,4,5,6,7,8,9,10], curried_const.(5))

# without curried:
Enum.map([1,2,3,4,5,6,7,8,9,10], &const(5, &1))

I have to admit, in elixir it looks quite a lot more clear and readable, even more idiomatic to simply use a capture :wink:

I think it is nice to play with currying and partial application in elixir, but I might probably stick with pipes and captures, since they are a native language thing, while the curried stuff feels foreign.

4 Likes

Basically: You can only use captures if you exactly know how many arguments you’re going to put in your function right now. In the case of partial application (which might happen in multiple steps), this becomes a problem.

Currying means that you can treat all kinds of functions as unary (taking a single parameter) functions. This allows you to pass them around to higher-order functions (functions that take functions as input). It means that these higher-order functions only have to worry about manipulating unary functions, instead of functions of any arity.

In essence, currying lets you delay the ‘capture’ step to at runtime, instead of having to be defined in your source code.

It is the kind of thing that you will probably not miss when you don’t know about it. On the other hand, there are certain situations that are unsolvable, unless you can curry.

The main reason why I created this library, is that I had such need myself. I am working on another library, called fun_land (it isn’t on Hex yet as it is still unfinished), which creates algebraic data types (basically, ‘containers’ for other kinds of data).

1 Like

Oh yeah! Now things are getting interesting! Any of the (for haskellers) well-known interfaces that carry over? Monads, Applicatives, other nice stuff?

Yes! I am going the full way, implementing behaviours for semigroups and monoids, reducable and traversable, functors, appliable functors, applicative functors, chainable functors, monads and monad-monoids (known in Haskell as MonadPlus).

Starting with a clean slate means that there are many things that many of the duplicates that were the result of Haskell’s functors being ‘reverse engineered’ like Applicative pure== Monad return, Monad >>==Applicative *>, liftAvs liftM, MonadPlus mzerovs Monoid memptyetc. that can be unified, making it a whole lot more transparent what is going on.

I am am also going to rename some things, to make it as approachable as possible for people new to algebraic data structures. (such as Mappable instead of Functor, Combinable instead of Monoid, wrap instead of return).

Oh, and there are explanations about the different data types involving fruit salad. :grinning:

The inspiration for the library comes from a JS specification called fantasy land.

If you’d like to check the still-unfinished library out and maybe add some feedback, or point out things that are unclear, you can find FunLand on GitHub :slight_smile: .

1 Like

Don’t you find that the design of the elixir standard library somewhat limits the usefulness of currying?

Typically elixir functions expect the data structure in the first parameter position - and the ubiquitous pipe operator exploits that fact. In Haskell the design pressure of “currying by default” leads to the last parameter position being ideal for the data structure.

That being said partial function application is still tremendously useful.

Don’t you find that the design of the elixir standard library somewhat limits the usefulness of currying?

Yes, you are right. Many functions in Elixir put the ‘most important argument’ at the front for this reason. This is often the parameter that changes the most.

Maybe there is a way to create a currying-like function that leaves the first argument open, although this might be a bad idea as it does not seem very idiomatic to me.

On the other hand, Currying does allow you to do things like:

use Currying

list = [1,2,3,4]

&Enum.into/3
|> curry(list)
|> curry(%{}) 
|> curry(fn x -> {x, x*x} end)

Given that this is just part of your larger effort I think you need to stick with what you have - it’s just that there is going to be an “impedance mismatch” with functions originating from the outside of your library as they are going to apply different criteria for the ordering of their function parameters (including possibly default parameters).

I was talking more about the usefulness of curried functions in isolation, in an environment that doesn’t default to it. In Haskell curried functions seem to be a result of a focus on single parameter functions, i.e. functions that compose easily; curried functions in essence seem to supply a mechanism that allows functions to be “configured” one-parameter-at-time via partial application until they can become part of a function composition (ultimately driving towards the Pointfree style).

So given the process of curried functions being the first step towards function composition I would expect in Elixir “the spirit of currying” to drive toward a pattern like

result = data_struct |> fn1pa |> fn2pa |> fn3pa

which may be useful when the functions in the composition aren’t “configured” (partially applied) inside the current function’s closure.

But within the context of your larger library the goals seem to be entirely different.

1 Like

Yeah I was thinking of the ‘most changing argument being in first position in Elixir’ too, it is the one big big thing that has bugged me from coming from Erlang, the most changing argument SHOULD be in the last position.

However, the curry could always build things in reverse maybe? Or just special code to put the last argument as the first in the actual call?

EDIT: And on a side note, the most changing argument in last position is a BEAM thing too, the BEAM can optimize function calls that have matching head arguments better than it can matching tails (if the head differs then it has to rebuild the entire argument list instead of being able to re-use the start, yes it is different from how Lists work).

2 Likes

This might be interesting! I’m looking for libraries with solid functor, applicative, etc. implementation, to see how this can be combined with my disc_union library. I’ve created a library for discriminated unions (product and sum types) with compile-time warnings, that is basically a bunch of macros that will throw an error at compile time when you miss to cover one of the defined cases.
It would be uber cool if this could be automatically combined with some of your protocols.

1 Like

Interesting stuff QQwy.

I’m a huge fan of FantasyLand and I’ve been trying hard to learn this stuff.

Is it just me, or does Elixir have its map arguments the wrong way round, much like the underscore.js library did, which Brian Lonsdorf gently pointed out in his talk http://functionaltalks.org/2013/05/27/brian-lonsdorf-hey-underscore-youre-doing-it-wrong/

Edit: Sorry OvermindDL1, looks like you already made that point!

2 Likes

Approaches that make sense in contexts with first class currying don’t necessarily apply to those contexts without.

1 Like

Yeah I would love if Elixir put it back to the end in a major version change, like 2.0 or so. I’d also think it would be awesome if you were highly encouraged by the elixir compiler (like it warninged at you if you did not) put a function spec right before the function with the types, and the compiler would check the types (dialyzer-like) at compile-time, maybe even throw in when checks based on those checks (unless explicitly requested not to some how if efficiency was important, or if it verified all paths in the final ‘production’ release were safe).

I want a type system in Erlang so badly, been wanting it since I first started using Erlang over a decade ago… :cry:

I think Elm does a type system pretty well, though it has a few warts that need fixing very very badly, however it is easy to use and the compiler gives awesome messages, even telling you how to fix your code. Haskell’s is awesome, though that may be too extreme for most.

1 Like

I think currying can be very cool, but there isn’t a plan here for how to deal with the massive overhead incurred by layering anonymous function on anonymous function for every argument ever. Until someone writes the BEAM to have proper currying support there’s nothing Elixir itself can do.

1 Like

To be clear, I’m not trying to say that the work Qqwy has done here isn’t cool, it definitely is. I also see how it can be useful for the library he’s making.

The point I’m trying to make is that proper currying requires support goes far deeper than even Elixir the language can provide, and that without such support I see little value in Elixir 2.0 trying to fake it or go with |> to last argument and so forth.

1 Like

I completely agree with @benwilson512 on this. Elixir has made the choice of taking the first parameter as ‘most important’ parameter for its functions. The BEAM does not have built-in support for currying. The library I made here is indeed slower than not using currying, as anonymous functions are being built in intermediate steps.

Elixir is Elixir because of the design choices that were made (Both in itself, and also partially the things that the BEAM or Erlang decided for it). Elixir is awesome because of its ability to build on top of what’s already built in the past on the BEAM.

Elixir is a dynamically typed language with awesome ideas on macro-creation, improving documentation and testing, and promoting explicitness in general (basically, the dynamic types are the only things that aren’t explicit in Elixir).

I really like Elixir because of these things. There are other languages which I like for different reasons. I like Haskell because of its built-in currying capabilities, and its purity which allows for clearer pattern-matching/guards than in Elixir. I dislike Haskell for the ease at which one can write intelligible programs, and for its high learning curve because of some other design decisions (many of which exist for historical reasons).

I like Ruby because it lets you quickly prototype things. I dislike Ruby for its implicitness and approach to monkey-patching.

I like Prolog because it uses the concept of unification and logic programming. I dislike Prolog because it is very hard to wrap your mind around this difference, and because it is very hard to write non-trivial programs with it.

I like Idris because it is a dependently-typed functional language that lets you reason about your code, instead of testing it. I dislike Idris because it still is very new and badly documented and also has a very high learning curve for newcomers.

I like Inform 7 because it lets you write programs in the English language, like they are books. I dislike Inform 7 because you can only use it to write Interactive Fiction with it.

These are just some languages I’ve experienced over the years. There are many more. I would love a language that would perfectly fit my own needs, but the only way to get such a language is to create it myself. Also, these needs will change over time.

And finally, there is no single language that is best for all tasks. It is easier to formulate some tasks in some languages, but there is no one-language-beats-all.

I’m not entirely sure how I ended up here, but anyway, that was my rant for the day, or something :sweat_smile:

7 Likes

Just a little note: Currying has been updated to version 1.0.3 which is a minor change that does not change anything, but cleans up some of the code behind the scenes, possibly making the code slightly faster and at least more readable :slight_smile: .

1 Like