haskell functions

EventHelix
functional
Published in
6 min readJan 20, 2018

Haskell is a pure functional language. This means functions in Haskell behave closer to mathematical functions. A function operates on the input parameters and returns a result. Functions do not modify state.

Here we will be introducing Haskell functions by examples presented in the following code snippet.

simple functions and type inference

Let’s start with simple functions that accept a single input value and output a single result.

half

half x = x / 2

This function takes a single value x and transforms it into a value that is half of the input. Note that we did not specify any types here. Haskell infers the types. A :t command for the function reveals the following type signature.

*HaskellFunctions> :t half
half :: Fractional a => a -> a

Key points to note here are:

  • half is a function that accepts an input of type a and outputs a value of type a
  • Haskell has inferred the type a is constrained to the Fractional type class.
  • Since we did not explicitly specify a type for a, Haskell inferred the most general type that will support the function’s requirements.
  • A :info command on Fractional reveals that it mandates support for (/) operator function. This selection was based on the use of the / in the half function.
*HaskellFunctions> :info Fractional
class Num a => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> a
{-# MINIMAL fromRational, (recip | (/)) #-}
-- Defined in `GHC.Real'
instance Fractional Float -- Defined in `GHC.Float'
instance Fractional Double -- Defined in `GHC.Float'

square

square x = x * x

square uses the * operation. This results in an inferred type Num.

square :: Num a => a -> a

:i on Num shows us that Num requires the support of *.

*HaskellFunctions> :i Num
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
-- Defined in `GHC.Num'
instance Num Word -- Defined in `GHC.Num'
instance Num Integer -- Defined in `GHC.Num'
instance Num Int -- Defined in `GHC.Num'
instance Num Float -- Defined in `GHC.Float'
instance Num Double -- Defined in `GHC.Float'

areaCircle

areaCircle radius = pi * square radius

The areaCircle function invokes the square function we had just defined.

*HaskellFunctions> :t areaCircle
areaCircle :: Floating a => a -> a

Note that the inferred type class is now less general than square as the areaCircle uses the pi constant defined in Floating type class.

*HaskellFunctions> :t pi
pi :: Floating a => a

:i on Floating shows that it extends the Fractional type class. So, some generality has been lost here.

*HaskellFunctions> :i Floating
class Fractional a => Floating a where
pi :: a
exp :: a -> a
log :: a -> a
sqrt :: a -> a
(**) :: a -> a -> a
logBase :: a -> a -> a
sin :: a -> a
cos :: a -> a
tan :: a -> a
asin :: a -> a
acos :: a -> a
atan :: a -> a
sinh :: a -> a
cosh :: a -> a
tanh :: a -> a
asinh :: a -> a
acosh :: a -> a
atanh :: a -> a
GHC.Float.log1p :: a -> a
GHC.Float.expm1 :: a -> a
GHC.Float.log1pexp :: a -> a
GHC.Float.log1mexp :: a -> a
{-# MINIMAL pi, exp, log, sin, cos, asin, acos, atan, sinh, cosh,
asinh, acosh, atanh #-}
-- Defined in `GHC.Float'
instance Floating Float -- Defined in `GHC.Float'
instance Floating Double -- Defined in `GHC.Float'

An important take away from the discussion above is that:

Haskell function definitions without explicit type specifications are as general as they possibly can be. Keeping functions as general as possible, promotes code reuse.

multi-parameter functions and currying

We have seen single parameter functions. How does Haskell support multi-parameter functions?

In Haskell all functions take only one argument. Multi-argument functions are formed by cascading multiple function applications. This is best explained with an example.

volumeCylinder

volumeCylinder r h = areaCircle r * h

The above function appears to take two arguments, radius (r) and height (h).

*HaskellFunctions> :t volumeCylinder
volumeCylinder :: Floating a => a -> a -> a

The signature of the function is shown above. The first two arrows represent the radius and height parameters. The third arrow represents the return value. This is further clarified below.

volumeCylinder :: Floating a 
=> a -- ^ radius
-> a -- ^ height
-> a -- ^ volume

It appears that the volumeCylinder function takes two parameters. Haskell however treats the above declaration as a cascade of two function applications:

  • volumeCylinder function takes radius (r) and returns a second function.
  • The second function takes the height as a parameter and returns the volume of the cylinder.

Strictly speaking, the volumeCylinder signature should be written as shown below.

volumeCylinder :: Floating a => a -> (a -> a)

Haskell implements functions this way to facilitate partial application. Partial application is explained by the gauge10Volume function that is formed by partially applying the parameters to the volumeCylinder function.

guage10Volume

gauge10Volume :: Floating a => a -> a
gauge10Volume = volumeCylinder (2.58826 / 2)

Consider the above function that determines the volume of a gauge 10 cable of given length. The function’s body just consists of partial application of the volumeCylinder function with just the radius parameter. The gauge10Volume just takes the height parameter (length of the cable) and will return the volume of a gauge 10 cable.

operations

Operations like +, * are also functions. The operations can be invoked in the conventional infix operation a+b or as a function (+) a b.

add1 = (+) 10 2            -- Add as a function
add2 = 10 + 2 -- Infix add
mul1 = (*) 10 2 -- Multiply as a function
mul2 = 10 * 2 -- Infix multiply
floatingDiv = 10 / 4 -- Haskell division returns Double
intDiv1 = div 10 4 -- div performs an integer division
intDiv2 = 10 `div` 4 -- infix version of div

You can learn more about the functions by issuing in the :i command in GHCI. You can see the function signatures as well as operator precedence.

*HaskellFunctions> :i (+) (-) (*) (/)
class Num a where
(+) :: a -> a -> a
...
-- Defined in `GHC.Num'
infixl 6 +
class Num a where
...
(-) :: a -> a -> a
...
-- Defined in `GHC.Num'
infixl 6 -
class Num a where
...
(*) :: a -> a -> a
...
-- Defined in `GHC.Num'
infixl 7 *

class Num a => Fractional a where
(/) :: a -> a -> a
...
-- Defined in `GHC.Real'
infixl 7 /

recursive function defined using equations

Haskell supports parameter pattern matching in functions. You can specify multiple declarations of the same function with different parameter patterns. For example, the factorial function is shown below with two declarations:

  • factorial 0 specifies the factorial for the number 0
  • factorial n defines the factorial for all integers other than 0.
factorial :: Integral a => a -> a
factorial 0 = 1
factorial n = n * factorial (n-1)

Also note the definition of factorial n is recursively defined in terms of factorial (n-1). Recursion is a very common pattern in functional programming. We will see it being used for function definitions, data structure definition and loops.

--

--