Reputation: 544
I'm playing around with idris and was trying to implement extensible records.
The main goal is to guarantee that the record's keys are unique.
For example
("Year" := 1998) +: rnil
is legal
("Title" := "test") +: ("Year" := 1998) +: rnil
also works, but
("Year" := "test") +: ("Year" := 1998) +: rnil
Should fail to compile.
I came up with the following implementation which compiles fine:
{-
See: http://lpaste.net/104020
and https://github.com/gonzaw/extensible-records
-}
module Record
import Data.List
%default total
data HList : List Type -> Type where
Nil : HList []
(::) : a -> HList xs -> HList (a :: xs)
infix 5 :=
data Field : lbl -> Type -> Type where
(:=) : (label : lbl) ->
(value : b) -> Field label b
labelsOf : List (lbl, Type) -> List lbl
labelsOf [] = []
labelsOf ((label, _) :: xs) = label :: labelsOf xs
toFields : List (lbl, Type) -> List Type
toFields [] = []
toFields ((l, t) :: xs) = (Field l t) :: toFields xs
data IsSet : List t -> Type where
IsSetNil : IsSet []
IsSetCons : Not (Elem x xs) -> IsSet xs -> IsSet (x :: xs)
data Record : List (lty, Type) -> Type where
MkRecord : IsSet (labelsOf ts) -> HList (toFields ts) ->
Record ts
infixr 6 +:
rnil : Record []
rnil = MkRecord IsSetNil []
prepend : { label : lbl,
xs : List (lbl, Type),
prf : Not (Elem label (labelsOf xs))
} ->
Field label t ->
Record xs ->
Record ((label, t) :: xs)
prepend {prf} f (MkRecord isSet fs) = MkRecord (IsSetCons prf isSet) (f :: fs)
data IsNo : Dec prop -> Type where
ItIsNo : IsNo (No y)
(+:) : DecEq lbl =>
{ label : lbl, xs : List (lbl, Type) } ->
Field label t ->
Record xs ->
{ auto isno : IsNo (isElem label $ labelsOf xs) } ->
Record ((label, t) :: xs)
(+:) {label} {xs} f r with (isElem label $ labelsOf xs)
(+:) { isno = ItIsNo } _ _ | (Yes _) impossible
(+:) f r | (No no) = prepend {prf = no} f r
The interesting bit is
{ auto isno : IsNo (isElem label $ labelsOf xs) } ->
The idea is that if the keys are unique, the compiler will trivialy find an instance of IsNo
while it won't if keys are not unique and therefore fail to compile.
This works well with for those example
("Year" := 1998) +: rnil -- Compiles fine
("Year" := "test") +: ("Year" := 1998) +: rnil -- fails to compile as expected
But
("Title" := "test") +: ("Year" := 1998) +: rnil
fails to compile with the following error:
(input):Type mismatch between
("Title" = "Year") -> "Title" = "Year"
and
("Title" = "Year") -> Void
I must admit that this error baffles me. Can anyone explain what's happening here ?
Upvotes: 0
Views: 89
Reputation: 4586
It seems that you're the first to use the DecEq
instance for String
in anger and as a result, you're the first to find that the way we build proof terms for primitives here is wrong. Sorry about that. The good news is that it's easy to fix (I've just tried it on your example and it's fine), the bad news is that you'll need the git head once I've pushed the fix.
We're overdue a new release anyway. I'll try to do that this weekend. Your code certainly looks fine to me!
Upvotes: 3