functor-friends-0.1.0.0: src/Data/Functor/Tutorial.hs
{-# LANGUAGE LambdaCase, PatternSynonyms, DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module Data.Functor.Tutorial where
import Data.Functor.Holey
import Data.Functor.Annotated
import Data.Functor.Decomposed
import Data.Functor.Foldable ( Fix(..), fold, cata )
-- | An example AST type, as an F-algebra. This AST will be used
-- to demonstrate some tricks for composing new data types
-- while maintaining separation of concerns.
--
-- This approach was inspired by the @recursion-schemes@ library
-- and the "Data Types a la Carte" paper.
data AstF t
= RealF Double
| ComplexF Double Double
| VarF String
| AddF t t
| MulF t t
| AbsF t
deriving (Eq, Ord, Functor)
type Ast = Fix AstF
-- | Patterns to simplify the construction and destructuring of ASTs
pattern Add x y = Fix (AddF x y)
pattern Mul x y = Fix (MulF x y)
pattern Abs x = Fix (AbsF x)
pattern Real x = Fix (RealF x)
pattern Complex x y = Fix (ComplexF x y)
pattern Var x = Fix (VarF x)
-- | A pretty-printing typeclass.
--
-- This demonstrates an approach to separation of concerns; the
-- pretty-printer for normal ASTs is implemented independently from
-- the pretty-printer for holes and the pretty-printer for annotations.
--
-- Although the implementations don't know about each other, they can
-- still be composed; as a result, a @Holey Ast@ knows how to print itself
-- using a mixture of the pretty-printer for @Ast@s and the pretty-printer
-- for @Hole@s.
class Pretty a where
pretty :: a -> String
-- | Pretty-printer for fixpoints.
instance Pretty (f (Fix f)) => Pretty (Fix f) where
pretty (Fix x) = pretty x
-- | Pretty-printer for ASTs.
instance Pretty t => Pretty (AstF t) where
pretty = \case
RealF d -> show d
ComplexF re im -> paren (show re ++ " + " ++ show im ++ "i")
AddF x y -> paren (pretty x ++ " + " ++ pretty y)
MulF x y -> paren (pretty x ++ " * " ++ pretty y)
AbsF x -> "|" ++ pretty x ++ "|"
VarF v -> v
where
paren x = "(" ++ x ++ ")"
-- | Pretty-printer for holes.
instance Pretty (f (Fix (HoleF f))) => Pretty (HoleF f (Fix (HoleF f))) where
pretty HoleF = "_"
pretty (ExistingF x) = pretty x
-- | Pretty-printer for annotations.
instance (Pretty (f (Fix (AnnotatedF a f))), Show a) => Pretty (AnnotatedF a f (Fix (AnnotatedF a f))) where
pretty (NoteF ann x) = pretty x ++ "{" ++ show ann ++ "}"
-- | A simple expression
expr1 :: Ast
expr1 = Abs (Add (Mul (Real 2) (Complex 0 1)) (Real 1))
-- | Another simple expression
expr2 :: Ast
expr2 = Add (Mul (Var "z") (Var "z")) (Mul (Real 3) (Var "z"))
-- | An expression-with-holes. Holes can be plugged with 'plug'.
--
-- λ> pretty expr3
-- "|(_ + 1.0)|"
--
-- λ> pretty (plug (Var "z") expr3)
--"|(z + 1.0)|"
--
-- λ> :t plug (Var "z") expr3
-- plug (Var "z") expr3 :: Fix AstF -- aka Ast
expr3 :: Holey Ast
expr3 = Existing (AbsF (Existing (AddF Hole (Existing (RealF 1)))))
-- | Real and Complex type tags
data Type = R | C deriving (Eq, Show)
-- | Annotate each subexpression with its type (real or complex).
--
-- λ> pretty expr1
-- "|((2.0 * (0.0 + 1.0i)) + 1.0)|"
--
-- λ> pretty (inferTypes expr1)
-- "|((2.0{R} * (0.0 + 1.0i){C}){C} + 1.0{R}){C}|{R}"
inferTypes :: Ast -> Annotated Type Ast
inferTypes = foldMemo typeOf
where
typeOf = \case
AddF R R -> R
MulF R R -> R
AbsF _ -> R
RealF _ -> R
_ -> C
-- | Remove all real-valued subexpressions, leaving a 'Hole'
-- in their place.
--
-- λ> pretty expr2
-- "((z * z) + (3.0 * z))"
--
-- λ> pretty (unreal expr2)
-- "((z * z) + (_ * z))"
unreal :: Ast -> Holey Ast
unreal = eraseTypes . punch (\x -> note x == R) . inferTypes
where
eraseTypes = natmap (fmap1 erase_)