Last active
October 6, 2021 21:45
-
-
Save frasertweedale/3727fec7c19ed6b62d60037cce679c76 to your computer and use it in GitHub Desktop.
length-indexed list experiment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# 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) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think
FROMTO
can be similarly improved. I'm running out of steam, so I'll just leave this here: