Partially applied functions [duplicate]
Asked Answered
S

4

1

While studying functional programming, the concept partially applied functions comes up a lot. In Haskell, something like the built-in function take is considered to be partially applied.

I am still unclear as to what it exactly means for a function to be partially applied or the use/implication of it.

Stacee answered 7/12, 2017 at 16:12 Comment(3)
Consider the function take. You could for example write take 4 [2,3,5,7,11,13,17,19] and it would evaluate to [2,3,5,7]. However, you could also define takeFour = take 4 and now you have a partially applied take function. It's partially applied because it's not been given the second input. This takeFour function can be applied to any list and it'll return the first four elements of that list. What we essentially did was create a specialized version of take by only giving take one argument instead of two.Came
Note that this doesn't really have anything to do with partially applied type parameters...Reset
The short answer is: all functions in Haskell take exactly one argument, and so there is no partial application. A call like take 4 primes is really an expression consisting of two function calls; take 4 returns a new function which is then applied to primes (the equivalent explicitly parenthesized expression is (take 4) primes). The term "partial application" is used, though, to describe the situation where you aren't making all the function calls needed to ultimately get a non-function value.Osuna
D
3

A function by itself can't be "partially applied" or not. It's a meaningless concept.

When you say that a function is "partially applied", you refer to how the function is called (aka "applied"). If the function is called with all its parameters, then it is said to be "fully applied". If some of the parameters are missing, then the function is said to be "partially applied".

For example:

-- The function `take` is fully applied here:
oneTwoThree = take 3 [1,2,3,4,5]

-- The function `take` is partially applied here:
take10 = take 10   -- see? it's missing one last argument

-- The function `take10` is fully applied here:
oneToTen = take10 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,42]

The result of a partial function application is another function - a function that still "expects" to get its missing arguments - like the take10 in the example above, which still "expects" to receive the list.


Of course, it all gets a bit more complicated once you get into the higher-order functions - i.e. functions that take other functions as arguments or return other functions as result. Consider this function:

mkTake n = take (n+5)

The function mkTake has only one parameter, but it returns another function as result. Now, consider this:

x = mkTake 10
y = mkTake 10 [1,2,3]

On the first line, the function mkTake is, obviously, "fully applied", because it is given one argument, which is exactly how many arguments it expects. But the second line is also valid: since mkTake 10 returns a function, I can then call this function with another parameter. So what does it make mkTake? "Overly applied", I guess?

Then consider the fact that (barring compiler optimizations) all functions are, mathematically speaking, functions of exactly one argument. How could this be? When you declare a function take n l = ..., what you're "conceptually" saying is take = \n -> \l -> ... - that is, take is a function that takes argument n and returns another function that takes argument l and returns some result.

So the bottom line is that the concept of "partial application" isn't really that strictly defined, it's just a handy shorthand to refer to functions that "ought to" (as in common sense) take N arguments, but are instead given M < N arguments.

Devise answered 7/12, 2017 at 16:23 Comment(0)
M
2

the classic example is

add     :: Int -> Int -> Int
add x y = x + y

add function takes two arguments and adds them, we can now implement

increment = add 1

by partially applying add, which now waits for the other argument.

Mellifluent answered 7/12, 2017 at 16:23 Comment(0)
O
2

Strictly speaking, partial application refers to a situation where you supply fewer arguments than expected to a function, and get back a new function created on the fly that expects the remaining arguments.

Also strictly speaking, this does not apply in Haskell, because every function takes exactly one argument. There is no partial application, because you either apply the function to its argument or you don't.

However, Haskell provides syntactic sugar for defining functions that includes emulating multi-argument functions. In Haskell, then, "partial application" refers to supplying fewer than the full number of arguments needed to obtain a value that cannot be further applied to another argument. Using everyone's favorite add example,

add :: Int -> Int -> Int
add x y = x + y

the type indicates that add takes one argument of type Int and returns a function of type Int -> Int. The -> type constructor is right-associative, so it helps to explicitly parenthesize it to emphasize the one-argument nature of a Haskell function: Int -> (Int -> Int).

When calling such a "multi-argument" function, we take advantage of the fact the function application is left-associative, so we can write add 3 5 instead of (add 3) 5. The call add 3 is thought of as partial application, because we could further apply the result to another argument.


I mentioned the syntactic sugar Haskell provides to ease defining complex higher-order functions. There is one fundamental way to define a function: using a lambda expression. For example, to define a function that adds 3 to its argument, we write

\x -> x + 3

For ease of reference, we can assign a name to this function:

add = \x -> x + 3

To further ease the defintion, Haskell lets us write in its place

add x = x + 3

hiding the explicit lambda expression.

For higher-order, "multiargument" functions, we would write a function to add two values as

\x -> \y -> x + y

To hide the currying, we can write instead

\x y -> x + y.

Combined with the syntactic sugar of replacing a lambda expression with a paramterized name, all of the following are equivalent definitions for the explicitly typed function add :: Num a => a -> a -> a:

add = \x -> \y -> x + y -- or \x y -> x + y as noted above
add x = \y -> x + y
add x y = x + y
Osuna answered 7/12, 2017 at 16:55 Comment(2)
It think it will make it clearer if you remove 2nd line in the last example.Mellifluent
@karafka I want to leave it there to demonstrate all the various ways to write the same fundamental expression, but I see your point. I've moved that form aside to a comment to emphasize the parameterized assignments.Osuna
M
0

This is best understood by example. Here's the type of the addition operator:

(+) :: Num a => a -> a -> a

What does it mean? As you know, it takes two numbers and outputs another one. Right? Well, sort of.

You see, a -> a -> a actually means this: a -> (a -> a). Whoa, looks weird! This means that (+) is a function that takes one argument and outputs a function(!!) that also takes one argument and outputs a value.

What this means is you can supply that one value to (+) to get another, partially applied, function:

(+ 5) :: Num a => a -> a

This function takes one argument and adds five to it. Now, call that new function with some argument:

Prelude> (+5) 3
8

Here you go! Partial function application at work!

Note the type of (+ 5) 3:

(+5) 3 :: Num a => a

Look what we end up with:

  1. (+) :: Num a => a -> a -> a
  2. (+ 5) :: Num a => a -> a
  3. (+5) 3 :: Num a => a

Do you see how the number of as in the type signature decreases by one every time you add a new argument?

  1. Function with one argument that outputs a function with one argument that in turn, outputs a value
  2. Function with one argument that outputs a value
  3. The value itself

"...something like the built-in function take is considered to be partially applied" - I don't quite understand what this means. The function take itself is not partially applied. A function is partially applied when it is the result of supplying between 1 and (N - 1) arguments to a function that takes N arguments. So, take 5 is a partially applied function, but take and take 5 [1..10] are not.

Moneychanger answered 7/12, 2017 at 16:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.