packages feed

fastsum-0.1.0.0: examples/Main.hs

{-# LANGUAGE DataKinds, DeriveFunctor, FlexibleContexts, KindSignatures, RankNTypes, TypeApplications, TypeOperators, UndecidableInstances #-}

module Main where

import Data.Monoid hiding (Sum(..))
import Data.Sum

-- okay, let's use Data.Sum to solve the expression problem
-- we'll build a little expression language, define an F-algebra, and print it out
-- you don't _have_ to use recursion schemes with Data.Sum, but they sure are nice

-- standard fixed point of a Functor. in the real world we would use an actual
-- recursion schemes library, but who has time for that?
newtype Fix f = In { out :: f (Fix f) }

-- here's our expression type - note that l is a type-level list of functors
type Expr (l :: [* -> *]) = Fix (Sum l)

-- numbers
newtype Lit a = Lit Int deriving Functor

-- smart constructor. the :< is pronounced "member":
-- what this says is that as long as Lit is a member of the type-level
-- list 'fs', we can inj it into an Expr that contains 'fs'
-- if we tried to inj it into an 'Expr [Thing1, Thing2]',
-- we would get an error message that Lit cannot be found in [Thing1, Thing2]
lit :: (Lit :< fs) => Int -> Expr fs
lit = In . inject . Lit

-- parens
newtype Paren a = Paren a
  deriving Functor

paren :: (Paren :< fs) => Expr fs -> Expr fs
paren = In . inject . Paren

-- math
data Op a
  = Add a a
  | Sub a a
  | Mul a a
    deriving Functor

(+:), (-:), (*:) :: (Op :< fs) => Expr fs -> Expr fs -> Expr fs
a +: b = In (inject (Add a b))
a -: b = In (inject (Sub a b))
a *: b = In (inject (Mul a b))

infixl 6 +:
infixl 6 -:
infixl 7 *:

-- here's our F-algebra that converts a sum type to a string
class Functor f => Pretty f where
  pretty :: f String -> String

instance Pretty Lit where
  pretty (Lit i) = show i

instance Pretty Paren where
  pretty (Paren a) = "(" <> a <> ")"

instance Pretty Op where
  pretty (Add a b) = concat [a, " + ", b]
  pretty (Sub a b) = concat [a, " - ", b]
  pretty (Mul a b) = concat [a, " * ", b]

-- this tells the compiler that any Sum type whose components
-- all implement Functor and Pretty supports pretty-printing too
instance (Apply Functor fs, Apply Pretty fs) => Pretty (Sum fs) where
  pretty = apply @Pretty pretty

-- a neutered catamorphism
runPretty :: Pretty f => Fix f -> String
runPretty = pretty . fmap runPretty . out

example :: Expr '[Lit, Op, Paren]
example = paren (lit 5 +: lit 10) *: lit 2

main :: IO ()
main = putStrLn (runPretty example)

-- now, if you so desired, you could add a new data type:
-- > data Div a = Div a a
-- declare a Pretty instance for it, and then create a new value of type
-- > Expr '[Lit, Op, Paren, Div]
-- and you get a perfect solution to the expression problem: seamless extension of functionality and of data-types