class ExpSYM a where
add :: a Int → a Int → a Int
lit :: Int → a Int
class MulSYM a where
mul :: a Int → a Int → a Int
Now suppose we extend with booleans and so on. We can freely combine addition and multiplication. As we add new data variants, old code need not be recompiled. We get the openness of OOP's class extensions with the flexibility of ADTs. ex1 :: (ExpSYM a, MulSYM a) => a Int
ex1 = add (lit 3) (mul (lit 4) (lit 5))
Not enough space here but you can also pattern match over final terms and interpret them in different ways (e.g. CBN vs CBV interpreters).Then as you go through your compiler pass you can see through the types that "capabilities" get reduced more and more until you reach some base language (sounds like free monads doesn't it?).
Tangentially if I were to design an FP language I would use tagless final for everything, and deforest as much as I can (if possible) to reduce overhead. How this affects other aspects (e.g. automatically translating pattern matching) I have not looked into, but it's interesting nonetheless.
getDates
:: Exp
-> Set Date
getDates = cata alg where
alg :: ExpF (Set Date) -> Set Date
alg (EDate i) = S.singleton i
alg e = fold e
substitute
:: Map VarId (ExpF Exp)
-> Exp
-> Exp
substitute env = cata alg where
alg :: ExpF Exp -> Exp
alg (EVar i)
| Just e <-M.lookup i env = Fix e
alg e = Fix eAs for matching remaining terms, I don't know the answer to that, would be interesting to investigate.
Yeah, having many typeclasses involved could be problematic. What sort of issues are you thinking of? One possibility is that so many constraints are involved no concrete type can instantiate a final term.
No this is not correct. Oleg provides a solution to double negation by creating a catamorphism that folds to a function. It's still a catamorphism. And folding to a function is not something the average Java programmer is likely to understand easily.