Zulip Chat Archive
Stream: lean4
Topic: Confusion about coercions
Ioannis Konstantoulas (Jul 12 2023 at 09:14):
In the Functional Programming book, we encounter a coercion from NonEmptyList
to List
:
instance : Coe (NonEmptyList α) (List α) where
coe
| { head := x, tail := xs } => x :: xs
and a claim that 'This allows non-empty lists to be used with the entire List API.' I am not sure what this entails; for instance, I am trying to append two NonEmptyList
s and it fails:
def myNeList : NonEmptyList String := ["hello", "my"]
def mySnList : NonEmptyList String := List.append myNeList ["world"]
results in
type mismatch
List.append ?m.1468 ["world"]
has type
List String : Type
but is expected to have type
NonEmptyList String : Type
What am I doing wrong here? Ultimately, I just want to be able to do
def concList : NonEmptyList String := myNeList ++ mySnList
Ruben Van de Velde (Jul 12 2023 at 09:24):
Well, you're successfully calling List.append
and this creates a new List
. At this point, lean says "hold on, is that new List
really nonempty?" - and this is indeed something you need to prove
Kyle Miller (Jul 12 2023 at 09:27):
Yeah, it means you can use NonEmptyList
wherever a List
is expected, but it doesn't mean you can use a List
wherever a NonEmptyList
is expected.
Arthur Adjedj (Jul 12 2023 at 09:29):
Coercions allow you to use NonEmptyList
s where a List
would be expected, but it only goes one way. For example, this definition doesn't give an error:
def myNeList : List String := ({head := "hello", tail := ["my"]} : NonEmptyList String)
If you wanted to do things the other way around, you would need to have a coercion from List
to NonEmptyList
. This, however, is not possible since you'd also need to coerce empty lists into a nonempty one. If you want to go deeper, there is a way to have some form of such coercion using a dependent coe, called CoeDep
:
instance : CoeDep (List α) (x::l) (NonEmptyList α) := ⟨{head := x, tail := l}⟩
Using this, any list that has a term on the head will be able to coerce to a NonEmptyList
. This makes your first definition myNeList
work. However, it's not so obvious to Lean that your second list mySnList
has such a shape, and as such, you'll still get an error on it.
Kyle Miller (Jul 12 2023 at 09:33):
You could define your own constructor that tries to prove nonemptiness automatically using simp
:
structure NonEmptyList (α : Type) : Type where
head : α
tail : List α
instance : Coe (NonEmptyList α) (List α) where
coe
| { head := x, tail := xs } => x :: xs
def NonEmptyList.ofList (xs : List α) (nonempty : xs ≠ [] := by simp [*]) : NonEmptyList α where
head := xs.head nonempty
tail := xs.tail
@[simp]
def NonEmptyList.coe_ofList (xs : List α) (nonempty: xs ≠ []) :
(NonEmptyList.ofList xs : List α) = xs := by
cases xs
· simp at nonempty
· simp [ofList]
def myNeList : NonEmptyList String := .ofList ["hello", "my"]
def mySnList : NonEmptyList String := .ofList <| List.append myNeList ["world"]
def myNeListOfNeList (neList : NonEmptyList α) : NonEmptyList α := .ofList neList
Ioannis Konstantoulas (Jul 12 2023 at 11:25):
My problem with all this is that the Functional Programming book is using the above code in the Applicative section:
inductive Validate (ε α : Type) : Type where
| ok : α → Validate ε α
| errors : NonEmptyList ε → Validate ε α
instance : Coe (NonEmptyList α) (List α) where
coe
| { head := x, tail := xs } => x :: xs
instance : CoeDep (List α) (x :: xs) (NonEmptyList α) where
coe := { head := x, tail := xs }
instance : Applicative (Validate ε) where
pure := .ok
seq f x :=
match f with
| .ok g => g <$> (x ())
| .errors e =>
match x () with
| .ok _ => .errors e
| .errors e' =>
.errors (e ++ e') -- NonEmptyList coercion fails to concatenate
This code is directly from that book and it fails. I am not sure what I am missing, since the definitions and instances regarding NonEmptyList
are scattered throughout the text.
Ioannis Konstantoulas (Jul 12 2023 at 21:03):
I wrote an implementation of HAppend
for non-empty lists, but the error in the above code persists :(
Arthur Adjedj (Jul 12 2023 at 21:16):
This works for me:
structure NonEmptyList (α : Type) : Type where
head : α
tail : List α
inductive Validate (ε α : Type) : Type where
| ok : α → Validate ε α
| errors : NonEmptyList ε → Validate ε α
instance : Functor (Validate α):= sorry
instance : Coe (NonEmptyList α) (List α) where
coe
| { head := x, tail := xs } => x :: xs
instance : CoeDep (List α) (x :: xs) (NonEmptyList α) where
coe := { head := x, tail := xs }
instance : Append (NonEmptyList α) where
append := λ ⟨x,a⟩ b => {head := x, tail := a ++ b}
instance : Applicative (Validate ε) where
pure := .ok
seq f x :=
match f with
| .ok g => g <$> (x ())
| .errors e =>
match x () with
| .ok _ => .errors e
| .errors e' =>
.errors (e ++ e')
Arthur Adjedj (Jul 12 2023 at 21:22):
but you're right, it's unfortunate that code coming straigth from the book doesn't work
Arthur Adjedj (Jul 13 2023 at 11:43):
Looking back at this, I'm having a hard time accepting that this shouldn't work:
structure NonEmptyList (α : Type _) : Type _ where
head : α
tail : List α
inductive Validate (ε α : Type _) : Type _ where
| ok : α → Validate ε α
| errors : NonEmptyList ε → Validate ε α
instance : Functor (Validate α):= sorry
instance : Coe (NonEmptyList α) (List α) where
coe
| { head := x, tail := xs } => x :: xs
instance : HAppend (List α) (NonEmptyList α) (NonEmptyList α) where
hAppend := λ a b => match a with
| [] => b
| x::a => {head := x, tail := a ++ b}
instance : Applicative (Validate ε) where
pure := .ok
seq f x :=
match f with
| .ok g => g <$> (x ())
| .errors e =>
match x () with
| .ok _ => .errors e
| .errors e' =>
.errors ((e : List ε) ++ e')
^^
/-has type
List ε : outParam (Type ?u.1962)
but is expected to have type
NonEmptyList ε : Type ?u.1962-/
Looking into the synthInstance
s, it looks to me like the outParam
of HAppend
is decided only upon the first argument (of type List ε
), and thus decides this is HAppend (List ε) (List ε) (List ε)
, before trying to coerce e'
into a List. I would naively expect the outParam
to take all "non-out" parameters into account before being decided upon. Is this an intended behaviour ? Marking foo
as a default_instance
doesn't solve the issue either.
Sebastian Ullrich (Jul 13 2023 at 12:05):
This kind of ambiguous input regarding order of coercions and heterogeneous operations is fundamentally problematic. The heuristic ++
uses to insert coercions fails in this case. https://github.com/leanprover/lean4/blob/a3ebfe29ea516e45dba97dfd303cfc11cc47ff04/src/Lean/Elab/Extra.lean#L91-L96
Kyle Miller (Jul 13 2023 at 12:27):
I wouldn't suggest to turn off the binop%
elaborator here, but in case it's illuminating here's how you do it, and then Lean finds the HAppend
instance:
macro_rules | `($x ++ $y) => `(HAppend.hAppend $x $y)
instance : Applicative (Validate ε) where
pure := .ok
seq f x :=
match f with
| .ok g => g <$> (x ())
| .errors e =>
match x () with
| .ok _ => .errors e
| .errors e' =>
.errors ((e : List ε) ++ e')
David Thrane Christiansen (Jul 13 2023 at 12:30):
Ioannis Konstantoulas said:
This code is directly from that book and it fails. I am not sure what I am missing, since the definitions and instances regarding
NonEmptyList
are scattered throughout the text.
This is definitely a drawback of the style!
The code examples that the book's build process draws from are available in its repository. The Append
instance is here: https://github.com/leanprover/fp-lean/blob/56422d4a279d6a74dcf6dc1f9eaa84227ea81f0c/examples/Examples/Classes.lean#L1024-L1026
All the book declaration
and expect info
lines can be ignored - they're part of how the code was extracted.
It would be a nice addition to the book to write a program to extract more readable source to all examples, eliminating the ones with errors.
Arthur Adjedj (Jul 13 2023 at 12:40):
David Thrane Christiansen said:
Ioannis Konstantoulas said:
This code is directly from that book and it fails. I am not sure what I am missing, since the definitions and instances regarding
NonEmptyList
are scattered throughout the text.This is definitely a drawback of the style!
The code examples that the book's build process draws from are available in its repository. The
Append
instance is here: https://github.com/leanprover/fp-lean/blob/56422d4a279d6a74dcf6dc1f9eaa84227ea81f0c/examples/Examples/Classes.lean#L1024-L1026All the
book declaration
andexpect info
lines can be ignored - they're part of how the code was extracted.It would be a nice addition to the book to write a program to extract more readable source to all examples, eliminating the ones with errors.
I believe @Ioannis Konstantoulas may have been lead by the exercise given at chapter 4.5 into believing it would make things work, seeing this message:
Ioannis Konstantoulas said:
I wrote an implementation of
HAppend
for non-empty lists, but the error in the above code persists :(
Perhaps it may help to have one other exercise before the HAppend one to simply prove Append (NonEmptyList α)
? This would be enough to make the code from the book "work"
David Thrane Christiansen (Jul 16 2023 at 08:47):
Good idea! I'm away from the computer for a bit - would you mind making an issue here? https://github.com/leanprover/fp-lean/issues
Last updated: Dec 20 2023 at 11:08 UTC