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),SYMSelf
mentioned 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