haskell functions
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 typea
and outputs a value of typea
- Haskell has inferred the type
a
is constrained to theFractional
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 onFractional
reveals that it mandates support for(/)
operator function. This selection was based on the use of the/
in thehalf
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 addmul1 = (*) 10 2 -- Multiply as a function
mul2 = 10 * 2 -- Infix multiplyfloatingDiv = 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 0factorial 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.