|
|
Line 1: |
Line 1: |
− | If you are new to Haskell you may think that type variables and [[polymorphism]] are fancy things that are rarely used.
| |
− | Maybe you are surprised that type variables and [[type class]] constraints can increase safety and readability
| |
− | also if you are eventually using only one concrete type.
| |
| | | |
− | Imagine the [[Prelude]] would contain the following functions:
| |
− | <haskell>
| |
− | maximum :: [Integer] -> Integer
| |
− | sum :: [Integer] -> Integer
| |
− | </haskell>
| |
− | Sure, the names are carefully chosen and thus you can guess what they do.
| |
− | But the signature is not as expressive as it could be.
| |
− | Indeed these functions are in the Prelude but with a more general signature.
| |
− | <haskell>
| |
− | maximum :: (Ord a) => [a] -> a
| |
− | sum :: (Num a) => [a] -> a
| |
− | </haskell>
| |
− | These functions can also be used for <hask>Integer</hask>s,
| |
− | but the signatures show which aspects of integers are actually required.
| |
− | We realize that <hask>maximum</hask> is not about numbers, but can also be used for other ordered types, like <hask>Char</hask>.
| |
− | We can also conclude that <hask>maximum []</hask> is undefined,
| |
− | since the <hask>Ord</hask> class has no function to construct certain values and the input list does not contain an element of the required type.
| |
− |
| |
− |
| |
− | Now consider that you have a complex function that is hard to understand.
| |
− | It is fixed to a concrete type, say <hask>Double</hask>.
| |
− | You want to divide that function into a function that does the processing of the structure
| |
− | and another function which does the calculations with <hask>Double</hask>.
| |
− | This is good style in the sense of the "[[Separation of concerns]]" idiom.
| |
− | You do it, because you want to untangle the [[Things_to_avoid#Avoid_explicit_recursion|explicit recursion]],
| |
− | which is hard to understand of its own,
| |
− | and the calculation, which also has pitfalls.
| |
− | The structure processing does not know about the <hask>Double</hask>
| |
− | and it is wise to use type variables instead of <hask>Double</hask>
| |
− |
| |
− | Making the example more concrete, look at a state monad transformer <hask>StateT</hask>
| |
− | which shall be nested by a nesting depth that is only known at runtime.
| |
− | Ok that's not possible, so just consider a <hask>State</hask> [[applicative functor]], that shall be nested the same way.
| |
− | The functor depends on an input of the same type as its functor output,
| |
− | that is we nest functions of type <hask>(Double -> State Double Double)</hask>.
| |
− | The nested functor has a list of state values as state value.
| |
− | The nesting depth depends on the length of the list of state values.
| |
− | (This design also forbids transformer techniques for general applicative functors.)
| |
− | We could write a nesting function fixed to type <hask>Double</hask>
| |
− | <haskell>
| |
− | stackStates :: (Double -> State Double Double) -> (Double -> State [Double] Double)
| |
− | </haskell>
| |
− | but it is too easy to mix up state and return value here, because they have the same type.
| |
− | You should really separate that
| |
− | <haskell>
| |
− | stackStates :: (a -> State s a) -> (a -> State [s] a)
| |
− | stackStates m = State . List.mapAccumL (runState . m)
| |
− | </haskell>
| |
− | also if you only use it for <hask>Double</hask>.
| |
− | This way the type checker asserts, that you never mix up the state with the other type.
| |
− |
| |
− |
| |
− | [[Category:Style]]
| |
− | [[Category:Idioms]]
| |