Imperative OOP Ceremony: Methods

As of March 2020, School of Haskell has been switched to read-only mode.

Functions

Let's write our first function. It simply substracts 1 from its argument. (Please don't mind the ' after return for now.)

-- show
import Prelude hiding ((.))
x.f = f(x)
-- /show
apply(f) = \(x) -> f(x)
return'(a) = a
-- show

function(x) = do {
  return'(x - 1);
}

main = do {
  print(function(42));
}
-- /show

Anonymous Functions

We can also define a function declaring its parameter after the equals sign. (A right-hand side argument declaration is surrounded by \... ->.) By doing so the expression on the right-hand side of our function declaration becomes a valid function on its own, which can be used inline, i.e. without an identifier.

import Prelude hiding ((.))
x.f = f(x)
apply(f) = \(x) -> f(x)
return'(a) = a
-- show

function = \(x) -> do {
  return'(x - 1);
}

main = do {
  print(function(42));
  print(42.apply(function));
  print(42.apply(\(x) -> do {return'(x - 1);}));
}
-- /show

An anonymous function is a lambda expression in FP lingo. (The \ is supposed to resemble a λ.)

Closures

A closure is a partially applicable function, i.e. a function that accepts a single argument and returns a function, which accepts a single argument and returns a value. (The nesting can be as deep as you like.)

import Prelude hiding ((.))
x.f = f(x)
apply(f) = \(x) -> f(x)
return'(a) = a
-- show
closure = \(a) -> do {
  return'(
    \(b) -> do {
      return'(b - a);
    }
  );
}

partiallyAppliedClosure(x) = do {
  return'(x.closure(1));
}

main = do {
  print(partiallyAppliedClosure(42));
  print(42.apply(partiallyAppliedClosure));
  print(42.closure(1));
}
-- /show

A closure can be made a function that accepts a tuple of arguments and vice versa. This process is called "(un-)currying".

import Prelude hiding ((.))
x.f = f(x)
apply(f) = \(x) -> f(x)
return'(a) = a
-- show
closure = \(a) -> do {
  return'(
    \(b) -> do {
      return'(b - a);
    }
  );
}
closure' = do { return'(uncurry(closure)); }

function(x, y) = do {
  return'(y - 1);
}
function' = do { return'(curry(function)); }

main = do {
  print(closure'(1, 42));
  print(42.function'(1));
}
-- /show

Operators

At the end of the last tutorial we saw that x.apply(f) can be written as f `apply` x. The reason for that is that apply is a closure with two arguments, a so-called operator. You normally see operators like - written between its two arguments like in 42 - 1. In order to use an operator written with alphanumeric characters like closure in infix notation, we have to surround it in backticks: 1 `closure` 42. In return, we need to surround - in parenthesis in order to treat it like an ordinary closure, i.e. 1.(-)(42).

42.closure(1)   -- -> 41
1 `closure` 42  -- -> 41
42 - 1          -- -> 41
1.(-)(42)       -- -> 41

As we can guess, closure is - but with its arguments flipped. Since closure is alphanumeric and - is not, this makes closure the alphanumeric equivalent of -, provided that we will use an alphanumeric operator like closure in object-oriented notation and a non-alphanumeric operator like - in infix notation.

42.closure(1)  -- -> 41
42 - 1         -- -> 41

Let's give closure a propper name, sub, and define it in terms of -. As we have seen, sub is - but with its arguments flipped, so:

sub = do {
  return'(flip((-)));
}

42 - 1     -- -> 41
42.sub(1)  -- -> 41

Methods

As we have seen in the previous tutorial, we can write a function that takes one argument in object-oriented notation, too. This is the very definition of our . operator: x.f = f(x). As we are parenthesis-phile, we introduced apply which is yet another convoluted way of function application: f `apply` x = f(x). Together, . and apply form inversion of control.

x.apply(f) == f(x)

It is interesting to note that f(x) has parenthesis, whereas x.f doesn't. This is not a necessity, but a choice. We could define f() which looks nice in x.f(), but looks awkward in f()(x). Therefore, we will avoid empty parenthesis from now on.

decrement() = \(x) -> do {
  return'(x.sub(1));
}

dec = \(x) -> do {
  return'(x.sub(1));
}

42.sub(1)       -- -> 41
42.decrement()  -- -> 41 :(
42.dec          -- -> 41 :)

Function Composition

You may have noticed, that sub = do { return'(flip((-))); } doesn't contain any variable. This style is called "pointfree" in FP lingo. (We could have defined sub as b `sub` a = do { return'(a - b); }, too.)

When define functions in terms of other functions, we often concatenate two functions into one. This is called function composition. For example, given the functions even, which returns True or False depending on the argument being even or not, and the function not, which inverts True and False, we can easily define a function odd, both in pointfull and pointfree style:

import Prelude hiding ((.))
x.f = f(x)
return'(a) = a
-- show
f `compose` g = \(x) -> do { return'(f(g(x))); }

oddPointfull(x) = do { return'(not(even(x))); }
oddPointfree = do { return'(not `compose` even); }

main = do {
  print(42.oddPointfull);
  print(42.oddPointfree);
}
-- /show

Haskell already has a compose operator of its own, i.e. ., which we hid from the Prelude in the previous tutorial in order to define object-oriented notation. This is why we just had to define our own compose operator.

Constants

Let's define a constant.

import Prelude hiding ((.))
x.f = f(x)
-- show
constant = 42

main = do {
  print(constant);
}
-- /show

We may have written constant = do { return'(42); }, but this doesn't make sense, since constant is a constant, and not a function, right? Well, in Haskell there is virtually no difference between a constant and the return value of a function, as the same argument applied to the same function will always yield the same return value. In FP lingo, functions are referentially transparent. (This has the benefit of return values being cachable - they can be memoized in FP lingo.) Therefore, we can treat functions just like constants and ommit the do { return'( ... ); } ceremony.

import Prelude hiding ((.))
x.f = f(x)
-- show
f `compose` g = \(x) -> f(g(x))

oddPointfull(x) = not(even(x))
oddPointfree = not `compose` even

main = do {
  print(42.oddPointfull);
  print(42.oddPointfree);
}
-- /show