Difference between revisions of "Type variables instead of concrete types"
Jump to navigation
Jump to search
(stackStates) |
Tomjaguarpaw (talk | contribs) (Deleting page that hasn't been edited for over 10 years) |
||
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]] |