-
-
Save frasertweedale/3727fec7c19ed6b62d60037cce679c76 to your computer and use it in GitHub Desktop.
| {-# LANGUAGE DataKinds #-} | |
| {-# LANGUAGE GADTs #-} | |
| {-# LANGUAGE PolyKinds #-} | |
| {-# LANGUAGE TypeFamilies #-} | |
| {-# LANGUAGE ScopedTypeVariables #-} | |
| {-# LANGUAGE RankNTypes #-} | |
| {-# LANGUAGE MultiParamTypeClasses #-} | |
| import Control.Applicative (Alternative(..)) | |
| import Data.Char (isDigit) | |
| newtype Const a b = Const { getConst :: a } | |
| newtype Flip p a b = Flip { runFlip :: p b a } | |
| newtype Compose f g a = Compose { runCompose :: f (g a) } | |
| data Nat = Z | S Nat | |
| type family SUB (n :: Nat) (m :: Nat) :: Nat | |
| type instance SUB n Z = n | |
| type instance SUB (S n) (S m) = SUB n m | |
| class NatInd (n :: Nat) where | |
| ind :: f Z -> (forall m. NatInd m => f m -> f (S m)) -> f n | |
| instance NatInd Z where | |
| ind z _f = z | |
| instance (NatInd m) => NatInd (S m) where | |
| ind z f = f (ind z f) | |
| type ZERO = Z | |
| type ONE = S Z | |
| type TWO = S (S Z) | |
| type THREE = S (S (S Z)) | |
| type FOUR = S (S (S (S Z))) | |
| type FIVE = S (S (S (S (S Z)))) | |
| type SIX = S (S (S (S (S (S Z))))) | |
| type SEVEN = S (S (S (S (S (S (S Z)))))) | |
| type EIGHT = S (S (S (S (S (S (S (S Z))))))) | |
| data EXACTLY (n :: Nat) a where | |
| NNil :: EXACTLY Z a | |
| NCons :: a -> EXACTLY n a -> EXACTLY (S n) a | |
| infixr 5 `NCons` | |
| instance Functor (EXACTLY n) where | |
| fmap f l = case l of | |
| NNil -> NNil | |
| NCons x xs -> NCons (f x) (fmap f xs) | |
| instance (Show a) => Show (EXACTLY n a) where | |
| show = show . toListExact | |
| toListExact :: EXACTLY n a -> [a] | |
| toListExact l = case l of | |
| NNil -> [] | |
| NCons h t -> h : toListExact t | |
| fromListExact :: NatInd n => [a] -> Maybe (EXACTLY n a) | |
| fromListExact = fmap (fmap runFlip . runCompose) . runCompose $ ind z f | |
| where | |
| z = Compose $ \l -> Compose $ case l of | |
| [] -> Just (Flip NNil) | |
| _ -> Nothing | |
| f r = Compose $ \l -> Compose $ case l of | |
| [] -> Nothing | |
| (x:xs) -> fmap (Flip . NCons x . runFlip) . runCompose $ runCompose r xs | |
| data UPTO (n :: Nat) a where | |
| UNil :: UPTO n a | |
| UCons :: a -> UPTO n a -> UPTO (S n) a | |
| infixr 5 `UCons` | |
| instance Functor (UPTO n) where | |
| fmap f l = case l of | |
| UNil -> UNil | |
| UCons x xs -> UCons (f x) (fmap f xs) | |
| instance (Show a) => Show (UPTO n a) where | |
| show = show . toListUpto | |
| toListUpto :: UPTO n a -> [a] | |
| toListUpto l = case l of | |
| UNil -> [] | |
| UCons h t -> h : toListUpto t | |
| fromListUpto :: NatInd n => [a] -> Maybe (UPTO n a) | |
| fromListUpto = fmap (fmap runFlip . runCompose) . runCompose $ ind z f | |
| where | |
| z = Compose $ \l -> Compose $ case l of | |
| [] -> Just (Flip UNil) | |
| _ -> Nothing | |
| f r = Compose $ \l -> Compose $ case l of | |
| [] -> Just (Flip UNil) | |
| (x:xs) -> fmap (Flip . UCons x . runFlip) . runCompose $ runCompose r xs | |
| {- LIST OF SIZE IN RANGE, REUSING EXACTLY AND UPTO -} | |
| data FROMTO (lo :: Nat) (hi :: Nat) a | |
| = FROMTO (EXACTLY lo a) (UPTO (SUB hi lo) a) | |
| instance Functor (FROMTO lo hi) where | |
| fmap f (FROMTO xs ys) = FROMTO (fmap f xs) (fmap f ys) | |
| toListFromto :: FROMTO lo hi a -> [a] | |
| toListFromto (FROMTO xs ys) = toListExact xs <> toListUpto ys | |
| instance Show a => Show (FROMTO lo hi a) where | |
| show = show . toListFromto | |
| fromListFromto | |
| :: forall lo hi a. (NatInd lo, NatInd (SUB hi lo)) | |
| => [a] -> Maybe (FROMTO lo hi a) | |
| fromListFromto l = | |
| let | |
| i = getConst (ind (Const 0) (Const . (+1) . getConst) :: Const Int lo) | |
| (l1, l2) = (take i l, drop i l) | |
| in | |
| FROMTO <$> fromListExact l1 <*> fromListUpto l2 | |
| -- | Lock-step induction over two nats | |
| class NatInd2 (n :: Nat) (m :: Nat) where | |
| ind2 | |
| :: f Z Z | |
| -> (forall q. (NatInd2 Z q) => f Z q -> f Z (S q)) | |
| -> (forall p. (NatInd2 p Z) => f p Z -> f (S p) Z) | |
| -> (forall p q. (NatInd2 p q) => f p q -> f (S p) (S q)) | |
| -> f n m | |
| instance NatInd2 Z Z where | |
| ind2 zz _zm _nz _nm = zz | |
| instance (NatInd2 Z m) => NatInd2 Z (S m) where | |
| ind2 zz zm nz nm = zm (ind2 zz zm nz nm) | |
| instance (NatInd2 n Z) => NatInd2 (S n) Z where | |
| ind2 zz zm nz nm = nz (ind2 zz zm nz nm) | |
| instance (NatInd2 n m) => NatInd2 (S n) (S m) where | |
| ind2 zz zm nz nm = nm (ind2 zz zm nz nm) | |
| {- LIST OF SIZE IN RANGE, STRUCTURAL -} | |
| data RANGE (lo :: Nat) (hi :: Nat) a where | |
| RNil :: RANGE Z hi a | |
| RCons :: a -> RANGE lo hi a -> RANGE (S lo) (S hi) a | |
| RConsHi :: a -> RANGE lo hi a -> RANGE lo (S hi) a | |
| infixr 5 `RCons` | |
| infixr 5 `RConsHi` | |
| instance Functor (RANGE lo hi) where | |
| fmap f l = case l of | |
| RNil -> RNil | |
| RCons x xs -> RCons (f x) (fmap f xs) | |
| RConsHi x xs -> RConsHi (f x) (fmap f xs) | |
| instance (Show a) => Show (RANGE lo hi a) where | |
| show = show . toListRange | |
| toListRange :: RANGE lo hi a -> [a] | |
| toListRange l = case l of | |
| RNil -> [] | |
| RCons h t -> h : toListRange t | |
| RConsHi h t -> h : toListRange t | |
| newtype Flip2 p a b c = Flip2 { runFlip2 :: p b c a } | |
| newtype Compose2 f g a b = Compose2 { runCompose2 :: f (g a b) } | |
| fromListRange :: (NatInd2 lo hi) => [a] -> Maybe (RANGE lo hi a) | |
| fromListRange = fmap (fmap runFlip2 . runCompose2) . runCompose2 $ ind2 | |
| -- lo and hi reached zero | |
| -- if input empty, return empty list | |
| -- if input non-empty, fail | |
| ( Compose2 $ \l -> Compose2 $ case l of [] -> Just (Flip2 RNil) ; _ -> Nothing ) | |
| -- lo reached zero | |
| -- if input empty, return empty list | |
| -- if input non-empty, recurse | |
| ( \r -> Compose2 $ \l -> Compose2 $ case l of | |
| [] -> Just (Flip2 RNil) | |
| (x:xs) -> fmap (Flip2 . RConsHi x . runFlip2) . runCompose2 $ runCompose2 r xs ) | |
| -- hi cannot reach zero before lo; fail | |
| ( \_r -> Compose2 $ \_l -> Compose2 Nothing ) | |
| -- hi and low non-zero | |
| -- if input empty, fail | |
| -- if input non-empty, recurse | |
| ( \r -> Compose2 $ \l -> Compose2 $ case l of | |
| [] -> Nothing | |
| (x:xs) -> fmap (Flip2 . RCons x . runFlip2) . runCompose2 $ runCompose2 r xs ) | |
| {- PARSER -} | |
| newtype Parser a = Parser { runParser :: String -> Maybe (a, String) } | |
| parseFailure :: Parser a | |
| parseFailure = Parser $ const Nothing | |
| satisfy :: (Char -> Bool) -> Parser Char | |
| satisfy pred = Parser $ \s -> case s of | |
| (c:cs) | pred c -> Just (c, cs) | |
| _ -> Nothing | |
| char :: Char -> Parser Char | |
| char c = satisfy (== c) | |
| digit :: Parser Char | |
| digit = satisfy isDigit | |
| instance Functor Parser where | |
| fmap f p = Parser $ \s -> case runParser p s of | |
| Nothing -> Nothing | |
| Just (a, s') -> Just (f a, s') | |
| instance Applicative Parser where | |
| pure a = Parser $ \s -> Just (a, s) | |
| pf <*> pa = Parser $ \s -> case runParser pf s of | |
| Nothing -> Nothing | |
| Just (f, s') -> runParser (f <$> pa) s' | |
| instance Alternative Parser where | |
| empty = parseFailure | |
| p1 <|> p2 = Parser $ \s -> runParser p1 s <|> runParser p2 s | |
| parseRange :: (NatInd2 lo hi) => Parser a -> Parser (RANGE lo hi a) | |
| parseRange p = fmap runFlip2 . runCompose2 $ ind2 | |
| ( Compose2 . pure . Flip2 $ RNil ) | |
| -- lo reached zero, hi did not. | |
| -- continue parsing, failure is acceptable | |
| -- | |
| ( \r -> Compose2 . fmap Flip2 $ | |
| RConsHi <$> p <*> (fmap runFlip2 . runCompose2 $ r) | |
| <|> pure RNil | |
| ) | |
| ( \_r -> Compose2 parseFailure ) | |
| -- hi and low non-zero | |
| -- continue parsing, failure at this level unacceptable | |
| ( \r -> Compose2 . fmap Flip2 $ | |
| RCons <$> p <*> (fmap runFlip2 . runCompose2 $ r) | |
| ) |
There should be a function that extracts an EXACTLY from an UPTO: out : UPTO n a -> Sigma Nat (\m -> (m <= n, EXACTLY m a)). That is to say, an UPTO n can be turned into an EXACTLY m for some m <= n.
There should also be a family of functions that 'inject' EXACTLY into UPTO: for a given n : Nat, we should have in_0 : EXACTLY 0 a -> UPTO n a, in_1 : EXACTLY 1 a -> UPTO n a, ..., in_n : EXACTLY n a -> UPTO n a. As a single function this would be in : m <= n -> EXACTLY m a -> UPTO n a.
If you squint, it looks like in takes as arguments the results of out. You could modify in to make this true and you wouldn't lose any precision: in : Sigma Nat (\m -> (m <= n, EXACTLY m a)) -> UPTO n a.
I'm gonna claim that UPTO n a is isomorphic to Sigma Nat (\m -> (m <= n, EXACTLY m a)). So it could be defined as:
data (<=) : Nat -> Nat -> Type where
LTE_Z : Z <= n
LTS_S : m <= n -> S m <= S n
data UPTO : Nat -> Type -> Type where
UPTO : m <= n -> EXACTLY m a -> UPTO n a
The structure of <= is the same as Nat- m <= n actually represents the natural number m.
This means that the new definition of UPTO is a pair of a natural number m (bounded by n), and a list of EXACTLY length m. I think that's a good definition.
Another way of putting it is that UPTO n a is either an EXACTLY 0 a or an EXACTLY 1 a or ... or an EXACTLY n a. It's an iterated sum, which is why I used Sigma.
I think FROMTO can be similarly improved. I'm running out of steam, so I'll just leave this here:
data FROMTO : Nat -> Nat -> Type -> Type where
FROMTO : lo <= m -> m <= hi -> EXACTLY m a -> FROMTO lo hi a
I recommend using singletons to implement
indonce and for all, rather than using a type class to compute an induction function per natural number.