Skip to content

Instantly share code, notes, and snippets.

@mdg
Last active September 20, 2018 15:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdg/4c60368314764eaa47b926d21fa39427 to your computer and use it in GitHub Desktop.
Save mdg/4c60368314764eaa47b926d21fa39427 to your computer and use it in GitHub Desktop.
rough draft of a blog post to go on leema.org

currying vs. default parameters

I didn't set out to implement currying. I was trying to implement closures but it seemed like it would be easier to implement closures if I added currying first.

But I didn't set out to implement closures either. I originally wanted to implement asynchronous expressions and futures but they too seemed easier to implement if I already had closures.

So here I find myself trying to implement currying, but I don't have a good sense of what the syntax should be.

what is currying?

Currying is when you call a function with a subset of its required parameters, and in the process create a new function that takes the parameters that have not yet been provided.

A simple example in Haskell might look like this:

multiply a b = a * b

main =
  let double = multiply 2
      answer = double 7
   in do print answer

default parameters

In Haskell, currying just looks like a normal function call but without all of the normal parameters.

In other languages, like Python, calling a function without all the parameters is how you use an alternative language feature: default parameters.

In this example, the two calls to foo are equivalent:

def foo(a, b=None):
    bar(a)
    if b is None:
        baz(b)

foo(8)
foo(8, None)

the syntax conflict

My plan for Leema has been to support both of these features, currying and default parameters. However, the problem with supporting both of them is that the standard syntax of calling a function while leaving out some of the parameters is ambiguous. Did the programmer intend to curry the function or call it with its default parameters?

So the question then is what is the best way for the programmer to disambiguate between these two possible intentions? I'm thinking of this question as two smaller questions.

Is there special syntax to indicate a call should be curried? Is there special syntax to indicate a call should pass default parameters?

Answering them together gives 4 options which I'll consider separately.

  1. Neither currying or default parameters have special syntax
  2. Default parameters have special syntax, but currying does not
  3. Currying has special syntax, but default parameters do not
  4. Both currying and default parameters require special syntax

no special syntax. at all.

I didn't really consider this case originally because it is so ambiguous. I can imagine a scenario where the ambiguity could be resolved by type inference. Is the result of the call used as the return type of the function or is it used as a function?

I'm not confident that this could be inferred frequently enough without resorting to type hints. If type hints are usually required then it would kind of be like the fourth option but accidentally.

Another alternative might be to look at whether the function has default parameters. If so, apply them, otherwise curry the function. This could be done consistently but might be too implicit for programmers to easily understand.

special syntax for default parameters

What would a special syntax for default parameters look like? Here are a few possible options that I'm thinking of.

    let with_defaults := foo(x, y)++

    let with_defaults := foo(x, y, ++)

    let with_defaults := foo(x, y, ...)
    ## does elixir or rust use ... or .. for something?
    ## one of them uses it in struct initialization or pattern matching

special syntax for currying

Here are twot syntax ideas to indicate that a function should be curried.

## In this model, all unpassed arguments are curried by the `...` token.
func main() ->
    let curryf1 := foo(x, y, ...)
--

## In this model, each curried parameter has to be indicated specifically
## by passing a `?` for that parameter. This is also similar to SQL syntax.
func main() ->
    let curryf2 := foo(x, y, ?)
    let curryf3 := foo(?, y, z)

    let result2 := curryf2(z)
    let result3 := curryf3(x)
--

special syntax for both

The fourth case is to be explicit so that using default parameters or currying are both visually distinct from a normal function call where all parameters are provided.

decision?

When I started this blog post, I was kind of leaning towards having a special syntax for currying and nothing extra for default parameters. In the process of writing this though, there is something appealing the explicitness of having a special syntax for default parameters as well.

In both of those cases, there is a special syntax for currying and between the 2 options, I prefer the ? syntax.

The question for default parameters is less obvious to me. The most common use case for default paramters is to make it easier to add a new parameter to a function that's already being used all over the place. This is especially important for dynamic languages where you may not discover some usage until the code is failing in production.

In this case, if there were a special syntax, it would require fixing all usages to the function and adding the special syntax. This kind of makes sense until you consider adding a second default parameter to a function where, at that point, you don't need to add anything. This suggests that the only two real options are to require no special syntax for default parameters or to not support default parameters at all.

Thanks for reading along. Checkout Leema if you get a chance sometime and see how this decision for currying syntax played out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment