Defining functions in these languages is quite concise, and there are a few different forms that you’ll want to be able to recognize.
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:
The result of this expression is
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.
greeting are now just two names referring to the same function value.
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.
Sometimes your function will have different behavior based on the value of your arguments. In other languages, you would use constructs like
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 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.
* 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
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.