Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Function Composition rely on Partial Application?

Does Function Composition rely on Partial Application?

Here's my understanding:

Observe the following functions that have some duplication of function calls:

let updateCells (grid:Map<(int * int), Cell>) =

    grid |> Map.toSeq
         |> Seq.map snd
         |> Seq.fold (fun grid c -> grid |> setReaction (c.X, c.Y)) grid

let mutable _cells = ObservableCollection<Cell>( grid |> Map.toSeq
                                                      |> Seq.map snd
                                                      |> Seq.toList )
let cycleHandler _ = 

    self.Cells <- ObservableCollection<Cell>( grid |> cycleThroughCells
                                                   |> Map.toSeq
                                                   |> Seq.map snd
                                                   |> Seq.toList )

If you’ve noticed, the following code appears in all three functions:

grid |> Map.toSeq
     |> Seq.map snd

Function Composition

Within functional programming, we can fuse functions together so that they can become one function.

To do this, let’s create a new function from the duplicated sequence of functions:

let getCells = Map.toSeq >> Seq.map snd >> Seq.toList

Now if you’re attentive, you will have noticed that we don’t use any arguments when using Function Composition. Hence, the grid value is not used. The reason behind this is because of Partial Application.

Partial Application

I’m still learning all these functional programming techniques. However, my understanding is that partial application is a technique within functional programming that postpones the need to accept a complete set of arguments for a given function. In other words, partial application is the act of deferring the acceptance of a complete set of arguments for a given function in which there is an expectation that the end-client will provide the rest of the arguments later. At least, that’s my understanding.

We can now take a function like:

let updateCells (grid:Map<(int * int), Cell>) =

    grid |> Map.toSeq
         |> Seq.map snd
         |> Seq.fold (fun grid c -> grid |> setReaction (c.X, c.Y)) grid

And refactor it to something like:

let updateCells (grid:Map<(int * int), Cell>) =

    grid |> getCells
         |> Seq.fold (fun grid c -> grid |> setReaction (c.X, c.Y)) grid

Are my thoughts regarding Function Composition being coupled with Partial Application accurate?

like image 483
Scott Nimrod Avatar asked Nov 29 '22 23:11

Scott Nimrod


1 Answers

Generics

Actually, if you take the expression

let getCells = Map.toSeq >> Seq.map snd >> Seq.toList

and attempt to compile it as a stand-alone expression, you'll get a compiler error:

error FS0030: Value restriction. The value 'getCells' has been inferred to have generic type val getCells : (Map<'_a,'_b> -> '_b list) when '_a : comparison
Either make the arguments to 'getCells' explicit or, if you do not intend for it to be generic, add a type annotation.

The reason it works in your case is because you're using the getCells function with grid, which means that the compiler infers it to have a constrained type.

In order to keep it generic, you can rephrase it using an explicit argument:

let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList

This expression is a valid stand-alone expression of the type Map<'a,'b> -> 'b list when 'a : comparison.

Point-free

The style used with the >> function composition operator is called point-free. It works well with partial application, but isn't quite the same.

Application

There is, however, an example of partial function application in this example:

let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList

The function snd has the following type:

'a * 'b -> 'b

It's function that takes a single argument.

You could also write the above getCells function without partial application of the snd function:

let getCells xs = xs |> Map.toSeq |> Seq.map (fun x -> snd x) |> Seq.toList

Notice that instead of a partially applied function passed to Seq.map, you can pass a lambda expression. The getCells function is still a function composed from other functions, but it no longer relies on partial application of snd.

Thus, to partially (pun intended) answer your question: function composition doesn't have to rely on partial function composition.

Currying

In F#, functions are curried by default. This means that all functions take exactly one argument, and returns a value. Sometimes (often), the return value is another function.

Consider, as an example, the Seq.map function. If you call it with one argument, the return value is another function:

Seq.map snd

This expression has the type seq<'a * 'b> -> seq<'b>, because the return value of Seq.map snd is another function.

Eta reduction

This means that you can perform an Eta reduction on the above lambda expression fun x -> snd x, because x appears on both sides of the expression. The result is simply snd, and the entire expression becomes

let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList

As you can see, partial application isn't necessary for function composition, but it does make it much easier.

Impartial

The above composition using the pipe operator (|>) still relies on partial application of the functions Map.toSeq, Seq.map, etcetera. In order to demonstrate that composition doesn't rely on partial application, here's an 'impartial' (the opposite of partial? (pun)) alternative:

let getCells xs =
    xs
    |> (fun xs' -> Map.toSeq xs')
    |> (fun xs' -> Seq.map (fun x -> snd x) xs')
    |> (fun xs' -> Seq.toList xs')

Notice that this version makes extensive use of lambda expressions instead of partial application.

I wouldn't compose functions in this way; I only included this alternative to demonstrate that it can be done.

like image 198
Mark Seemann Avatar answered Feb 04 '23 05:02

Mark Seemann