Partial Application vs Parameters - which do you prefer?

I was watching some functional programming video where the person codes the exact application in multiple ways.

One of the preferred ways was using partial applications for dependency injection and I kept thinking about some packages I was writing because I didn’t use the partial application because it felt odd in Elixir for some reason.

So I am curious about what would you prefer to read and write every day if you have to.

Using just extra parameters

def get_templates(repo, logger, pagination \\ []) do
  # ...
end

Or partial application

def get_templates(repo, logger) do
  fn pagination ->
  end
end
2 Likes

Without library support I think writing curried functions (your second example is not partial application) is tedious. Also in elixir and erlang curried functions are expensive in terms of reduction count, as they are recursive calls that might not be necessary.

Especially when they are arpitrarily nested.

Also your curried example is not valid elixir, as anonymous functions can’t have optional parameters.

But yes, in general, I like partial application and currying, but in elixir/erlang I only felt to do so during the start, when I came from Haskell and was used to use that style. But now I rarely feel the need.

4 Likes

Yeah, my bad, I just copied pasted the code :sweat_smile:

Where can I read more about this? I don’t understand what you mean. Does this mean that I shouldn’t go crazy with FP in Elixir?

hmmm when I called get_templates I get a partially applied function, no? I guess I miss understood about this

I just googled this since I keep getting confused what people mean about Curry vs Partial Application language agnostic - What is the difference between currying and partial application? - Stack Overflow

But this is going sideways.

Yeah, I just miss the clearness of such of style since it is easier to separate the API from the dependency injection portion of it.

Exactly, Partial application is a call side thing, which is pretty easy when you have curried functions, but also possible on non curried functions, by wrapping them into a clojure.

def add(a, b), do: a + b

def add2(), do: &add(2, &1)
def add(a), do: &(a + &1)

def add2(), do: add(2)

In both cases in add2/0 we use partial application to return a function, which would add 2 to its argument.

Just google explanations about how the scheduler assigns calculation times.

SImplified, each function you call will increase the reduction count of your process, and if it reaches a certain count, the process will be halted and the BEAM switches to the next one. Then other processes can run until your original is back on the table and can be run.

So, the more “needless” functions you call, the less CPU time you get.

I rarely use DI. And if I do, I usually consider it a part of the API. I do treat is quite often as call-site configuration ovverrides and handle them as part of the passed in config map/KW-list.

2 Likes

Where can I read more about this? I don’t understand what you mean. Does this mean that I shouldn’t go crazy with FP in Elixir?

FP doesn’t mean currying. Elixir is a functional programming language, but it doesn’t have any syntactic sugar for currying so using it is, as @NobbZ put it, tedious. In languages like Haskell currying is “built in”. In the case of Haskell, that also hides the fact that everything is curried (except when you make use of it).

For an example of what currying is check out Ramda’s curry, which converts an ordinary function into a curried function in JS Ramda Documentation.

4 Likes

I may be using the wrong words to express what I mean. Change FP to FP practices; I am not sure if that would make the differences :smile:

Yeah this is why I avoid doing those practices, so I wanted to confirm if I was wrong for thinking that way :blush:

Worth saying, in this case, I only have one parameter, but the idea was to pass all the DI in the first function.

I don’t understand why the conversation about currying, my intention is not having a curried function but a partial application function.

Is this example any better? I am not trying to get a function with arity 1, just partially apply DI.

def get_templates(repo, logger, ...) do
  fn pagination ->
  end
end

If you have a function get_templates and it normally takes a repo, but you want to partially apply it for a specific repo, you could do that yes. For example

def get_templates(repo, pagination) do
  ...
end

def partial_get_templates(pagination) do
  get_templates(MyRepo, pagination)
end

def main() do
  partial_get_templates(5)

  # Or, without the extra def
  partial = fn pagination -> get_templates(MyRepo, pagination)

  partial.(5)
end

But neither one is very practical and the partial_get_templates relies on MyRepo being global.

I should also point out that partial application doesn’t really do DI. You still have to take a function, let’s call it F1, and partially apply it with say 1 argument called A1, and call that F2. Then you use F2 somewhere. But F2 is hard coded to A1. There’s no DI. You still have to solve the DI problem by eg modifying functions to take a module and call functions on it, or pass the capture around.

If you want to be able to swap out implementations there are a few solutions. Some examples:

Using config to choose implementation

Have your code take a Module and use behaviors to define callbacks on that module, then you can at runtime decide which module to use, your code doesn’t care as long as the module has the expected functions defined.

And there’s lots more. But none of it is related to partial application, really.

All rely on MyRepo to exist on runtime as a module globally, therefore I’m not sure what you considere this as a downside.

