Given a function, for example a function
f of type
Int -> Bool, the effect of calling the function on an
Int must be nothing other than returning a
f cannot read a file, write to command line, or start a thread, as a side effect of being called.
Of course, it is possible to do all these things, but requires a change to the type:
- Fewer brackets are fine (
add1AndPrint x = print x >> return (x > 1)), and are here just for clarity.
This applies not just to functions, but all values:
booleancannot have type
Bool- because its definition calls
The benefit of purity¶
Because of purity, a function will give the same answer no matter where or when it is called, as long as the input is the same. This makes parallelism easy, often almost trivial:
Purity also lends itself to modular code, and easy refactoring. Consider:
Suppose we want to replace
simpleFunction, which also has the type
UserInput -> Picture.
Because Haskell is pure (but see caveats) and so
complexFunction is not creating/mutating global variables, or opening or closing files, we can be confident that there will be no unexpected implications of making the change, such as a subtle error with changed variable assignment, when
complexFunction as input.
Because of purity, you may always replace a function call in Haskell with its resulting value. For instance, if the value of
positionOfWhiteKing chessboard is
"a4", then this
is equivalent to
Use this fact to understand complex programs, by substituting complex expressions for their values:
To work out what this does, we consult the definition of
take (shown here with some aesthetic simplifications for clarity):
Following this definition, we replace
take 2 [1,2,3] (or more explicitly,
#1hs take 2 (1 : [2,3])) with the pattern that it matches:
We can continue in this vein, repeatedly consulting the definition of
This technique is always applicable, no mater how complex the program.
A function is total if it returns a result for any possible input. For example,
head is not total:
In Haskell, non-total (aka partial) functions are permitted, although they are discouraged. Functions may be partial by throwing an runtime error on some inputs (like
head), or by running indefinitely, (like
last [1..]). Haskell will generally warn you about the first kind, but not the second, since it is harder to detect.
Haskell allows a backdoor, mainly useful for debugging.
This is the ability for functions to throw an "unsafe" error:
undefined has the type
forall a. a, so it can appear anywhere in a program and assume the correct type (see here for more details on how universal quantification works).
As such, it is useful as a "to do" marker (see type driven development).
Created: January 11, 2023