2020-09-03
|~4 min read
|798 words
Today I learned about total functions, as well as their antipode, partial functions.
According to NIST, a total function is “[a] function which is defined for all inputs of the right type, that is, for all of a domain.” A partial function is the opposite, i.e. a function which is not defined for all inputs.
I came across this concept in the context of programming - specifically a linting program that would review applications for adherence to a rule set that functions be written as total functions. Partial functions are generally unnecessary and depending on the language (or the linting rules) may prevent your program from compiling.
You almost never have an excuse for writing a partial function! Partial Functions | Haskell
To help understand why partial functions are unnecessary, as well as solidify these concepts, I wanted to understand how you might rewrite partial functions into total functions.
Two examples that kept coming up were:
Head
- a function that returns the first element of a list.Reciprocal
- a function that returns the reciprocal of a number.Looking first at head
, we can see how it might fail:
function head(array: string[]) {
return array[0]
}
const b = head([])
b.toUpperCase()
If the array is empty, this will pass the type checking, however, it will explode at run time as we try to invoke toUpperCase
on undefined
.1
On the other hand, reciprocal
is interesting in that the built-in javascript implementation of dividing by zero is already total.
function reciprocal(x: number) {
return 1 / x
}
const y = reciprocal(0)
Whereas other languages, like Python, throw an error when dividing by zero:
>>> 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Javascript handles the case:
console.log(1 / 0, typeof 1 / 0) // Infinity NaN
console.log(0 / 0) // NaN NaN
(Note: Nan
, which stands for “Not-a-Number”, is part of the ECMAScript Number type2)
So, if head
is a partial function, how do we refactor it since “You almost never have an excuse for writing a partial function!“?
To help me think about this, I looked at how fp-ts
implemented it. fp-ts
is a popular library for using functional paradigms with Typescript.
// https://github.com/gcanti/fp-ts/blob/5d0126587d36977540962b6580ae95df988234e4/src/ReadonlyArray.ts#L454-L456
export function head<A>(as: ReadonlyArray<A>): Option<A> {
return isEmpty(as) ? none : some(as[0])
}
// https://github.com/gcanti/fp-ts/blob/5d0126587d36977540962b6580ae95df988234e4/src/ReadonlyArray.ts#L346-L348
export function isEmpty<A>(as: ReadonlyArray<A>): boolean {
return as.length === 0
}
// https://github.com/gcanti/fp-ts/blob/5d0126587d36977540962b6580ae95df988234e4/src/Option.ts#L109
export const some = <A>(a: A): Option<A> => ({ _tag: "Some", value: a })
// https://github.com/gcanti/fp-ts/blob/5d0126587d36977540962b6580ae95df988234e4/src/Option.ts#L103
export const none: Option<never> = { _tag: "None" }
To allow for all lists, even empty ones, fp-ts
checks first the length of the array using isSome
. If the list is empty, it returns none
. If it’s defined, then it returns some
of the first list.3
The most interesting part for me is what Option
is. From the Option.ts
file:
/** * ```ts * type Option<A> = None | Some<A> * ``` * * `Option<A>` is a container for an optional value of type `A`. If the value of type `A` is present, the `Option<A>` is * an instance of `Some<A>`, containing the present value of type `A`. If the value is absent, the `Option<A>` is an * instance of `None`. * * An option could be looked at as a collection or foldable structure with either one or zero elements. * Another way to look at `Option` is: it represents the effect of a possibly failing computation. * * @since 2.0.0 */
Using this:
import { head } from "fp-ts/Array"
console.log(head([])) // {_tag: None}
console.log(head([1])) // {_tag: some, value: 1}
So, a total function is one that is defined for inputs of an accepted type and it’s possible to refactor partial functions to be total.
We don’t need to write them all for ourselves though. Tools like fp-ts
provide guardrails to make it easy. On the other hand, this whole investigation started because as a team we were looking at linting plugins for ESLint which has both a plugin for functional programming and more specifically total functions.
fp-ts
wraps return values in an object with a _tag
property.Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!