I'm following this blog post: http://semantic-domain.blogspot.com/2012/12/total-functional-programming-in-partial.html
It shows a small OCaml compiler program for System T (a simple total functional language).
The entire pipeline takes OCaml syntax (via Camlp4 metaprogramming) transforms them to OCaml AST that is translated to SystemT Lambda Calculus (see: module Term
) and then finally SystemT Combinator Calculus (see:
module Goedel
). The final step is also wrapped with OCaml metaprogramming Ast.expr
type.
I'm attempting to translate it to Haskell without the use of Template Haskell.
For the SystemT Combinator form, I've written it as
{-# LANGUAGE GADTs #-}
data TNat = Zero | Succ TNat
data THom a b where
Id :: THom a a
Unit :: THom a ()
ZeroH :: THom () TNat
SuccH :: THom TNat TNat
Compose :: THom a b -> THom b c -> THom a c
Pair :: THom a b -> THom a c -> THom a (b, c)
Fst :: THom (a, b) a
Snd :: THom (a, b) b
Curry :: THom (a, b) c -> THom a (b -> c)
Eval :: THom ((a -> b), a) b -- (A = B) * A -> B
Iter :: THom a b -> THom (a, b) b -> THom (a, TNat) b
Note that Compose
is forward composition, which differs from (.)
.
During the translation of SystemT Lambda Calculus to SystemT Combinator Calculus, the Elaborate.synth
function tries to convert SystemT Lambda calculus variables into series of composed projection expressions (related to De Brujin Indices). This is because combinator calculus doesn't have variables/variable names. This is done with the Elaborate.lookup
which uses the Quote.find
function.
The problem is that with my encoding of the combinator calculus as the GADT THom a b
. Translating the Quote.find
function:
let rec find x = function
| [] -> raise Not_found
| (x', t) :: ctx' when x = x' -> <:expr< Goedel.snd >>
| (x', t) :: ctx' -> <:expr< Goedel.compose Goedel.fst $find x ctx'$ >>
Into Haskell:
find :: TVar -> Context -> _
find tvar [] = error "Not Found"
find tvar ((tvar', ttype):ctx)
| tvar == tvar' = Snd
| otherwise = Compose Fst (find tvar ctx)
Results in an infinite type error.
• Occurs check: cannot construct the infinite type: a ~ (a, c)
Expected type: THom (a, c) c
Actual type: THom ((a, c), c) c
The problem stems from the fact that using Compose
and Fst
and Snd
from the THom a b
GADT can result in infinite variations of the type signature.
The article doesn't have this problem because it appears to use the Ast.expr
OCaml thing to wrap the underlying expressions.
I'm not sure how to resolve in Haskell. Should I be using an existentially quantified type like
data TExpr = forall a b. TExpr (THom a b)
Or some sort of type-level Fix
to adapt the infinite type problem. However I'm unsure how this changes the find
or lookup
functions.
tvar == tvar' = Snd
indicates that find has typefind :: TVar -> Context -> THom (a, b) b
, hence(find tvar ctx)
has same type. so the type of arguments ofCompose Fst (find tvar ctx)
ascompose (Thom (a, b) a) (Thom (a, b) b)
, Note that in the definition of compose need:a == (a, b)
, but it is impossible. – Torofind :: TVar -> Context -> ???
. The easy but inelegant solution is to avoid GADTs forTHom
(and introduce partiality everywhere around). Another solution is to try going fully dependent, introducing more indices: something likefind :: TVar v -> Context c -> THom (F v c)
with some type familyF
. In Agda/Coq I'm confident we could write this, given enough time. In Haskell, we probably need more "proof" parameters, singleton types, assuming it's doable at all (I'm not entirely confident). – Cruciatefind
function? – Toro(((),x),y)
and to accessx
you want to use something likefst;snd
AKA "discard the last variable, take the next". In general, in longer contexts you generatefst;fst;....;fst;snd
to discard many variables and take the one you want at the end. The whole point of doing this is so that we expose more information in the type, unlike e.g. using a plain list. This approach however requires a lot of dependent functions. – CruciateTHom
s. You should make all the recursive occurrences strict. This neither worsens nor improves the problem you're having, just makes the translation closer. (At least, I think adding strictness solves the problem. The author says that laziness causes partiality, but doesn't offer a solution). – Velamenlet bad = Compose bad SuccH in bad
should be disallowed. I think that liberal application of!
annotations should eliminate things like that, such that iffun :: THom a b
, then eitherfun = _|_
orfun
is a real, complete, total, 100% functional, no partiality of any kind, anywhere representation of a function froma
tob
. – Velamen