@NobbZ to clarify, what I meant was that the def example relies on MyRepo being global, while the anonymous function could be used to partially apply anything in the closure of the calling code. Eg

def main do
  user_id = get_user_id()
  partial = fn pagination -> get_templates(user_id, pagination)

  ...

  partial.(5)
end

Since the result of get_user_id is not available at compile time, it can’t be used if you defined the partially applied function with def.

That’s what I meant by downside :slight_smile: both examples have limitations, I was just pointing to this one.

Sorry, I don’t get it. If you provide a default, than that default should have a meaning. In case of DI, the default is about always a module implementing a behaviour.

As you provide a default dependency, you should also implement it. So I do not understand your reasoning.

:thinking:
To qualify as curried I’d expect:

def get_templates(repo) do
  fn logger ->
    fn pagination ->
      # ...
    end
  end
end

i.e. an arity 1 function returning another arity 1 function (or finally a result)

def get_templates(repo, logger, pagination) do
  # ...
end

def get_templates(repo, logger) do
  fn pagination ->
    get_templates(repo, logger, pagination)
  end
end

Partial application takes a function with n parameters and returns a function with less than n parameters.
get_templates/2 returns an fn/1 function which will execute get_templates/3 once it gets that final argument.
That behaviour in my view at least emulates the intent behind partial application.

Note: the default argument syntax sugar can be a bit misleading as it may not be clear to a newcomer that

def get_templates(repo, logger, pagination \\ [] ) do
  # ...
end

actually generates get_templates/3 and get_templates/2 which are technically 2 distinct functions.

My take is that in a general interface you should stick to exposing plain full arity functions, i.e. there should be no speculative (convenience) partial application/currying going on that might be fashionable elsewhere:

const getTemplates = repo => logger => pagination => {
  // ...
}

However it is common practice to hold and encapsulate dependencies inside a function’s closure when there is a benefit to doing so.

I just miss the clearness of such of style since it is easier to separate the API from the dependency injection portion of it.

This is where I don’t follow:

  • The dependency injection aspect merely requires that a function of a specific arity is provided for later execution.
  • “partial application” only comes into play if we have a function that requires additional dependencies, arguments that we have to bind prior to passing it to another function.
  • So while “partial application” may be a (necessary) preliminary action prior to a particular dependency injection, “partial application” and dependecy injection are entirely separate concerns.

So as user of a function which requires another function to be injected, you explicitly “partially apply” only when circumstances actually demand it.

Shorter functions will tend to relinquish the CPU sooner, while longer functions will tend to hog it. But in the bigger scheme of things I don’t think it is something to worry about because eventually everybody will get another kick at the can.

I will agree however that writing functions speculatively in a curried style is pointless (i.e. leads to needless functions).

2 Likes

I fixed the original source code because it had just one parameter (repo) and people kept talking about curry vs partial application since a partial application with one parameter for all the functions is a curried function (from my understand).

Outside of that, understood, I think I have an idea of what should I do with these situations.

Curried function means, that you have a function of a single arg, that returns a function of single arg and so on, until the final value is returned. You curry on the function definition site.

Partial application though, means to not provide all arguments right now and get a function back, which takes the remaining arguments. Partial application is done on the call site.

Currying in Haskell and most ML dialects is it, what makes the partial application easy and effortless.

In Erlang and Elixir there is neither syntactc sugar for currying nor for partial application. So the best thing we can do to partially apply a function is to use the SpecialForms fn/1 and &/1 as already shown before.

Personally, I consider just using &/1 much easier than hand currying a function by hand and then using dotted calls everywhere, despite the fact if I want to partially apply or not.

2 Likes

Because functions are not curried by default, working with curried functions is usually non-idiomatic in Elixir. I did write a library called Currying a while back that lets you curry arbitrary functions. However, partially applying functions in the same way as you’d do in Haskell has two problems in Elixir:

  1. It is slower because the compiler is not opitmized for these kinds of things; curried functions are not ‘built in’ and manually added currying is not removed by the compiler.
  2. In great contrast to both Haskell and Erlang, in Elixir, the ‘most important argument’ is usually the first argument, rather than the last: when partially applying a function, this is usually the thing we want to fill in last, which does not work well when working with currying-based partial application.

However, partial application based on the two super-helpful special forms that @NobbZ already mentioned as well (fn and &) is super helpful, very idiomatic and widely used in practice.


I do not think there exists a battle between parameters on one hand and partial application on the other: Whenever we use a function, this is where we decide whether we want to fill in all parameters at once, or still keep a couple of them blank (which we can do using fn, &, and/or wrapping the actual function we want to call in a new, descriptively named function). This is a decision for the consumer (the place the function is called) of the code, not the producer (the place the function is defined).

4 Likes