Created
March 17, 2023 15:24
-
-
Save kuribas/36291152663166bb1ee4b1eca7d6337f to your computer and use it in GitHub Desktop.
This file contains hidden or 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
module Records | |
import Data.String | |
import Data.Maybe | |
import Data.List | |
import Data.List.Elem | |
import Data.Vect | |
import Data.Morphisms | |
import Decidable.Equality | |
import Data.IOArray.Prims | |
infixr 10 :-> | |
infixr 10 !! | |
infixr 3 <*~> | |
infixr 4 <$~> | |
data Any : Type where | |
MkAny : a -> Any | |
unsafeGetAny : Any -> a | |
unsafeGetAny (MkAny a) = believe_me a | |
public export | |
RecordSpec : Type -> Type | |
RecordSpec k = List (String, k) | |
public export | |
data RecordEntry : String -> Type -> Type where | |
(:->) : (0 s:String) -> t -> RecordEntry s t | |
export | |
data RecordBuilder : (RecordSpec specType) -> (specType -> Type) -> Type where | |
MkRecordBuilder : (ArrayData Any -> Int -> IO ()) -> RecordBuilder s f | |
export | |
Nil : RecordBuilder Nil c | |
Nil = MkRecordBuilder $ \a, ix => pure () | |
export | |
(::) : {s:String} -> | |
{0 f:k -> Type} -> | |
{t: k} -> | |
RecordEntry s (f t) -> | |
RecordBuilder xs f -> | |
RecordBuilder ((s, t) :: xs) f | |
(_ :-> x) :: MkRecordBuilder builder = MkRecordBuilder $ \arr, ix => | |
do primIO $ prim__arraySet arr ix (MkAny x) | |
builder arr (ix+1) | |
public export | |
AllConstraints : (a -> Type) -> List a -> Type | |
AllConstraints f [] = () | |
AllConstraints f (c::cs) = (f c, AllConstraints f cs) | |
public export | |
OverSpec : (k -> Type) -> (String, k) -> Type | |
OverSpec c (name, value) = c value | |
public export | |
AllSpecs : (k -> Type) -> RecordSpec k -> Type | |
AllSpecs c = AllConstraints (OverSpec c) | |
-- proof that the spec has some field with a given type. | |
public export | |
data HasField : (s:String) -> (t:k) -> RecordSpec k -> Type where | |
[search s] | |
FirstField : HasField s t ((s, t) :: _) | |
NextField : (0 prf : (t == s) === False) => HasField s v xs -> HasField s v ((t, w) :: xs) | |
%builtin Natural HasField | |
public export | |
implementation Uninhabited (HasField s t []) where | |
uninhabited FirstField impossible | |
uninhabited NextField impossible | |
public export | |
HasField' : RecordSpec k -> (String, k) -> Type | |
HasField' r (s, t) = HasField s t r | |
public export | |
HasFields : (l:RecordSpec k) -> RecordSpec k -> Type | |
HasFields l r = AllConstraints (HasField' r) l | |
data IsNothing : Maybe a -> Type where | |
ItIsNothing : IsNothing Nothing | |
-- proof that an optional field with the given type exists. | |
-- present. This is written in terms of HasField, so it is be | |
-- isomorphic to Maybe Natural, and can use efficient indexing. | |
public export | |
data HasOptionalField : (s:String) -> (t:k) -> RecordSpec k -> Type where | |
NoSuchField : IsNothing (lookup s specs) => HasOptionalField s t specs | |
FieldExists : HasField s t specs => HasOptionalField s t specs | |
public export | |
HasOptionalField' : RecordSpec k -> (String, k) -> Type | |
HasOptionalField' r (s, t) = HasOptionalField s t r | |
public export | |
HasOptionalFields : (l:RecordSpec k) -> RecordSpec k -> Type | |
HasOptionalFields l r = AllConstraints (HasOptionalField' r) l | |
public export | |
remField : RecordSpec k -> (String, k) -> RecordSpec k | |
remField [] _ = [] | |
remField ((s, k) :: specs) (t, l) = | |
if (s == t) then specs else (s, k) :: remField specs (t, l) | |
public export | |
remFields : (l:RecordSpec k) | |
-> RecordSpec k | |
-> RecordSpec k | |
remFields spec [] = spec | |
remFields spec (x :: xs) = remFields (remField spec x) xs | |
public export | |
data NubFields : RecordSpec k -> RecordSpec k -> RecordSpec k ->Type where | |
MkNubFields : {spec:RecordSpec k} -> {fields: RecordSpec k} -> NubFields spec fields (remFields spec fields) | |
public export | |
data WithRecordFields : RecordSpec k | |
-> RecordSpec k | |
-> RecordSpec k | |
-> RecordSpec k | |
-> Type where | |
MkWithRecordFields : {spec:RecordSpec k} | |
-> {mandatory: RecordSpec k} | |
-> {optional: RecordSpec k} | |
-> {other: RecordSpec k} | |
-> HasFields spec mandatory | |
=> NubFields spec (optional ++ mandatory) other | |
=> WithRecordFields spec mandatory optional other | |
export | |
data Record : RecordSpec specType -> (specType -> Type) -> Type where | |
MkRecord : Int -> ArrayData Any -> Record s f | |
public export | |
SimpleRecord : RecordSpec Type -> Type | |
SimpleRecord s = Record s Prelude.id | |
export | |
recordHead : Record ((l, x) :: rs) f -> f x | |
recordHead (MkRecord ix arr) = | |
unsafeGetAny $ unsafePerformIO $ primIO $ prim__arrayGet arr ix | |
export | |
recordTail : Record (_ :: rs) f -> Record rs f | |
recordTail (MkRecord ix arr) = MkRecord (ix+1) arr | |
namespace RecordView | |
public export | |
data RecordView : (sp : RecordSpec k) -> (f : k -> Type) -> Type where | |
Nil : RecordView [] f | |
(::) : {0 f : k -> Type} -> RecordEntry s (f t) -> Record xs f -> RecordView ((s, t) :: xs) f | |
export | |
recordView : {spec:RecordSpec k} -> | |
Record spec f -> | |
RecordView spec f | |
recordView {spec=[]} _ = [] | |
recordView {spec=(lbl,t)::xs} r = (lbl :-> recordHead r) :: recordTail r | |
public export | |
RecordEntryType : (l:RecordSpec k) -> (s:String) -> (k -> Type) -> HasField s t l => Type | |
RecordEntryType ((s, t) :: _) s f @{FirstField} = f t | |
RecordEntryType (_ :: xs) s f @{NextField x} = RecordEntryType xs s f | |
export | |
buildRecord : {s: RecordSpec specType} -> RecordBuilder s f -> Record s f | |
buildRecord {s} (MkRecordBuilder builder) = unsafePerformIO $ | |
do newArr <- primIO $ prim__newArray (cast $ length s) (MkAny ()) | |
builder newArr 0 | |
pure $ MkRecord 0 newArr | |
export | |
hasFieldToIndex : HasField s t r -> Integer | |
hasFieldToIndex FirstField = 0 | |
hasFieldToIndex (NextField prevField) = 1 + hasFieldToIndex prevField | |
%builtin NaturalToInteger hasFieldToIndex | |
export | |
get : (0 s:String) -> HasField s t r => Record r f -> (f t) | |
get @{fld} s (MkRecord ix arr) = | |
unsafeGetAny $ unsafePerformIO $ primIO $ prim__arrayGet arr $ | |
(cast $ hasFieldToIndex fld) + ix | |
export | |
getMaybe : (0 s:String) -> HasOptionalField s t r => Record r f -> Maybe (f t) | |
getMaybe _ @{NoSuchField} _ = Nothing | |
getMaybe s @{FieldExists} rl = Just $ get s rl | |
export | |
getOpt : (0 s:String) -> HasOptionalField s t r => Lazy (f t) -> Record r f -> f t | |
getOpt s def r = fromMaybe def $ getMaybe s r | |
mapRecordBuilder : {specs:RecordSpec k} -> | |
({a : k} -> f a -> g a) -> | |
Record specs f -> | |
RecordBuilder specs g | |
mapRecordBuilder f r with (recordView r) | |
_ | [] = [] | |
_ | ((lbl :-> v) :: y) = (lbl :-> f v) :: mapRecordBuilder f y | |
export | |
mapRecord : {specs: RecordSpec k} -> | |
({a : k} -> f a -> g a) -> | |
Record specs f -> | |
Record specs g | |
mapRecord f r = buildRecord $ mapRecordBuilder f r | |
fillRecordBuilder : (s:RecordSpec k) -> (forall a. f a) -> RecordBuilder s f | |
fillRecordBuilder [] v = [] | |
fillRecordBuilder ((s, x) :: xs) v = (s :-> v) :: fillRecordBuilder xs v | |
export | |
fillRecord : (s:RecordSpec k) -> (forall a. f a) -> Record s f | |
fillRecord r v = buildRecord $ fillRecordBuilder r v | |
zipWithRecordBuilder : ({a : k} -> f a -> g a -> h a) -> | |
{s: RecordSpec k} -> | |
Record s f -> | |
Record s g -> | |
RecordBuilder s h | |
zipWithRecordBuilder f r1 r2 with ((recordView r1, recordView r2)) | |
_ | ([], []) = [] | |
_ | (lbl :-> x :: z, lbl :-> y :: w) = | |
lbl :-> f x y :: zipWithRecordBuilder f z w | |
export | |
zipWithRecord : ({a : k} -> f a -> g a -> h a) -> | |
{s: RecordSpec k} -> | |
Record s f -> | |
Record s g -> | |
Record s h | |
zipWithRecord f r1 r2 = buildRecord $ zipWithRecordBuilder f r1 r2 | |
namespace Hkd | |
public export | |
data HkdList : (k -> Type) -> List k -> Type where | |
Nil : HkdList f [] | |
(::) : {f:k -> Type} -> f a -> HkdList f b -> HkdList f (a :: b) | |
export | |
mapHkd : {g : k -> Type} -> (forall a . f a -> g a) -> HkdList f s -> HkdList g s | |
mapHkd f [] = [] | |
mapHkd f (x :: y) = f x :: mapHkd f y | |
splitRow : {fs : List (k -> Type)} -> | |
{as : RecordSpec k} -> | |
HkdList (Record ((s, a) :: as)) fs -> | |
( HkdList ($ a) fs | |
, HkdList (Record as) fs) | |
splitRow [] = ([], []) | |
splitRow (r :: y) with (recordView r) | |
_ | ((lbl :-> x) :: z) = | |
let (ls, zs) = splitRow y | |
in (x :: ls, z :: zs) | |
zipWithManyRB : {fs : List (k -> Type)} -> | |
{s : RecordSpec k} -> | |
({a : k} -> Hkd.HkdList ($ a) fs -> g a) -> | |
Hkd.HkdList (Record s) fs -> | |
RecordBuilder s g | |
zipWithManyRB f l with (s) | |
_ | [] = [] | |
_ | ((s1, a1) :: ss) = | |
let (l1, ls) = splitRow l | |
in s1 :-> f l1 :: zipWithManyRB f ls | |
export | |
zipWithManyRecord : {fs : List (k -> Type)} -> | |
{s : RecordSpec k} -> | |
({a : k} -> Hkd.HkdList ($ a) fs -> g a) -> | |
Hkd.HkdList (\f => Record s f) fs -> | |
Record s g | |
zipWithManyRecord f l = buildRecord $ zipWithManyRB f l | |
export | |
foldMapRecord : Monoid m => | |
{spec:RecordSpec k} -> | |
({a : k} -> f a -> m) -> | |
Record spec f -> m | |
foldMapRecord f r with (recordView r) | |
_ | [] = neutral | |
_ | ((lbl :-> x) :: y) = f x <+> foldMapRecord f y | |
export | |
foldrRecord : {spec:RecordSpec k} -> | |
({a : k} -> f a -> acc -> acc) -> | |
acc -> | |
Record spec f -> | |
acc | |
foldrRecord f acc r with (recordView r) | |
_ | [] = acc | |
_ | ((_ :-> x) :: y) = f x $ foldrRecord f acc y | |
export | |
foldlRecord : {s : RecordSpec k} -> | |
({a : k} -> acc -> f a -> acc) -> | |
acc -> | |
Record s f -> | |
acc | |
foldlRecord f acc r with (recordView r) | |
_ | [] = acc | |
_ | ((_ :-> x) :: y) = foldlRecord f (f acc x) y | |
export | |
recordToList : {s : RecordSpec k} -> | |
{a : Type} -> | |
Record s (const a) -> | |
List a | |
recordToList = foldrRecord (::) [] | |
recordBuilderLabels : (s : RecordSpec k) -> RecordBuilder s (const String) | |
recordBuilderLabels [] = [] | |
recordBuilderLabels ((lbl, x) :: xs) = (lbl :-> lbl) :: recordBuilderLabels {s = xs} | |
export | |
recordLabels : (s : RecordSpec k) -> Record s (const String) | |
recordLabels s = buildRecord $ recordBuilderLabels s | |
recordBuilderDicts : (0 c : k -> Type) -> (s : RecordSpec k) -> (cs : AllSpecs c s) => RecordBuilder s c | |
recordBuilderDicts _ [] = [] | |
recordBuilderDicts c ((s, x) :: xs) {cs=(c1,_)} = (s :-> c1) :: recordBuilderDicts c xs | |
export | |
recordDicts : (0 c : k -> Type) -> (l : RecordSpec k) -> (cs : AllSpecs c l) => Record l c | |
recordDicts c l {cs} = buildRecord $ recordBuilderDicts c l {cs=cs} | |
recordBuilderSpecs : (l : RecordSpec k) -> RecordBuilder l (const k) | |
recordBuilderSpecs [] = [] | |
recordBuilderSpecs ((s, spec) :: xs) = s :-> spec :: recordBuilderSpecs xs | |
export | |
recordSpecs : (l : RecordSpec k) -> Record l (const k) | |
recordSpecs l = buildRecord $ recordBuilderSpecs l | |
export | |
{spec : RecordSpec k} -> | |
{f : k -> Type} -> | |
AllSpecs (\k => Show (f k)) spec => | |
Show (Record spec f) where | |
show r = "[" <+> | |
( joinBy ", " $ | |
recordToList $ | |
zipWithManyRecord | |
(\ [showDict, lbl, x] => | |
"(lbl" <+> " :-> " <+> show @{showDict} x <+> ")") | |
[ recordDicts (\k => Show (f k)) spec | |
, recordLabels spec | |
, r] | |
) <+> "]" | |
testShow : {l:List (String, Type)} -> AllSpecs Show l => SimpleRecord l -> Record l (const String) | |
testShow x = zipWithManyRecord (\[d, x] => show @{d} x) | |
[recordDicts Show l, x] | |
testx : Record [("anint", Int), ("astring", String)] Prelude.id | |
testx = buildRecord ["anint" :-> 3, "astring" :-> "foo"] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment