Julien Tournay
Julien Tournay

Reputation: 544

Compilation error while implementing extensible record

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

Answers (1)

Edwin Brady
Edwin Brady

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

Related Questions