0
votes

I'm getting a problem where I have a value with type fun a, with fun being a function and a a value which doesn't get computed at type-checking and throws an unification error when I force it to be the result of that function application.

The specific error is this:

When checking right hand side of testRec2 with expected type
         Record [("A", String), ("C", Nat)]

 Type mismatch between
         Record (projectLeft ["A", "C"]
                             [("A", String),
                              ("B", String),
                              ("C", Nat)]) (Type of hProjectByLabels_comp ["A",
                                                                           "C"]
                                                                          testRec1
                                                                          (getYes (isSet ["A",
                                                                                          "C"])))
 and
         Record [("A", String), ("C", Nat)] (Expected type)

 Specifically:
         Type mismatch between
                 projectLeft ["A", "C"]
                             [("A", String), ("B", String), ("C", Nat)]
         and
                 [("A", String), ("C", Nat)]

This is from an implementation of HList-like records in Idris, with the following example:

testRec1 : Record [("A", String), ("B", String), ("C", Nat)]
-- testRec1's value is already defined    

testRec2 : Record [("A", String), ("C", Nat)]
testRec2 = hProjectByLabels_comp ["A", "C"] testRec1 (getYes $ isSet ["A", "C"])

... the following types:

IsSet : List t -> Type

isSet : DecEq t => (xs : List t) -> Dec (IsSet xs)

LabelList : Type -> Type

IsLabelSet : LabelList lty -> Type

HList : LabelList lty -> Type

Record : LabelList lty -> Type

recToHList : Record ts -> HList ts

recLblIsSet : Record ts -> IsLabelSet ts

hListToRec : DecEq lty => {ts : LabelList lty} -> {prf : IsLabelSet ts} -> HList ts -> Record ts

IsProjectLeft : DecEq lty => List lty -> LabelList lty -> LabelList lty -> Type

IsProjectRight : DecEq lty => List lty -> LabelList lty -> LabelList lty -> Type

hProjectByLabelsHList : DecEq lty => {ts : LabelList lty} -> (ls : List lty) -> HList ts -> ((ls1 : LabelList lty ** (HList ls1, IsProjectLeft ls ts ls1)), (ls2 : LabelList lty ** (HList ls2, IsProjectRight ls ts ls2)))

projectLeft : DecEq lty => List lty -> LabelList lty -> LabelList lty

hProjectByLabelsLeftIsSet_Lemma2 : DecEq lty => {ls : List lty} -> {ts1, ts2 : LabelList lty} -> IsProjectLeft ls ts1 ts2 -> IsLabelSet ts1 -> IsLabelSet ts2

fromIsProjectLeftToComp : DecEq lty => {ls : List lty} -> {ts1, ts2 : LabelList lty} -> IsProjectLeft ls ts1 ts2 -> IsSet ls -> ts2 = projectLeft ls ts1

hProjectByLabels_comp : DecEq lty => {ts : LabelList lty} -> (ls : List lty) -> Record ts -> IsSet ls -> Record (projectLeft ls ts)

... and the following (necessary) definitions:

LabelList : Type -> Type
LabelList lty = List (lty, Type)

IsLabelSet : LabelList lty -> Type
IsLabelSet ts = IsSet (map fst ts) 

projectLeft : DecEq lty => List lty -> LabelList lty -> LabelList lty
projectLeft [] ts = []
projectLeft ls [] = []
projectLeft ls ((l,ty) :: ts) with (isElem l ls)
  projectLeft ls ((l,ty) :: ts) | Yes lIsInLs = 
    let delLFromLs = deleteElem ls lIsInLs
        rest = projectLeft delLFromLs ts
    in (l,ty) :: rest
  projectLeft ls ((l,ty) :: ts) | No _ = projectLeft ls ts

deleteElem : (xs : List t) -> Elem x xs -> List t
deleteElem (x :: xs) Here = xs
deleteElem (x :: xs) (There inThere) =
  let rest = deleteElem xs inThere
  in x :: rest 

