Type variables instead of concrete types
From HaskellWiki
(introduction) |
(stackStates) |
||
| Line 22: | Line 22: | ||
| - | + | Now consider that you have a complex function that is hard to understand. | |
| - | + | It is fixed to a concrete type, say <hask>Double</hask>. | |
| - | [[Things_to_avoid#Avoid_explicit_recursion]] | + | 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:Style]] | ||
[[Category:Idioms]] | [[Category:Idioms]] | ||
Current revision
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:
maximum :: [Integer] -> Integer sum :: [Integer] -> Integer
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.
maximum :: (Ord a) => [a] -> a sum :: (Num a) => [a] -> a
but the signatures show which aspects of integers are actually required.
We realize that
Now consider that you have a complex function that is hard to understand.
You want to divide that function into a function that does the processing of the structure
and another function which does the calculations withThis is good style in the sense of the "Separation of concerns" idiom. You do it, because you want to untangle the explicit recursion, which is hard to understand of its own, and the calculation, which also has pitfalls.
The structure processing does not know about thewhich shall be nested by a nesting depth that is only known at runtime.
Ok that's not possible, so just consider aThe functor depends on an input of the same type as its functor output,
that is we nest functions of typeThe 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 typestackStates :: (Double -> State Double Double) -> (Double -> State [Double] Double)
but it is too easy to mix up state and return value here, because they have the same type. You should really separate that
stackStates :: (a -> State s a) -> (a -> State [s] a) stackStates m = State . List.mapAccumL (runState . m)
This way the type checker asserts, that you never mix up the state with the other type.
