How to Define a Function in Haskell and PureScript

4 minute read

Defining functions in these languages is quite concise, and there are a few different forms that you’ll want to be able to recognize.

Basic Functions

A function with parameters can be defined using only whitespace and the equals sign:

name param1 param2 = ...

Any time there are multiple names on the left side on an equals sign, the first is the function name and the remaining are parameter names.

The body of the function is on the right hand side of the equals sign. Here is a simple function that combines two string to make a greeting:

hello name = "Hello, " ++ name

Calling a function is done simply by giving the name with its arguments separated by spaces:

hello "you"

The result of this expression is "Hello, you".

Types

Specifying the type of your function is optional if the compiler can infer it. Adding type signatures is recommended because they are very informative when reading code. Here is our definition including the type signature:

hello :: String -> String
hello name = "Hello, " ++ name

When you define a function this way, the type of the right hand expression should match the return type of the function. Here are some example expressions listed with their types:

hello                  :: String -> String
hello "friend"         :: String
"Hello, " ++ "friend"  :: String

Functions as Values

It is also possible to define functions where the parameter names are not listed on the left:

hello :: String -> String
hello = ...

This works anytime the type of the expression on the right is String -> String.

Unlike many other languauges, functions themselves are values and can be used any place other values can.

Once a function has been defined, its name can be used to refer to it as a value:

greeting :: String -> String
greeting = hello

In this case, we are not calling hello but referring to the function itself. All values including functions are immutable, and never change once created. hello and greeting are now just two names referring to the same function value.

Lambdas

A lambda expression lets us create an anonymous function value:

\param1 param2 -> ...

There is no equals sign here, and this function does not have a name. This evaluates to a function with two parameters. Using our example:

hello :: String -> String
hello = \name -> "Hello, " ++ name

The value on the right of the equals is a complete function of type String -> String. This hello function works exactly the same as our first example.

Pattern Matching

Sometimes your function will have different behavior based on the value of your arguments. In other languages, you would use constructs like switch, case, or if inside your function to handle this.

Pattern matching allows us to redefine our function for specific cases rather than create branching structures:

hello "Alice" = "Hi Alice!"
hello "Bob"   = "Hey Bob"
hello name    = "Hello, " ++ name

Anytime the argument data matches a specific case we have defined, that function body will be used instead. The last case uses a parameter instead of concrete data, so it will handle any case not matched above it.

Never omit the final catch-all definition, as this will result in a compiler error or a program that could possibly crash.

Pattern matching is also very useful for destructuring lists and algebraic data types. Many functions that take these types as parameters will use pattern matching to extract values out of the data structure. I will not go into this more here but will show an example:

data Shape = Circle Float
           | Rectangle Float Float           

shapeArea :: Shape -> Float
shapeArea (Circle r)      = 3.1415 * r * r
shapeArea (Rectangle h w) = h * w

Guards

Guards allow different function bodies based on any true or false condition. Pattern matching is limited to literal data structures, and guards can be used with any expression of type Boolean. Here is the same example from above:

hello name
  | name == "Alice" = "Hi Alice!"
  | name == "Bob    = "Hey Bob"
  | otherwise       = "Hello, " ++ name

otherwise is just a synonym for true, you should always have a catch-all guard to ensure all code paths are defined.

Operators

Operators like ++, <$>, <*>, or * are just functions that default to infix notation. Alphanumeric functions use prefix notation. We will not go deep into operator precedence and defining operators here, but you will want to recognize when an operator is being used.

Operators are usually written in infix notation, where the first argument comes before the function name:

2 + 3

Parentheses allow you to use an operator in prefix notation like a regular function:

(+) 2 3

The operator with parentheses is also the official name of the function, if you want to use it as a value:

addNumbers = (+)

Regular functions use prefix notation by default but can used in an infix position by using ticks. The following are equivalent:

addNumbers 2 3
2 `addNumbers` 3

Conclusion

This has been a quick rundown of function forms in Haskell and PureScript. Hopefully any code examples you see will now be a little more clear! We have only scratched the surface of some very useful features, and you now have what you need to dig into these topics on your own.