Partial Application

A large part of why functional programming is so powerful is being able to conveniently pass around functions as if they're values.

This lets you create functions whose behavior is more generalised as a portion of the behavior can be passed down as a parameter.

The Closure Type

Among types such as int or (float, float), functions-as-values also have types in the form of closures.

fn(int, int -> int)

     The type of a closure which expects two int parameters and returns an int

So for example;

fn change_if_just f m as fn(int -> int), Maybe int -> Maybe int =
  match m
  | Nothing -> Nothing
  | Just n  -> Just (f n)

     A function which runs a function to change a value if it exists

The Magic # Unary Operator

To treat a function as if it's a value, the # symbol is used.

fn add_five_if_just m as Maybe int -> Maybe int =
  change_if_just #add_five m

fn add_five x as int -> int =
  x + 5

     A function which adds 5 to an integer if it exists

The # symbol is a general-purpose way to pass various expressions as closures and can be used in a couple of different ways.

fn add x y as int, int -> int = x + y

// ... //

// turns the function into a closure of type
// fn(int, int -> int)
#add

// partially applys the function to create a closure with one less parameter
// fn(int -> int)
#(add 5)

// partially applys an operator to create a closure with a single parameter
// fn(int -> int)
#(+ 5)

// turn a literal value into a closure
// fn( -> int), can also be written as fn(int)
#5

With this in mind, we can rewrite the previous example as:

fn add_five_if_just m as Maybe int -> Maybe int =
  change_if_just #(+ 5) m

     A function which adds 5 to an integer if it exists

Anonymous Functions with Lambdas

If a function doesn't seem important enough to give it a name, we can inline it with a lambda.

fn main =
  (\n -> n + 1) 5

     Runs the inline function with the parameter 5 to create 6

Lambdas can also be passed as closures the same way as named functions.

fn add_five_if_just m as Maybe int -> Maybe int =
  change_if_just #(\n -> n + 5) m

Partially Applicating Where-Bindings

TODO: Is it overly complicated and confusing to explain this here? Maybe we should have a separate design patterns chapter

A common design pattern is to partially apply where-bindings

fn add_five_if_just m as Maybe int -> Maybe int =
  change_if_just #(add 5)
 where
  fn add x y = x + y

  fn change_if_just f as fn(int -> int) -> Maybe int =
    match m
    | Nothing -> Nothing
    | Just n  -> Just (f n)