Function decoration pattern
From HaskellWiki
Contents |
1 Motivation
You want to add extra properties to a function type, but you don't want the users to have to tediously project out the decorated type when they don't care about the decorations.
This can be generalized to arbitrary values instead of just functions.
2 Approach
Use type classes to drive the projection by the how the value is used.
{-# LANGUAGE MultiParamTypeClasses, Rank2Types, FlexibleContexts, FlexibleInstances #-} -- This implementation is somewhat general, but it is not intended -- that all examples can be cast in exactly this way. data Decorate d a b = Decorated (a -> b) (d a b) class Decorated d a b dec where decorated :: (a -> b) -> d a b -> dec a b -- The above is a Scott-encoding of the below which is equivalent. -- The Scott-encoded version is often more convenient and efficient.` -- decorated :: Decorate d a b -> dec a b instance Decorated d a b (Decorate d) where decorated = Decorated instance Decorated d a b (->) where decorated f _ = f type IsDecorated d a b = forall dec. Decorated d a b dec => dec a b -- Not a very realistic example. type UnitTested = Decorate (,) type IsUnitTested a b = IsDecorated (,) a b makeTested :: (a -> b) -> a -> b -> IsUnitTested a b makeTested f a b = decorated f (a, b) test :: Eq b => UnitTested a b -> Bool test (Decorated f (a, b)) = f a == b testedSquare :: Num a => IsUnitTested a a testedSquare = makeTested (\x -> x * x) 3 9 main = do print (map testedSquare [1,2,3]) putStrLn (if test testedSquare then "Passed" else "Failed")
3 Examples
The archetypical example is the type of isomorphisms e.g. as used in the lens library.
An isomorphism is a function equipped with an inverse. Traditionally, this would be represented by a data type such as
data Iso a b = Iso { _to :: a -> b, _from :: b -> a }
_to
class Isomorphic a b iso where iso :: (a -> b) -> (b -> a) -> iso a b instance Isomorphic a b Iso where iso = Iso instance Isomorphic a b (->) where iso to _ = to type IsIsomorphic a b = forall iso. Isomorphic a b iso => iso a b
IsIsomorphic a b
Iso a b
from :: Iso a b -> b -> a
op :: Iso a b -> Iso b a
from :: Iso a b -> IsIsomorphic b a
Iso
Another example would be allowing arrays to used as functions but still being able to get at the bounds when you needed them.
4 Notes
This is closely related to the Yoneda lemma and representability. Essentially we are identifying the valuex
($ x)
x
($ x) observation
makeTested
