I have a string sort function defined as below and want to prove a lemma
sort_str_list_same below. I am not Coq expert, I tried to solve it using induction, but could not solve it. Please help me solving it. Thanks,
Require Import Ascii.
Require Import String.
Notation "x :: l" := (cons x l) (at level 60, right associativity).
Notation "[ ]" := nil.
Notation "[ x , .. , y ]" := (cons x .. (cons y nil) ..).
Fixpoint ble_nat (n m : nat) : bool :=
match n with
| O => true
| S n' =>
match m with
| O => false
| S m' => ble_nat n' m'
end
end.
Definition ascii_eqb (a a': ascii) : bool :=
if ascii_dec a a' then true else false.
(** true if s1 <= s2; s1 is before/same as s2 in alphabetical order *)
Fixpoint str_le_gt_dec (s1 s2 : String.string)
: bool :=
match s1, s2 with
| EmptyString, EmptyString => true
| String a b, String a' b' =>
if ascii_eqb a a' then str_le_gt_dec b b'
else if ble_nat (nat_of_ascii a) (nat_of_ascii a')
then true else false
| String a b, _ => false
| _, String a' b' => true
end.
Fixpoint aux (s: String.string) (ls: list String.string)
: list String.string :=
match ls with
| nil => s :: nil
| a :: l' => if str_le_gt_dec s a
then s :: a :: l'
else a :: (aux s l')
end.
Fixpoint sort (ls: list String.string) : list String.string :=
match ls with
| nil => nil
| a :: tl => aux a (sort tl)
end.
Notation "s1 +s+ s2" := (String.append s1 s2) (at level 60, right associativity) : string_scope.
Lemma sort_str_list_same: forall z1 z2 zm,
sort (z1 :: z2 :: zm) =
sort (z2 :: z1 :: zm).
Proof with o.
Admitted.
Your lemma is equivalent to forall z1 z2 zm, aux z1 (aux z2 zm) = aux z2 (aux z1 zm). Here's how you can prove a similar theorem, for an arbitrary type with an order relation. To use it in your case, you just have to prove the given hypothesis. Note that the Coq standard library defines some sorting functions and proves lemmas about them, so you may be able to solve your problem without having to prove too many things.
Require Import Coq.Lists.List.
Section sort.
Variable A : Type.
Variable comp : A -> A -> bool.
Hypothesis comp_trans :
forall a b c, comp a b = true ->
comp b c = true ->
comp a c = true.
Hypothesis comp_antisym :
forall a b, comp a b = true ->
comp b a = true ->
a = b.
Hypothesis comp_total :
forall a b, comp a b = true \/ comp b a = true.
Fixpoint insert (a : A) (l : list A) : list A :=
match l with
| nil => a :: nil
| a' :: l' => if comp a a' then a :: a' :: l'
else a' :: insert a l'
end.
Lemma l1 : forall a1 a2 l, insert a1 (insert a2 l) = insert a2 (insert a1 l).
Proof.
intros a1 a2 l.
induction l as [|a l IH]; simpl.
- destruct (comp a1 a2) eqn:E1.
+ destruct (comp a2 a1) eqn:E2; trivial.
rewrite (comp_antisym _ _ E1 E2). trivial.
+ destruct (comp a2 a1) eqn:E2; trivial.
destruct (comp_total a1 a2); congruence.
- destruct (comp a2 a) eqn:E1; simpl;
destruct (comp a1 a) eqn:E2; simpl;
destruct (comp a1 a2) eqn:E3; simpl;
destruct (comp a2 a1) eqn:E4; simpl;
try rewrite E1; trivial;
try solve [rewrite (comp_antisym _ _ E3 E4) in *; congruence];
try solve [destruct (comp_total a1 a2); congruence].
+ assert (H := comp_trans _ _ _ E3 E1). congruence.
+ assert (H := comp_trans _ _ _ E4 E2). congruence.
Qed.
Section sort.
Related
I'm trying to translate this piece of code in Haskell that describes List anamorphism but can't quite get it to work.
The final three lines are supposed to generate a function count which given an int will produce an int list [n, n-1, ..., 1]
Haskell code:
data Either a b = Left a | Right b
type List_coalg u x = u -> Either () (x, u)
list ana :: List_coalg u x -> u -> [x]
list_ana a = ana where
ana u = case a u of
Left _ -> []
Right (x, l) -> x : ana l
count = list_ana destruct_count
destruct_count 0 = Left ()
destruct_count n = Right (n, n-1)
What I have so far:
type ('a, 'b) List_coalg = 'a -> (unit, 'a*'b) Either
fun list_ana (f : ('a, 'b) List_coalg) : 'a -> 'b list =
let
fun ana a : 'b list =
case f a of
Left () => []
| Right (x, l) => x :: ana l
in
ana
end
fun destruct_count 0 = Left ()
| destruct_count n = Right (n, n-1)
val count = list_ana destruct_count
I get the following error:
catamorphism.sml:22.7-24.35 Error: case object and rules do not agree [UBOUND match]
rule domain: (unit,'b * 'a) Either
object: (unit,'a * 'b) Either
in expression:
(case (f a)
of Left () => nil
| Right (x,l) => x :: ana l)
Not sure how to fix this as I am not super proficient in SML.
As you mentioned in the comments, the type parameters got mixed up. With a bit of renaming for comparison:
type List_coalg a b = a -> Either () (b, a) -- (b, a)
type ('a, 'b) List_coalg = 'a -> (unit, 'a*'b) Either (* ('a * 'b) *)
which leads to a mismatch after pattern-matching on the pair:
Right (x, l) -> x : ana l
-- x :: b
-- l :: a
Right (x, l) => x :: ana l
(* x : 'a *)
(* l : 'b *)
I'm looking for a Haskell container type like Data.Map that uses intervals as keys, where the left-most and right-most keys may also be unbounded intervals, but are otherwise non-overlapping. Additionally, the container should support a function similar to zipWith that allows to merge two containers into a new one, using the intersection of both key sets as the new key set and the argument function for a pointwise combination of both value sets.
There already are several packages that provide interval-based maps. I've had a look at IntervalMap, fingertree and SegmentTree, but none of these packages seem to provide the desired combination function. They all seem to use intervals for the intersection functions, that are equal in both maps, while I need a version that breaks intervals down into smaller ones if necessary.
The container should basically provide an efficient and storable mapping for key/value series of the form Ord k => k -> Maybe a, i.e. functions only defined on specific intervals or having larger intervals mapping to the same value.
Here is a small example to demonstrate the issue:
... -4 -3 -2 -1 0 1 2 3 4 ... -- key set
-----------------------------------
... -1 -1 -1 -1 0 1 1 1 1 ... -- series corresponding to signum
... 5 5 5 5 5 5 5 5 5 ... -- series corresponding to const 5
The first series could be efficiently expressed by a mapping [-infinity, -1] -> -1; [0, 0] -> 0; [1, infinity] -> 1 and the second one by [-infinity, infinity] -> 5. Now applying a combination function with (*) as arument function should give a new series
... -4 -3 -2 -1 0 1 2 3 4 ... -- key set
-----------------------------------
... -5 -5 -5 -5 0 5 5 5 5 ... -- combined series
The crucial point here—and all of the afore-mentioned packages don't seem to be able to do that—is that, when combining the key sets for these two series, you have to take the different values also into account. Both series span the full range of [-infinity, infinity] but it's necessary to break it into three parts for the final series.
There are also packages for working with intervals, e.g. the range package, which also provides an intersection operation on lists of intervals. However, I didn't found a way to use that in combination with one of the Map variants because it collapses adjacents intervals when doing calculations with them.
NB: Such a container is somewhat similar to a ZipList that extends to both sides, which is why I think it should also be possible to define a lawful Applicative instance for it, where <*> corresponds to the above-mentioned combining function.
To cut a long story short, is there already a package that provides such a container? Or is there an easy way to use the existing packages to build one?
The best suggestion from the comments above seems to be the step-function package, as suggested by B. Mehta. I haven't tried that package yet, but it looks like building a wrapper around that SF type is what I was looking for.
Meanwhile, I implemented another solution which I'd like to share. The code for the combining function (combineAscListWith in the code below) is a bit clumsy as it's more general than for just getting the intersection of both maps, so I'll sketch the idea:
First we need an Interval type with an Ord instance which stores pairs of Val a values which can either be -infinity, some value x or +infinity. Form that we can build an IntervalMap which is just a normal Map that maps these intervals to the final values.
When combining two such IntervalMaps by intersection, we first convert the maps into lists of key/value pairs. Next we traverse both lists in parallel to zip both lists into another one which corresponds to the final intersection map. There are two main cases when combining the list elements:
Both left-most intervals start at the same value. In that case we found an interval that actually overlaps/intersects. We clip the longer interval to the shorter one, and use the values associated with the two intervals to get the result value, which now—together with the shorter interval—goes into the result list. The rest of the longer interval goes back to the input lists.
One of the intervals starts at a smaller value than the other, which means we found a part of the two series that do not overlap. So for the intersection, all of the non-overlapping part of the interval (or even the whole interval) can be discared. The rest (if any) goes back to the input list.
For completeness, here's the full example code. Again, the code is rather clumsy; a step-function-based implementation would certainly be more elegant.
import Control.Applicative
import Data.List
import qualified Data.Map as Map
data Val a = NegInf | Val a | Inf deriving (Show, Read, Eq, Ord)
instance Enum a => Enum (Val a) where
succ v = case v of
NegInf -> NegInf
Val x -> Val $ succ x
Inf -> Inf
pred v = case v of
NegInf -> NegInf
Val x -> Val $ pred x
Inf -> Inf
toEnum = Val . toEnum
fromEnum (Val x) = fromEnum x
data Interval a = Interval { lowerBound :: Val a, upperBound :: Val a } deriving (Show, Read, Eq)
instance Ord a => Ord (Interval a) where
compare ia ib = let (a, a') = (lowerBound ia, upperBound ia)
(b, b') = (lowerBound ib, upperBound ib)
in case () of
_ | a' < b -> LT
_ | b' < a -> GT
_ | a == b && a' == b' -> EQ
_ -> error "Ord.Interval.compare: undefined for overlapping intervals"
newtype IntervalMap i a = IntervalMap { unIntervalMap :: Map.Map (Interval i) a }
deriving (Show, Read)
instance Functor (IntervalMap i) where
fmap f = IntervalMap . fmap f . unIntervalMap
instance (Ord i, Enum i) => Applicative (IntervalMap i) where
pure = IntervalMap . Map.singleton (Interval NegInf Inf)
(<*>) = intersectionWith ($)
intersectionWith :: (Ord i, Enum i) => (a -> b -> c)
-> IntervalMap i a -> IntervalMap i b -> IntervalMap i c
intersectionWith f = combineWith (liftA2 f)
combineWith :: (Ord i, Enum i) => (Maybe a -> Maybe b -> Maybe c)
-> IntervalMap i a -> IntervalMap i b -> IntervalMap i c
combineWith f (IntervalMap mpA) (IntervalMap mpB) =
let cs = combineAscListWith f (Map.toAscList mpA) (Map.toAscList mpB)
in IntervalMap $ Map.fromList [ (i, v) | (i, Just v) <- cs ]
combineAscListWith :: (Ord i, Enum i) => (Maybe a -> Maybe b -> c)
-> [(Interval i, a)] -> [(Interval i, b)] -> [(Interval i, c)]
combineAscListWith f as bs = case (as, bs) of
([], _) -> map (\(i, v) -> (i, f Nothing (Just v))) bs
(_, []) -> map (\(i, v) -> (i, f (Just v) Nothing)) as
((Interval a a', va) : as', (Interval b b', vb) : bs')
| a == b -> case () of
_ | a' == b' -> (Interval a a', f (Just va) (Just vb)) : combineAscListWith f as' bs'
_ | a' < b' -> (Interval a a', f (Just va) (Just vb)) : combineAscListWith f as' ((Interval (succ a') b', vb) : bs')
_ | a' > b' -> (Interval a b', f (Just va) (Just vb)) : combineAscListWith f ((Interval (succ b') a', va) : as') bs'
| a < b -> case () of
_ | a' < b -> ((Interval a a', f (Just va) Nothing)) :
(if succ a' == b then id else ((Interval (succ a') (pred b), f Nothing Nothing) :)) (combineAscListWith f as' bs)
_ | True -> (Interval a (pred b), f (Just va) Nothing) : combineAscListWith f ((Interval b a', va) : as') bs
| a > b -> case () of
_ | b' < a -> ((Interval b b', f Nothing (Just vb))) :
(if succ b' == a then id else ((Interval (succ b') (pred a), f Nothing Nothing) :)) (combineAscListWith f as bs')
_ | True -> (Interval b (pred a), f Nothing (Just vb)) : combineAscListWith f as ((Interval a b', vb) : bs')
showIntervalMap :: (Show i, Show a, Eq i) => IntervalMap i a -> String
showIntervalMap = intercalate "; " . map (\(i, v) -> showInterval i ++ " -> " ++ show v)
. Map.toAscList . unIntervalMap
where
showInterval (Interval (Val a) (Val b)) | a == b = "[" ++ show a ++ "]"
showInterval (Interval a b) = "[" ++ showVal a ++ " .. " ++ showVal b ++ "]"
showVal NegInf = "-inf"
showVal (Val x) = show x
showVal Inf = "inf"
main :: IO ()
main = do
let signumMap = IntervalMap $ Map.fromList [(Interval NegInf (Val $ -1), -1),
(Interval (Val 0) (Val 0), 0), (Interval (Val 1) Inf, 1)]
putStrLn $ showIntervalMap $ (*) <$> signumMap <*> pure 5
I've taken András Kovács's DBIndex.hs, a very simple implementation of a dependently typed core, and tried simplifying it even further, as much as I possibly could, without "breaking" the type system. After several simplifications, I was left with something much smaller:
{-# language LambdaCase, ViewPatterns #-}
data Term
= V !Int
| A Term Term
| L Term Term
| S
| E
deriving (Eq, Show)
data VTerm
= VV !Int
| VA VTerm VTerm
| VL VTerm (VTerm -> VTerm)
| VS
| VE
type Ctx = ([VTerm], [VTerm], Int)
eval :: Bool -> Term -> Term
eval typ term = err (quote 0 (eval term typ ([], [], 0))) where
eval :: Term -> Bool -> Ctx -> VTerm
eval E _ _ = VE
eval S _ _ = VS
eval (V i) typ ctx#(vs, ts, _) = (if typ then ts else vs) !! i
eval (L a b) typ ctx#(vs,ts,d) = VL a' b' where
a' = eval a False ctx
b' = \v -> eval b typ (v:vs, a':ts, d+1)
eval (A f x) typ ctx = fx where
f' = eval f typ ctx
x' = eval x False ctx
xt = eval x True ctx
fx = case f' of
(VL a b) -> if check a xt then b x' else VE -- type mismatch
VS -> VE -- non function application
f -> VA f x'
check :: VTerm -> VTerm -> Bool
check VS _ = True
check a b = quote 0 a == quote 0 b
err :: Term -> Term
err term = if ok term then term else E where
ok (A a b) = ok a && ok b
ok (L a b) = ok a && ok b
ok E = False
ok t = True
quote :: Int -> VTerm -> Term
quote d = \case
VV i -> V (d - i - 1)
VA f x -> A (quote d f) (quote d x)
VL a b -> L (quote d a) (quote (d + 1) (b (VV d)))
VS -> S
VE -> E
reduce :: Term -> Term
reduce = eval False
typeof :: Term -> Term
typeof = eval True
The problem is that I have no idea what makes a type system consistent, so I had no criteria (other than intuition) and probably broke it in several ways. It, more or less, does what I think a type system should do, though:
main :: IO ()
main = do
-- id = ∀ (a:*) . (λ (x:a) . a)
let id = L S (L (V 0) (V 0))
-- nat = ∀ (a:*) . (a -> a) -> (a -> a)
let nat = L S (L (L (V 0) (V 1)) (L (V 1) (V 2)))
-- succ = λ (n:nat) . ∀ (a:*) . λ (s : a -> a) . λ (z:a) . s (n a s z)
let succ = L nat (L S (L (L (V 0) (V 1)) (L (V 1) (A (V 1) (A (A (A (V 3) (V 2)) (V 1)) (V 0))))))
-- zero = λ (a:*) . λ (s : a -> a) . λ (z : a) . z
let zero = L S (L (L (V 0) (V 1)) (L (V 1) (V 0)))
-- add = λ (x:nat) . λ (y:nat) . λ (a:*) . λ(s: a -> a) . λ (z : a) . (x a s (y a s z))
let add = L nat (L nat (L S (L (L (V 0) (V 1)) (L (V 1) (A (A (A (V 4) (V 2)) (V 1)) (A (A (A (V 3) (V 2)) (V 1)) (V 0)))))))
-- bool = ∀ (a:*) . a -> a -> a
let bool = L S (L (V 0) (L (V 1) (V 2)))
-- false = ∀ (a:*) . λ (x : a) . λ(y : a) . x
let false = L S (L (V 0) (L (V 1) (V 0)))
-- true = ∀ (a:*) . λ (x : a) . λ(y : a) . y
let true = L S (L (V 0) (L (V 1) (V 1)))
-- loop = ((λ (x:*) . (x x)) (λ (x:*) . (x x)))
let loop = A (L S (A (V 0) (V 0))) (L S (A (V 0) (V 0)))
-- natOrBoolId = λ (a:bool) . λ (t:(if a S then nat else bool)) . λ (x:t) . t
let natOrBoolId = L bool (L (A (A (A (V 0) S) nat) bool) (V 0))
-- nat
let c0 = zero
let c1 = A succ zero
let c2 = A succ c1
let c3 = A succ c2
let c4 = A succ c3
let c5 = A succ c4
-- Tests
let test name pass = putStrLn $ "- " ++ (if pass then "OK." else "ERR") ++ " " ++ name
putStrLn "True and false are bools"
test "typeof true == bool " $ typeof true == bool
test "typeof false == bool " $ typeof false == bool
putStrLn "Calling 'true nat' on two nats selects the first one"
test "reduce (true nat c1 c2) == c1" $ reduce (A (A (A true nat) c1) c2) == reduce c1
test "typeof (true nat c1 c2) == nat" $ typeof (A (A (A true nat) c1) c2) == nat
putStrLn "Calling 'true nat' on a bool is a type error"
test "reduce (true nat true c2) == E" $ reduce (A (A (A true nat) true) c2) == E
test "reduce (true nat c2 true) == E" $ reduce (A (A (A true nat) c2) true) == E
putStrLn "More type errors"
test "reduce (succ true) == E" $ reduce (A succ true) == E
putStrLn "Addition works"
test "reduce (add c2 c3) == c5" $ reduce (A (A add c2) c3) == reduce c5
test "typeof (add c2 c2) == nat" $ typeof (A (A add c2) c3) == nat
putStrLn "Loop isn't typeable"
test "typeof loop == E" $ typeof loop == E
putStrLn "Function with type that depends on value"
test "typeof (natOrBoolId true c2) == nat" $ typeof (A (A natOrBoolId true) c2) == nat
test "typeof (natOrBoolId true true) == E" $ typeof (A (A natOrBoolId true) true) == E
test "typeof (natOrBoolId false c2) == E" $ typeof (A (A natOrBoolId false) c2) == E
test "typeof (natOrBoolId false true) == bool" $ typeof (A (A natOrBoolId false) true) == bool
My question is, what exactly makes a system consistent? Specifically:
What problems do I get from the things I did (removing Pi, merging infer/eval, etc.)? Can those be somehow "justified" (generating a different system but still "correct")?
Basically: is it possible to fix this system (i.e., make it "suitable as the core of a dependently typed language just like CoC") while keeping it small?
Runnable code.
First of all, consistency is a thing in mathematical logic, meaning that a logic doesn't contain a contradiction. I've seen people speaking about the "consistency" of type theories, but unfortunately it is not a well-defined term.
In the context of lambda calculus, many people use the term "consistency" to mean vastly different things. Some people say the untyped lambda-calculus is consistent, by which they mean that different derivations of a lambda-term lead to the same result (i.e. the Church-Rosser property). In that respect, most calculi are consistent.
However, consistent can also mean that the Curry-Howard isomorphic logic of the calculus is consistent. Simply-typed lambda-calculus corresponds to first-order intuitionistic logic (without equality), and when people say STLC is consistent they really mean that first-order intuitionistic logic is consistent. In this sense of consistency, consistency means that the bottom (void) type doesn't have an inhabitant (and, thus, a derivation). That is, every expression must produce a value with a valid type. Bottom corresponds to falsehood, so this means that falsehood can't be derived (because you can derive anything from falsehood).
Now, in this sense, to be consistent you can't have nontermination (fix), non-returning functions, or control operators (call/cc and friends). Haskell is not consistent in this sense, because you can write functions that produce arbitrary types (the function f x = f x has type a -> b; obviously, this isn't consistent). Similarly, you can return undefined from anything, making it producing something of the bottom type. So, to make sure that a type theory is consistent in this sense, you'd have to make sure that you can't return the empty type from any expression.
But then, in the first sense of "consistency", Haskell is consistent I believe (barring some wacky features). Equational equivalence should be good.
I am dealing with some weird problem. I want to write a Haskell program, which will print given logical formulae, i.e.
print (showFormula (I (N (Z 'p')) (A (C (Z 'p') (Z 'q')) (Z 'r'))))
(where I is implication, A - alternative, N - negation, C - conjunction and Z - character)
should print something like that:
"(~p => ((p & q) | r))"
So far, my code looks like that:
data Formula
= Z Char
| V Bool
| N Formula
| C Formula Formula
| A Formula Formula
| I Formula Formula
| Join Formula Formula
showFormula :: Formula -> String
showFormula (Z c) = [c]
showFormula (Join a b) = (showFormula a) ++ (showFormula b)
showFormula (N a) = '~':(showFormula a)
showFormula (C a b) = "("++(showFormula a)++" & "++(showFormula b)++")"
showFormula (A a b) = "("++(showFormula a)++" | "++(showFormula b)++")"
showFormula (I a b) = "("++(showFormula a)++" => "++(showFormula b)++")"
It does print proper string but only when you input simple formula like (C (Z 'a') (Z 'b')) and it crashes with some expanded formulae. I know that the problem is passing a Formula parameter instead of String to showFormula function, but I have no idea how to change that. Please give me some advices how to fix that.
If you compile your code with the -Wall flag it will show you the following warning:
fml.hs:4:1: Warning:
Pattern match(es) are non-exhaustive
In an equation for `showFormula': Patterns not matched: V _
You forgot to write the V case for showFormula so it will crash if it reaches that, similarly to how the head function crashes if you call it with an empty list.
Looks like you just missed the (V b) case in your pattern match.
data Formula
= Z Char
| V Bool
| N Formula
| C Formula Formula
| A Formula Formula
| I Formula Formula
| Join Formula Formula
showFormula :: Formula -> String
showFormula (V b) = show b
showFormula (Z c) = [c]
showFormula (Join a b) = (showFormula a) ++ (showFormula b)
showFormula (N a) = '~':(showFormula a)
showFormula (C a b) = "("++(showFormula a)++" & "++(showFormula b)++")"
showFormula (A a b) = "("++(showFormula a)++" | "++(showFormula b)++")"
showFormula (I a b) = "("++(showFormula a)++" => "++(showFormula b)++")"
main :: IO ()
main = do
print (showFormula (I (N (Z 'p')) (A (C (Z 'p') (Z 'q')) (Z 'r'))))
-- "(~p => ((p & q) | r))"
Suppose there are some data types to express lambda and combinatorial terms:
data Lam α = Var α -- v
| Abs α (Lam α) -- λv . e1
| App (Lam α) (Lam α) -- e1 e2
deriving (Eq, Show)
infixl 0 :#
data SKI α = V α -- x
| SKI α :# SKI α -- e1 e2
| I -- I
| K -- K
| S -- S
deriving (Eq, Show)
There is also a function to get a list of lambda term's free variables:
fv ∷ Eq α ⇒ Lam α → [α]
fv (Var v) = [v]
fv (Abs x e) = filter (/= x) $ fv e
fv (App e1 e2) = fv e1 ++ fv e2
To convert lambda term to combinatorial term abstract elimination rules could be usefull:
convert ∷ Eq α ⇒ Lam α → SKI α
1) T[x] => x
convert (Var x) = V x
2) T[(E₁ E₂)] => (T[E₁] T[E₂])
convert (App e1 e2) = (convert e1) :# (convert e2)
3) T[λx.E] => (K T[E]) (if x does not occur free in E)
convert (Abs x e) | x `notElem` fv e = K :# (convert e)
4) T[λx.x] => I
convert (Abs x (Var y)) = if x == y then I else K :# V y
5) T[λx.λy.E] => T[λx.T[λy.E]] (if x occurs free in E)
convert (Abs x (Abs y e)) | x `elem` fv e = convert (Abs x (convert (Abs y e)))
6) T[λx.(E₁ E₂)] => (S T[λx.E₁] T[λx.E₂])
convert (Abs x (App y z)) = S :# (convert (Abs x y)) :# (convert (Abs x z))
convert _ = error ":["
This definition is not valid because of 5):
Couldn't match expected type `Lam α' with actual type `SKI α'
In the return type of a call of `convert'
In the second argument of `Abs', namely `(convert (Abs y e))'
In the first argument of `convert', namely
`(Abs x (convert (Abs y e)))'
So, what I have now is:
> convert $ Abs "x" $ Abs "y" $ App (Var "y") (Var "x")
*** Exception: :[
What I want is (hope I calculate it right):
> convert $ Abs "x" $ Abs "y" $ App (Var "y") (Var "x")
S :# (S (KS) (S (KK) I)) (S (KK) I)
Question:
If lambda term and combinatorial term have a different types of expression, how 5) could be formulated right?
Let's consider the equation T[λx.λy.E] => T[λx.T[λy.E]].
We know the result of T[λy.E] is an SKI expression. Since it has been produced by one of the cases 3, 4 or 6, it is either I or an application (:#).
Thus the outer T in T[λx.T[λy.E]] must be one of the cases 3 or 6. You can perform this case analysis in the code. I'm sorry but I don't have the time to write it out.
Here it's better to have a common data type for combinators and lambda expressions. Notice that your types already have significant overlap (Var, App), and it doesn't hurt to have combinators in lambda expressions.
The only possibility we want to eliminate is having lambda abstractions in combinator terms. We can forbid them using indexed types.
In the following code the type of a term is parameterised by the number of nested lambda abstractions in that term. The convert function returns Term Z a, where Z means zero, so there are no lambda abstractions in the returned term.
For more information about singleton types (which are used a bit here), see the paper Dependently Typed Programming with Singletons.
{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies, GADTs, TypeOperators,
ScopedTypeVariables, MultiParamTypeClasses, FlexibleInstances #-}
data Nat = Z | Inc Nat
data SNat :: Nat -> * where
SZ :: SNat Z
SInc :: NatSingleton n => SNat n -> SNat (Inc n)
class NatSingleton (a :: Nat) where
sing :: SNat a
instance NatSingleton Z where sing = SZ
instance NatSingleton a => NatSingleton (Inc a) where sing = SInc sing
type family Max (a :: Nat) (b :: Nat) :: Nat
type instance Max Z a = a
type instance Max a Z = a
type instance Max (Inc a) (Inc b) = Inc (Max a b)
data Term (l :: Nat) a where
Var :: a -> Term Z a
Abs :: NatSingleton l => a -> Term l a -> Term (Inc l) a
App :: (NatSingleton l1, NatSingleton l2)
=> Term l1 a -> Term l2 a -> Term (Max l1 l2) a
I :: Term Z a
K :: Term Z a
S :: Term Z a
fv :: Eq a => Term l a -> [a]
fv (Var v) = [v]
fv (Abs x e) = filter (/= x) $ fv e
fv (App e1 e2) = fv e1 ++ fv e2
fv _ = []
eliminateLambda :: (Eq a, NatSingleton l) => Term (Inc l) a -> Term l a
eliminateLambda t =
case t of
Abs x t ->
case t of
Var y
| y == x -> I
| otherwise -> App K (Var y)
Abs {} -> Abs x $ eliminateLambda t
App a b -> S `App` (eliminateLambda $ Abs x a)
`App` (eliminateLambda $ Abs x b)
App a b -> eliminateLambdaApp a b
eliminateLambdaApp
:: forall a l1 l2 l .
(Eq a, Max l1 l2 ~ Inc l,
NatSingleton l1,
NatSingleton l2)
=> Term l1 a -> Term l2 a -> Term l a
eliminateLambdaApp a b =
case (sing :: SNat l1, sing :: SNat l2) of
(SInc _, SZ ) -> App (eliminateLambda a) b
(SZ , SInc _) -> App a (eliminateLambda b)
(SInc _, SInc _) -> App (eliminateLambda a) (eliminateLambda b)
convert :: forall a l . Eq a => NatSingleton l => Term l a -> Term Z a
convert t =
case sing :: SNat l of
SZ -> t
SInc _ -> convert $ eliminateLambda t
The key insight is that S, K and I are just constant Lam terms, in the same way that 1, 2 and 3 are constant Ints. It would be pretty easy to make rule 5 type-check by making an inverse to the 'convert' function:
nvert :: SKI a -> Lam a
nvert S = Abs "x" (Abs "y" (Abs "z" (App (App (Var "x") (Var "z")) (App (Var "y") (Var "z")))))
nvert K = Abs "x" (Abs "y" (Var "x"))
nvert I = Abs "x" (Var "x")
nvert (V x) = Var x
nvert (x :# y) = App (nvert x) (nvert y)
Now we can use 'nvert' to make rule 5 type-check:
convert (Abs x (Abs y e)) | x `elem` fv e = convert (Abs x (nvert (convert (Abs y e))))
We can see that the left and the right are identical (we'll ignore the guard), except that 'Abs y e' on the left is replaced by 'nvert (convert (Abs y e))' on the right. Since 'convert' and 'nvert' are each others' inverse, we can always replace any Lam 'x' with 'nvert (convert x)' and likewise we can always replace any SKI 'x' with 'convert (nvert x)', so this is a valid equation.
Unfortunately, while it's a valid equation it's not a useful function definition because it won't cause the computation to progress: we'll just convert 'Abs y e' back and forth forever!
To break this loop we can replace the call to 'nvert' with a 'reminder' that we should do it later. We do this by adding a new constructor to Lam:
data Lam a = Var a -- v
| Abs a (Lam a) -- \v . e1
| App (Lam a) (Lam a) -- e1 e2
| Com (SKI a) -- Reminder to COMe back later and nvert
deriving (Eq, Show)
Now rule 5 uses this reminder instead of 'nvert':
convert (Abs x (Abs y e)) | x `elem` fv e = convert (Abs x (Com (convert (Abs y e))))
Now we need to make good our promise to come back, by making a separate rule to replace reminders with actual calls to 'nvert', like this:
convert (Com c) = convert (nvert c)
Now we can finally break the loop: we know that 'convert (nvert c)' is always identical to 'c', so we can replace the above line with this:
convert (Com c) = c
Notice that our final definition of 'convert' doesn't actually use 'nvert' at all! It's still a handy function though, since other functions involving Lam can use it to handle the new 'Com' case.
You've probably noticed that I've actually named this constructor 'Com' because it's just a wrapped-up COMbinator, but I thought it would be more informative to take a slightly longer route than just saying "wrap up your SKIs in Lams" :)
If you're wondering why I called that function "nvert", see http://unapologetic.wordpress.com/2007/05/31/duality-terminology/ :)
Warbo is right, combinators are constant lambda terms, consequently the conversion function is
T[ ]:L -> C with L the set of lambda terms and C that of combinatory terms and with C ⊂ L .
So there is no typing problem for the rule T[λx.λy.E] => T[λx.T[λy.E]]
Here an implementation in Scala.