First-class Pattern Matching in the Final Approach¶
Core Ideas¶
In today’s FP seminar, I refined the idea in my mind, which is directly responsible for
- another Pattern-matching in the final approach not noted at http://okmij.org/ftp/tagless-final/#course-oxford
- first-class pattern matching(for lazy programming language only..)
The core idea is,
avoid using type classes to express tagless final, instead, follow OCaml’s implementations(
SYM,SYMShow,sym(by higher rank types),SYMSelfmentioned in http://okmij.org/ftp/tagless-final/)code shall look this way:
match (succ zero) $ defaultMatcher { zero_m = error "non-exhausted" , succ_m = \_ -> "it's succ case!" } -- => "it's succ case!"
Full implementation in Haskell¶
(p.s: initially I use PureScript, Row types are nice)
{-# LANGUAGE RankNTypes #-}
module Main where
-- Signature
data SYM repr
= SYM
{ add_m :: repr -> repr -> repr
, neg_m :: repr -> repr
, lit_m :: Bool -> repr
}
-- above definition is similar to the following one
-- class SYM repr where
-- add :: repr -> repr -> repr
-- neg :: repr -> repr
-- lit :: Bool -> repr
--
-- however, encoding signatures with data types, makes it possible for us
-- to achieve more first-class things
type Self = forall repr. SYM repr -> repr
lit :: Bool -> Self
lit bool mod = lit_m mod bool
neg :: Self -> Self
neg term mod = neg_m mod (term mod)
add :: Self -> Self -> Self
add t1 t2 mod = add_m mod (t1 mod) (t2 mod)
-- module 1: self algebra
-- however, not compiled...
-- self :: SYM Self
-- self = SYM
-- { lit_m = lit
-- , neg_m = neg
-- , add_m = add
-- }
-- module 2: string algebra
symShow :: SYM String
symShow = SYM
{ add_m = \l r -> "(" ++ l ++ " + " ++ r ++ ")"
, neg_m = ("-" ++)
, lit_m = show
}
-- module 3: evaluator
symEval :: SYM Bool
symEval = SYM
{ add_m = (&&)
, neg_m = not
, lit_m = id
}
data Init = Add Init Init | Lit Bool | Neg Init
deriving (Show)
-- module 4: to ADT
symInit :: SYM Init
symInit = SYM
{ add_m = Add
, neg_m = Neg
, lit_m = Lit
}
-- pattern matching operation for elements in self algebras
matchTerm :: Self -> Self
matchTerm term matcher = term matcher
nonExhausted = error
-- a default pattern matching cases
-- look, this is first class pattern matching!
defaultMatcher :: forall repr. SYM repr
defaultMatcher = SYM
{ lit_m = nonExhausted "literal"
, add_m = nonExhausted "addition"
, neg_m = nonExhausted "negation"
}
main :: IO ()
main = do
putStrLn $ term1 symShow -- => True
print $ term2 symEval -- => False
print $ term2 symInit -- => Add (Lit False) (Lit True)
putStrLn $ matchTerm term1 $ defaultMatcher
{ lit_m = const "it's a literal!"}
-- => it's a literal!
putStrLn $ matchTerm term2 $ defaultMatcher
{ add_m = \_ _ -> "it's an addition!"}
-- => it's an addition!
where
term1 = lit True
term2 = lit False `add` lit True
Q: Think, why this works for LAZY language only?
A: Because initial algebra of tagless final implies pre-order visit.
Hence, currently, it works only for Haskell, but not for PureScript, even type checked for the latter.
Enhancement 1¶
To avoid impredicative polymorphisms and support GADTs:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PartialTypeSignatures #-}
module Main where
data Exp a where
IfExp :: Exp Bool -> Exp a -> Exp a -> Exp a
Lam :: (a -> b) -> Exp (a -> b)
App :: Exp (a -> b) -> Exp a -> Exp b
Val :: a -> Exp a
-- Signature
data SYM repr =
SYM
{ ifExp_m :: repr Bool -> forall a. repr a -> repr a -> repr a
, lam_m :: forall a b. (a -> b) -> repr (a -> b)
, app_m :: forall a b. repr (a -> b) -> repr a -> repr b
, val_m :: forall a. a -> repr a
}
newtype Self a = Self (forall repr. SYM repr -> repr a)
ifExp :: Self Bool -> forall a. Self a -> Self a -> Self a
ifExp (Self bool) (Self t) (Self f) = Self $ \mod -> ifExp_m mod (bool mod) (t mod) (f mod)
lam :: (a -> b) -> Self (a -> b)
lam f = Self $ \mod -> lam_m mod f
app :: Self (a -> b) -> Self a -> Self b
app (Self f) (Self arg) = Self $ \mod -> app_m mod (f mod) (arg mod)
val :: a -> Self a
val v = Self $ \mod -> val_m mod v
-- module 1: self algebra
self :: SYM Self
self = SYM
{ ifExp_m = ifExp
, lam_m = lam
, app_m = app
, val_m = val
}
-- module 4: to ADT
symInit :: SYM Exp
symInit = SYM
{ ifExp_m = IfExp
, lam_m = Lam
, val_m = Val
, app_m = App
}
-- pattern matching operation for elements in self algebras
runTerm :: Self a -> forall repr. SYM repr -> repr a
runTerm (Self term) matcher = term matcher
matchTerm = runTerm
nonExhausted = error
-- a default pattern matching cases
-- look, this is first class pattern matching!
defaultMatcher :: forall repr. SYM repr
defaultMatcher = SYM
{ lam_m = nonExhausted "lambda"
, app_m = nonExhausted "application"
, val_m = nonExhausted "value"
, ifExp_m = nonExhausted "if expression"
}
evalInit :: Exp a -> a
evalInit = \case
Lam f -> f
App f a -> evalInit f (evalInit a)
IfExp cond t f -> evalInit (if evalInit cond then t else f)
Val v -> v
newtype C a = C a deriving (Show)
evalFinal :: SYM C
evalFinal = SYM
{ ifExp_m = \(C cond) e f -> if cond then e else f
, app_m = \(C f) (C arg) -> C (f arg)
, val_m = C
, lam_m = C
}
main :: IO ()
main = do
let result :: forall repr. SYM repr -> repr _
result = runTerm term1
print $ evalInit (result symInit)
print $ result evalFinal
where
term1 = app (lam $ \x -> x + 1) (val 1)
-- =>
-- 2
-- C 2