getYes : (d : Dec p) -> case d of { No _ => (); Yes _ => p}
getYes (No _ ) = ()
getYes (Yes prf) = prf

hProjectByLabels_comp : DecEq lty => {ts : LabelList lty} -> (ls : List lty) -> Record ts -> IsSet ls -> Record (projectLeft ls ts)
hProjectByLabels_comp {ts} ls rec lsIsSet =
  let 
    isLabelSet = recLblIsSet rec
    hs = recToHList rec
    (lsRes ** (hsRes, prjLeftRes)) = fst $ hProjectByLabelsHList ls hs
    isLabelSetRes = hProjectByLabelsLeftIsSet_Lemma2 prjLeftRes isLabelSet
    resIsProjComp = fromIsProjectLeftToComp prjLeftRes lsIsSet
    recRes = hListToRec {prf=isLabelSetRes} hsRes
  in rewrite (sym resIsProjComp) in recRes

Basically, there is a function projectLeft that is applied to 2 lists and returns a new one. The type of hProjectByLabels_comp applies this function at the type-level. To actually construct the resulting list, I have a predicate of the style Pred l1 l2 l3 and a lemma of the style Pred l1 l2 l3 -> l3 = projectLeft l1 l2. In hProjectByLabels_comp I apply the lemma to the predicate and use rewrite to get the correct type-signature (rewriting l3, which is implicit in the predicate that appears inside the implementation, into projectLeft l1 l2, or projectLeft ls ts in this specific case).

I would expect that applying hProjectByLabels_comp to a record will compute projectLeft ls ts correctly. However, in the example above it fails to evaluate/compute projectLeft ["A", "C"] [("A", String), ("B", String), ("C", Nat)]. This seems weird, since evaluating that function application in the REPL gives exactly [("A", String), ("C", Nat)], which is what the type expects, but Idris can't seem to compute this function when type-checking.

I am not sure if the implementation of some of the lemmas/functions has anything to do with this or if it's just something about the types alone.

I tried replicating this error using a simpler example (with predicates and functions on Nats) but that simpler example type-checked correctly, so I couldn't find another way to replicate this error.

I'm using Idris 0.9.20.2

Edit: I tried rewriting projectLeft in the following way to see if anything changed but it keeps showing the same error

projectLeft : DecEq lty => List lty -> LabelList lty -> LabelList lty
projectLeft ls [] = []
projectLeft ls ((l,ty) :: ts) with (isElem l ls)
  projectLeft ls ((l,ty) :: ts) | Yes lIsInLs = 
    let delLFromLs = deleteElem ls lIsInLs
        rest = projectLeft delLFromLs ts
    in (l,ty) :: rest
  projectLeft ls ((l,ty) :: ts) | No _ = projectLeft ls ts
2
I see that testRec1 does not have any value assigned, is that intentional? Otherwise, you can't depend on the computational behaviour of something that has a hole inside.Yet Another Geek
It is intentional. The example assumes the value testRec1 already exists (it has no holes inside).gonzaw
Would it be possible for you to provide a gist to the complete example or use a more minimal one? It is hard to debug without that, since everything else looks fine so far.Yet Another Geek

2 Answers

1
votes

Is projectLeft a total function? Partial functions don't reduce in type signatures, that is, you just see them applied to their arguments, not what the result of that application reduces to.

An example demonstrating this:

type : Int -> Type
type 0 = String

a : type 0
a = "Hello"

will fail to compile with a type error complaining about not being able to match type 0 with String. Despite the function type being defined for the value in question, Idris refuses to apply partial functions in type signatures. You can still apply it in the repl, though. type 0 gives String : Type, and type 1 gives type 1 : Type (unreduced).

1
votes

Apparently this issue is solved now after updating to Idris 0.12. Haven't changed anything but it typechecks now.