Zulip Chat Archive
Stream: lean4
Topic: Should this be added to lean 4?
Joseph O (Feb 23 2022 at 02:30):
I would appreciate feedback or confirmation whether this would be useful to add to lean 4
Arthur Paulino (Feb 23 2022 at 02:33):
A way to know is when you end up needing the same piece of code in 2~3 different contexts.
Example: this is how HashMap.ofList
was born:
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/easy.20HashMap.20instantiation/near/267786491
Chris B (Feb 23 2022 at 02:38):
I think it's generally preferable for that kind of thing on ranges to be a stream/iterator and not a concrete list.
Joseph O (Feb 23 2022 at 02:51):
I have had my need for it. For instance, I don't about everyone else, but I rarely use for loops in lean, and I have had to use Ranges before, piping it into a lot of list methods. I was using List.range n
, but recently learned about the range syntax, and needed a use case with stepping, so I was making a toList
function, and I assume other people would find it useful too
Joseph O (Feb 23 2022 at 02:51):
Ranges in my opinion are meant to generate some sort of existing data structure, notably a list
Joseph O (Feb 23 2022 at 02:53):
HashMap
even has a toList
to allow easy manipulating.
Joseph O (Feb 23 2022 at 16:50):
I would prefer some more opinions before I decide.
Mario Carneiro (Feb 23 2022 at 17:02):
it might help to have actual examples of use
Mario Carneiro (Feb 23 2022 at 17:03):
Most mathlib additions are done because they are dependencies for some other work that either exists in another PR or will be PR'd soon. What's your application?
Joseph O (Feb 23 2022 at 19:29):
Mario Carneiro said:
Most mathlib additions are done because they are dependencies for some other work that either exists in another PR or will be PR'd soon. What's your application?
well this isnt for mathlib4 but lean4. Either way, my application is for usefulness
Mario Carneiro (Feb 23 2022 at 19:30):
that's not an answer
Joseph O (Feb 23 2022 at 19:30):
What is the point of having ranges, that cant be converted for lists. A use case could be working with mod systems, or primes, and filtering out ranges and numbers, which is what I was doing, and found the need for
Mario Carneiro (Feb 23 2022 at 19:31):
could you show a concrete example?
Mario Carneiro (Feb 23 2022 at 19:31):
like a piece of code that you would like to write, that can't be written easily now and would be better with your additions
Mario Carneiro (Feb 23 2022 at 19:31):
that's what I mean by an application
Joseph O (Feb 23 2022 at 19:32):
ok, give me a moment
Joseph O (Feb 23 2022 at 19:49):
very basic example
def foo :=
[28:1000]
|>.toList
|>.map (fun n => [:n] |>.toList |>.filter (n % · = 0))
|>.filter (·.length = 2)
i am unlikely this will convince you. If you want another better example, let me know
Joseph O (Feb 23 2022 at 19:56):
I will get an example with the step feature. But can I ask you something: What was the point of ranges when they can only be looped through, explain that to me. My idea may not be "useful", but the current version is even worse! barely any functionality!
Henrik Böving (Feb 23 2022 at 20:00):
I feel like the for x in [a:b]
is an amazing functionality by itself already, sure it could be improved but it's already very useful when you are writing code in a more imperative style
Joseph O (Feb 23 2022 at 20:06):
Henrik Böving said:
I feel like the
for x in [a:b]
is an amazing functionality by itself already, sure it could be improved but it's already very useful when you are writing code in a more imperative style
How many people here write in an imperative style? Are we going to exclude functional style for ranges just because we feel like it?
Joseph O (Feb 23 2022 at 20:07):
You arent really giving good arguments
Henrik Böving (Feb 23 2022 at 20:09):
I've for example written code with for loops in doc-gen 4, besides I'm not making a point against extending the functionality, I'm merely making a point against "barely any functionality"
Mario Carneiro (Feb 23 2022 at 20:10):
One reason to prefer the for loop over the list operations, at least in current lean 4, is that the latter uses a lot more memory. For loops actually compile roughly to for loops, while list operations also compile to creating big linked lists and consuming them
Mario Carneiro (Feb 23 2022 at 20:12):
But that's not to say that we shouldn't have a Range.toList
, that seems like a simple addition. I don't think you posted an implementation of this anywhere though
Joseph O (Feb 23 2022 at 20:12):
I can do that now if you want
Mario Carneiro (Feb 23 2022 at 20:13):
All of the other functions there exist already, right?
Mario Carneiro (Feb 23 2022 at 20:14):
You can write a toList
function generic over Stream
, although I think you can't prove it terminates in general
Mario Carneiro (Feb 23 2022 at 20:15):
it is a bit easier to translate to an array instead of a list, because you push to the end of an array
Mario Carneiro (Feb 23 2022 at 20:16):
plus arrays have just as much functional API as lists in lean 4
Joseph O (Feb 23 2022 at 20:17):
private def List.range' (start stop : Nat) :=
List.drop start $ List.range stop
private def List.range'' (start stop : Nat) : Nat → List Nat
| 1 => List.range' start stop
| step => (List.range' start stop).filter (fun x => (x = 0) || (x % step = 0))
def toList : Range → List Nat
| { start := m, stop := n, step := o} => List.range'' m n o
#eval [4:1000:5].toList
Joseph O (Feb 23 2022 at 20:17):
Mario Carneiro said:
All of the other functions there exist already, right?
not really
Mario Carneiro (Feb 23 2022 at 20:32):
Hm, if your step size is large then that creates a lot of elements. You can leverage the ForIn
instance to construct the array for you:
def Std.Range.toArray (r : Std.Range) : Array Nat := Id.run do
let mut arr := #[]
for i in r do
arr := arr.push i
pure arr
def Std.Range.toList (r : Std.Range) : List Nat := r.toArray.toList
def foo : List (List Nat) :=
[28:1000]
|>.toList
|>.map (fun n => [:n] |>.toList |>.filter (n % · = 0))
|>.filter (·.length = 2)
Joseph O (Feb 23 2022 at 20:34):
I guess that makes sense
Mario Carneiro (Feb 23 2022 at 20:34):
Note that the indirection through arrays is deliberate. Even if you want to construct a list in the end, it's more efficient to construct the array first and tail-recursively convert it to a list at the end
Joseph O (Feb 23 2022 at 20:35):
I see
Joseph O (Feb 23 2022 at 20:35):
You might as well make the pr now.
Mario Carneiro (Feb 23 2022 at 20:35):
Here's another way to write the toArray
function:
def Std.Range.toArray (r : Std.Range) : Array Nat :=
Id.run <| r.forIn #[] fun i arr => ForInStep.yield (arr.push i)
Mario Carneiro (Feb 23 2022 at 20:35):
it's the same thing but without the do notation sugar
Mario Carneiro (Feb 23 2022 at 20:37):
This is a collaboration not a competition. This is still your PR, there is still documentation and examples, and maybe there are other streams that could also use toArray
or toList
functions
Joseph O (Feb 23 2022 at 20:37):
If you think this is useful to add, you can add it. I am not going to copy someone's code and claim it to be mine
Mario Carneiro (Feb 23 2022 at 20:38):
You do realize that most of mathlib's files have multiple authors, right?
Joseph O (Feb 23 2022 at 20:38):
Yes I do, but I didn't author anything here
Mario Carneiro (Feb 23 2022 at 20:39):
Sure you did, you came up with the idea and wrote a v1 implementation, which makes you first author by mathlib rules
Mario Carneiro (Feb 23 2022 at 20:40):
and like I said this isn't a PR yet
Mario Carneiro (Feb 23 2022 at 20:40):
this is a function
Joseph O (Feb 23 2022 at 20:43):
Hmm. So this will be added to mathlib not lean4?
Mario Carneiro (Feb 23 2022 at 20:43):
Yes, as you can see it works just as well if it's not in core
Mario Carneiro (Feb 23 2022 at 20:44):
lean 4 is designed to be extensible from outside the core distribution
Mario Carneiro (Feb 23 2022 at 20:44):
You also suggested a ToString
and a Repr
instance above. Can you use the s!
macro to write that?
Joseph O (Feb 23 2022 at 20:45):
Mario Carneiro said:
You also suggested a
ToString
and aRepr
instance above. Can you use thes!
macro to write that?
I mean, the instances would have been simple
Joseph O (Feb 23 2022 at 20:45):
to write
Mario Carneiro (Feb 23 2022 at 20:45):
okay, then write them
Mario Carneiro (Feb 23 2022 at 20:45):
you have to start somewhere
Mario Carneiro (Feb 23 2022 at 20:46):
API design is more than you are giving credit for
Mario Carneiro (Feb 23 2022 at 20:47):
for instance, is it printed like [1, 3, 5, ..., 17]
or [1 : 17 : 2]
or [1:17:2]
Mario Carneiro (Feb 23 2022 at 20:48):
the function is easy to write, but there are bikeshedding issues everywhere you look
Joseph O (Feb 23 2022 at 20:53):
Mario Carneiro said:
for instance, is it printed like
[1, 3, 5, ..., 17]
or[1 : 17 : 2]
or[1:17:2]
I was thinking [1:17:2]
Mario Carneiro (Feb 23 2022 at 21:00):
I think you're right. (It should look like the input syntax and have the same style, more or less.) Small PRs like that are generally uncontroversial, and will help you get practice for larger changes
Joseph O (Feb 23 2022 at 21:02):
Im having trouble with repr. I have a repr instance for my tuple type, and it takes a string, but this isnt workng
instance : Repr Range where
reprPrec range := "[" ++ reprPrec range.start 0 ++ ":" ++ reprPrec range.stop 0 ++ ":" ++ reprPrec range.step 0 ++ "]"
Joseph O (Feb 23 2022 at 21:02):
(i tried using the s! macro, but that didnt work)
Mario Carneiro (Feb 23 2022 at 21:04):
is that a MWE? Because if so you are missing an open
Joseph O (Feb 23 2022 at 21:05):
Mario Carneiro said:
is that a MWE? Because if so you are missing an
open
here is a MWE:
namespace Std
instance : Repr Range where
reprPrec range := "[" ++ reprPrec range.start 0 ++ ":" ++ reprPrec range.stop 0 ++ ":" ++ reprPrec range.step 0 ++ "]"
end Std
Mario Carneiro (Feb 23 2022 at 21:07):
If you write
namespace Std
instance : Repr Range where
reprPrec range := _
and put your cursor over the _
you will see that it is expecting a Nat -> Format
, which means it is expecting another argument
Joseph O (Feb 23 2022 at 21:07):
Ah, I got it
Joseph O (Feb 23 2022 at 21:07):
Should the s!
macro work now?
Joseph O (Feb 23 2022 at 21:08):
Let me try
Mario Carneiro (Feb 23 2022 at 21:08):
the second argument is the precedence under which to display the expression by the way. Since this is a bracketed expression, it looks the same regardless of precedence so you can ignore it
Joseph O (Feb 23 2022 at 21:09):
Yeah, it does. Ok, it all works
Mario Carneiro (Feb 23 2022 at 21:09):
also, since it wants a Format
and not a String
you should use f!
instead of s!
Joseph O (Feb 23 2022 at 21:09):
s!
worked though
Mario Carneiro (Feb 23 2022 at 21:10):
Yaël Dillies said:
That makes a good motto: Code that compiles is not code that works.
Joseph O (Feb 23 2022 at 21:10):
And is it ok if I do
#eval [:17]
it evaluates that to [0:17:1]
, or should it just evaluate to [:17]
Mario Carneiro (Feb 23 2022 at 21:10):
I think it should print as [:17]
if possible
Joseph O (Feb 23 2022 at 21:31):
Do you have an idea for a nice way of doing that. I would prefer some verbal context, as to try it out mainly myself, but I don't mind if you share code yourself.
Mario Carneiro (Feb 23 2022 at 22:39):
@Joseph O Do a pattern match on range, with cases for <0, stop, 1>
, <0, stop, step>
, <start, stop, 1>
, <start, stop, step>
Yakov Pechersky (Feb 23 2022 at 23:13):
I think you're right. (It should look like the input syntax and have the same style, more or less.)
Is there a nice way in lean4 to prove that repr roundtrips?
Joseph O (Feb 24 2022 at 01:21):
Mario Carneiro said:
Joseph O Do a pattern match on range, with cases for
<0, stop, 1>
,<0, stop, step>
,<start, stop, 1>
,<start, stop, step>
ok
Joseph O (Feb 24 2022 at 01:22):
Should I make an auxiliary function to do that?
Joseph O (Feb 24 2022 at 01:26):
Mario Carneiro said:
Here's another way to write the
toArray
function:def Std.Range.toArray (r : Std.Range) : Array Nat := Id.run <| r.forIn #[] fun i arr => ForInStep.yield (arr.push i)
Does doing it this way provide any benefit?
Joseph O (Feb 24 2022 at 01:28):
Also, is there anywhere I can learn how to use generators like you used here in lean 4?
Joseph O (Feb 24 2022 at 02:03):
Is it ok to have to Pull requests open at the same time?
Joseph O (Feb 24 2022 at 02:04):
But are you sure something like this shouldn't be added to lean4
itself? Like, why was Hashmap.ofList
deemed good enough to add to the core std, for instance?
Arthur Paulino (Feb 24 2022 at 02:58):
I don't think it's a matter of something being "good enough" or not. If something isn't good enough, it won't make it to mathlib either.
As I said in the issue, it's standard for programming languages to provide easy interfaces to instantiate their own versions of hash maps out of the box
Siddhartha Gadgil (Feb 24 2022 at 04:10):
Just a possibility (following scala design) - one can have a typeclass Seq α
with a toArray
field, with methods like Seq.toList
implemented using this. Also from Seq (α × Nat)
we can generate a HashMap. Range
should then have a Seq
instance. I would be happy to make rough implementations for discussion.
Incidentally I also needed HashMap.fromList
and HashMap.fromArray
.
Mac (Feb 24 2022 at 04:12):
Joseph O said:
very basic example
def foo := [28:1000] |>.toList |>.map (fun n => [:n] |>.toList |>.filter (n % · = 0)) |>.filter (·.length = 2)
i am unlikely this will convince you. If you want another better example, let me know
You might find my WIP lean4-itertools library to be of interest. I was designed exactly to help do this kind of thing.
Joseph O (Feb 24 2022 at 11:46):
Arthur Paulino said:
I don't think it's a matter of something being "good enough" or not. If something isn't good enough, it won't make it to mathlib either.
As I said in the
HashMap.ofList
issue, it's standard for programming languages to provide easy interfaces to instantiate their own versions of hash maps out of the box
Converting ranges to lists are standard in languages as well, in fact, most ranges in langs are lists from the start, just written in shorter notation
Joseph O (Feb 24 2022 at 12:11):
Siddhartha Gadgil said:
Just a possibility (following scala design) - one can have a typeclass
Seq α
with atoArray
field, with methods likeSeq.toList
implemented using this. Also fromSeq (α × Nat)
we can generate a HashMap.Range
should then have aSeq
instance. I would be happy to make rough implementations for discussion.Incidentally I also needed
HashMap.fromList
andHashMap.fromArray
.
Interesting idea. This means that Seq (a x Nat) for example would have a method toArray and generate a hash map?
Siddhartha Gadgil (Feb 24 2022 at 13:48):
That is what I mean. Indeed one can have an instance of Seq a
from an instance of ForIn
at least with the correct parameters (I am struggling to understand what ForIn
means in general, having only used it in imperative for
loops).
Joseph O (Feb 24 2022 at 14:05):
I guess I should add it to mathlib4 for now and it may be added to the lean 4 std?
Yakov Pechersky (Feb 24 2022 at 14:18):
A range in python3 is surely not a list by default. Maybe we're talking past each other. Joseph, when you think about and mention lists, are you just referring to any datatype that has indexing, iteration, a length, appending, insertion, mutation at an index; or do you mean some particular implementation like a linked list, doubly linked list, indexed tree?
Siddhartha Gadgil (Feb 24 2022 at 14:31):
There are probably better ways in lean than the below, but this is what I meant.
universe u
class Iterable (l: Type u → Type u) where
toArray : l α → Array α
instance : Iterable List :=
⟨fun l => l.toArray⟩
instance : Iterable Array :=
⟨fun l => l⟩
def mkArray [it : Iterable l] (l: l α): Array α :=
it.toArray l
def mkList [it : Iterable l] (l: l α): List α :=
(mkArray l).toList
open Std
def mkHashMap [it : Iterable l][Hashable α][BEq α][BEq β](l: l (α × β)): HashMap α β :=
(mkArray l).foldl (fun acc (k, v) => acc.insert k v) HashMap.empty
Yakov Pechersky (Feb 24 2022 at 14:34):
Probably you want mkList and mkHashMap to be fields in the structure that have default implementations, so that one can override. For example, a List's mkList shouldn't need to go through an Array and then to a List
Siddhartha Gadgil (Feb 24 2022 at 14:38):
Thanks @Yakov Pechersky I agree and this is something I am learning now.
Mario Carneiro (Feb 24 2022 at 14:43):
Siddhartha Gadgil said:
That is what I mean. Indeed one can have an instance of
Seq a
from an instance ofForIn
at least with the correct parameters (I am struggling to understand whatForIn
means in general, having only used it in imperativefor
loops).
A ForIn
impl is nothing more or less than the natural requirement for desugaring for
loops. You give it a monadic function which reports whether to update the state or early-return, and it folds the function over the data structure or iterable or what have you.
Mario Carneiro (Feb 24 2022 at 14:45):
It's a bit more general than "iterators" such as you would find in e.g. Rust, because it's not just a next
function that gets called repeatedly, you get the loop body itself and can do funny business like reuse old state values or apply the function a transfinite number of times
Yakov Pechersky (Feb 24 2022 at 14:46):
So like a python generator but with more introspection?
Mario Carneiro (Feb 24 2022 at 14:47):
I don't think imperative languages without a monad abstraction can express this
Mario Carneiro (Feb 24 2022 at 14:48):
Python generators are generalized along a different axis, what with the yield point stuff
Mario Carneiro (Feb 24 2022 at 14:49):
I can imagine do
notation gaining the ability to desugar yield
expressions as well, but generators are not currently supported
Joseph O (Feb 24 2022 at 14:55):
Siddhartha Gadgil said:
There are probably better ways in lean than the below, but this is what I meant.
universe u class Iterable (l: Type u → Type u) where toArray : l α → Array α instance : Iterable List := ⟨fun l => l.toArray⟩ instance : Iterable Array := ⟨fun l => l⟩ def mkArray [it : Iterable l] (l: l α): Array α := it.toArray l def mkList [it : Iterable l] (l: l α): List α := (mkArray l).toList open Std def mkHashMap [it : Iterable l][Hashable α][BEq α][BEq β](l: l (α × β)): HashMap α β := (mkArray l).foldl (fun acc (k, v) => acc.insert k v) HashMap.empty
This is pretty neat.
Joseph O (Feb 24 2022 at 14:56):
Yeah, and ranges are not usually lists by default. In F#, they are sequences
Siddhartha Gadgil (Feb 24 2022 at 14:57):
Thanks. But the above does not take care of Range
, for which it has to be tweaked.
I'll take a shot at this.
Mario Carneiro (Feb 24 2022 at 15:00):
You can implement Iterable
for anything with a suitable Stream
or ForIn
implementation
Sebastian Ullrich (Feb 24 2022 at 15:01):
Mario Carneiro said:
It's a bit more general than "iterators" such as you would find in e.g. Rust, because it's not just a
next
function that gets called repeatedly, you get the loop body itself and can do funny business like reuse old state values or apply the function a transfinite number of times
This is external vs. internal iteration. Internal iteration like ForM
is relatively simple to set up and generate good code for, but limited in other regards - you cannot implement zip
on two internal iterators, for example. I'd love to see a robust external iteration library for Lean in the future.
Mario Carneiro (Feb 24 2022 at 15:01):
Stream
is external iteration, is it not?
Sebastian Ullrich (Feb 24 2022 at 15:04):
Hah yes, I forgot we already have it because it is so barebones and therefore barely used
Sebastian Ullrich (Feb 24 2022 at 15:11):
We would at least need a new typeclass for bounded streams if we want to make them as useful as ForM
Joseph O (Feb 24 2022 at 15:11):
Siddhartha Gadgil said:
Thanks. But the above does not take care of
Range
, for which it has to be tweaked.
I'll take a shot at this.
It shouldn't take to much tweaking
Joseph O (Feb 24 2022 at 15:11):
At least if I think about it
Siddhartha Gadgil (Feb 24 2022 at 15:13):
@Mario Carneiro but does Range
have a suitable ForIn
. A Range
is only an Iterable Nat
so if the only parameter of an Iterable
is the type family I do not see an implementation.
Sebastian Ullrich (Feb 24 2022 at 15:25):
Mario Carneiro said:
Stream
is external iteration, is it not?
There is also an interesting comparison to lazy lists, which have a very similar but simpler interface (which may or may not be a problem for code generation). The big difference is of course that lazy lists are provably finite if not defined coinductively. You can still make them infinite using partial
, you just won't be able to prove anything about them.
Siddhartha Gadgil (Feb 24 2022 at 15:27):
Another take - this is getting more complicated, but includes Range
. I am happy to learn how to simplify.
universe u
class Iterable (l: Type u)(α : Type u) where
toArray : l → Array α
class IterableFamily (l: Type u → Type u) where
toArray : l α → Array α
instance {l: Type u → Type u}[it : IterableFamily l] (α : Type u) : Iterable (l α) α :=
⟨fun x => it.toArray x⟩
instance : IterableFamily List :=
⟨fun l => l.toArray⟩
instance : IterableFamily Array :=
⟨fun l => l⟩
def mkArray {l: Type u → Type u}[it : Iterable (l α) α] (l: l α): Array α :=
it.toArray l
def mkList {l: Type u → Type u}[it : Iterable (l α) α] (l: l α): List α :=
(mkArray l).toList
def mkHashMap {l: Type u → Type u}{α β : Type u}
[it : Iterable (l (α × β)) (α × β )][Hashable α][BEq α][BEq β](l: l (α × β)): HashMap α β :=
(mkArray l).foldl (fun acc (k, v) => acc.insert k v) HashMap.empty
def ForIn.toArray [ForIn Id l α](x : l): (Array α) := Id.run
do
let mut arr := Array.mk []
for a in x do
arr := arr.push a
return arr
instance : Iterable Range Nat :=
⟨fun r => ForIn.toArray r⟩
Joseph O (Feb 24 2022 at 15:53):
interesting
Joseph O (Feb 24 2022 at 15:55):
Do you mind showing an example of using this to convert a Range
to an Array
of some sort?
Arthur Paulino (Feb 24 2022 at 16:01):
One thing I still haven't understood: if you want a list of numbers, why not make a function that creates one directly instead of instantiating a Range and then converting it into a list?
Arthur Paulino (Feb 24 2022 at 16:04):
The use of Range
is too straightforward to me. It supports trivial iterations for for
loops.
If you want data to play with, Range
is not the shortest path
Arthur Paulino (Feb 24 2022 at 16:07):
I'm not on my PC, but I think we already have List.range
, which might serve as an inspiration if you want something more plastic/generic
Siddhartha Gadgil (Feb 24 2022 at 16:08):
The above had a problem because of my switching back and forth. The following version of mkArray
gives an example:
def mkArray [it : Iterable l α] (x: l): Array α :=
it.toArray x
instance : Iterable Range Nat :=
⟨fun r => ForIn.toArray r⟩
def r : Range := [0:3]
def arr : Array Nat := mkArray r
Siddhartha Gadgil (Feb 24 2022 at 16:13):
In cleaned up form, here is the code again. @Arthur Paulino I agree that for Range
this seems to not make sense. May be useful occasionally though.
universe u
class Iterable (l: Type u)(α : Type u) where
toArray : l → Array α
class IterableFamily (l: Type u → Type u) where
toArray : l α → Array α
instance {l: Type u → Type u}[it : IterableFamily l] (α : Type u) : Iterable (l α) α :=
⟨fun x => it.toArray x⟩
instance : IterableFamily List :=
⟨fun l => l.toArray⟩
instance : IterableFamily Array :=
⟨fun l => l⟩
def mkArray [it : Iterable l α] (x: l): Array α :=
it.toArray x
def mkList [it : Iterable l α] (x: l): List α :=
(mkArray x).toList
def mkHashMap
[it : Iterable l (α × β )][Hashable α][BEq α][BEq β](x: l ): HashMap α β :=
let arr : Array (α × β) := mkArray x
arr.foldl (fun acc (k, v) => acc.insert k v) HashMap.empty
def ForIn.toArray [ForIn Id l α](x : l): (Array α) := Id.run
do
let mut arr := Array.mk []
for a in x do
arr := arr.push a
return arr
instance : Iterable Range Nat :=
⟨fun r => ForIn.toArray r⟩
def r : Range := [0:3]
def arr : Array Nat := mkArray r
Mario Carneiro (Feb 24 2022 at 16:15):
Arthur Paulino said:
One thing I still haven't understood: if you want a list of numbers, why not make a function that creates one directly instead of instantiating a Range and then converting it into a list?
There isn't much overhead to creating a Range
, it is just a trivial struct, so I would say that this is just a convenient syntax for calling List.range
Mario Carneiro (Feb 24 2022 at 16:16):
you would need four versions of the function to get the same behavior with direct list functions
Joseph O (Feb 24 2022 at 16:19):
I would say 3 actually
Joseph O (Feb 24 2022 at 16:19):
One for just stop, one for start and stop, and one for start, stop and step
Arthur Paulino (Feb 24 2022 at 16:33):
I suspect there's a solution with just one function and optional parameters with default values
Mario Carneiro (Feb 24 2022 at 16:35):
sure, Range
's constructor itself has default values like that
Mario Carneiro (Feb 24 2022 at 16:36):
but Range
also has fancy syntax which a custom function wouldn't have
Joseph O (Feb 24 2022 at 16:39):
yes
Mario Carneiro (Feb 24 2022 at 16:40):
You can also use @[csimp]
lemmas to rewrite calls to [:n].toList
to List.range n
Gabriel Ebner (Feb 24 2022 at 16:44):
Why would List.range
be any more efficient than Range.toList
? Because of the step size?
Joseph O (Feb 24 2022 at 16:48):
No customizable start of step
Mario Carneiro (Feb 24 2022 at 17:09):
Well, List.range
as it exists today probably isn't that efficient, since it's not tail recursive IIRC. But if we assume that all the functions are given the most efficient implementations given their spec, it would be advantageous to call List.range
when we know at compile time that the step is 1 and the start is 0
Joseph O (Feb 24 2022 at 17:33):
A lot of conversation, yet im not sure we have made a decision yet
Mario Carneiro (Feb 24 2022 at 17:40):
Range.toArray
and Range.toList
are no-brainers (in that the spec is clear and there is no downside to providing the function), everything else is speculative / comes as part of a larger untested infrastructure
Sebastian Ullrich (Feb 24 2022 at 18:13):
Just to give an idea of what I had in mind, this quick draft already generates pretty good code. The only improvements I can see would be to pass the Range
object by value/as separate parameters through the loop (which is a general missing compiler optimization), and to introduce an ExactSizeIterator
on top that allows the array to be created with the correct capacity from the beginning.
def Stream.drop [Stream stream value] (n : Nat) (s : stream) : stream :=
match n with
| 0 => s
| n + 1 => drop n s
class BoundedStream (stream : Type u) (value : outParam (Type v)) extends Stream stream value where
is_bounded : ∀ s, ∃ n, next? (Stream.drop n s) |>.isNone
instance : BoundedStream Std.Range Nat where
is_bounded := sorry
structure Stream.Map (stream : Type u) (value value' : Type v) (f : value → value') where
s : stream
instance [BoundedStream stream value] : BoundedStream (Stream.Map stream value value' f) value' where
next? s := (fun (a, s) => (f a, ⟨s⟩)) <$> Stream.next? s.1
is_bounded := sorry
def Stream.map [Stream stream value] (f : value → value') (s : stream) : Stream.Map stream value value' f := ⟨s⟩
def BoundedStream.toArray [BoundedStream stream value] (s : stream) : Array value :=
go s #[]
where
go s arr := match h : Stream.next? s with
| none => arr
| some (a, s') =>
have : Classical.choose (BoundedStream.is_bounded s') < Classical.choose (BoundedStream.is_bounded s) := by
sorry
go s' (arr.push a)
termination_by go _ => Classical.choose (BoundedStream.is_bounded s)
set_option trace.compiler.ir.result true
#eval BoundedStream.toArray [3:5]
#eval BoundedStream.toArray (Stream.map (· + 1) [3:5])
Those sorries? Oh, nothing to worry, especially not that last one...
Mario Carneiro (Feb 24 2022 at 18:15):
Stream.drop
is the identity function
Mario Carneiro (Feb 24 2022 at 18:16):
probably simpler to define Stream.nth
instead of drop
Joseph O (Feb 24 2022 at 18:22):
Mario Carneiro said:
Range.toArray
andRange.toList
are no-brainers (in that the spec is clear and there is no downside to providing the function), everything else is speculative / comes as part of a larger untested infrastructure
What do you mean by "everything else"? Did I miss something in the conversation?
Mario Carneiro (Feb 24 2022 at 18:22):
I mean all the other proposals and ideas on this thread
Mario Carneiro (Feb 24 2022 at 18:23):
If you are looking for something to PR you can safely ignore them
Mario Carneiro (Feb 24 2022 at 18:27):
@Sebastian Ullrich Setting aside the sketchy Classical.choose (BoundedStream.is_bounded s)
termination measure, I wonder whether it would be advantageous to have something like rust's ExactSizeIterator
: make the bound an explicit part of the structure and use it to pre-seed the size of the array
Mario Carneiro (Feb 24 2022 at 18:28):
Also the bound could depend on the input stream value
Joseph O (Feb 24 2022 at 19:14):
Mario Carneiro said:
If you are looking for something to PR you can safely ignore them
Question is, where should i PR to. Mathlib4 or lean4?
Mario Carneiro (Feb 24 2022 at 19:21):
Mario Carneiro said:
My suggestion is to add to mathlib4, for example here -> https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Data/List/Defs.lean
Joseph O (Feb 24 2022 at 19:34):
Ah I missed that. I was thinking about adding a file called Range.lean
, in Init.Data
, wince the toList
and toArray
methods have to be underst the Std.Range
namespace
Sebastian Ullrich (Feb 24 2022 at 20:03):
Mario Carneiro said:
Sebastian Ullrich Setting aside the sketchy
Classical.choose (BoundedStream.is_bounded s)
termination measure, I wonder whether it would be advantageous to have something like rust'sExactSizeIterator
: make the bound an explicit part of the structure and use it to pre-seed the size of the array
Yes, I mentioned that :) . But it should not be mandatory, you still want toArray
to work on filter
. Good point with drop
though, haha, I'll leave that and fixing the metric as an exercise to the reader.
Jay Sulzberger (Feb 25 2022 at 23:24):
I do not know what Common Lisp's loop is, but I have heard talk of its mysteries.
Jay Sulzberger (Feb 25 2022 at 23:31):
I sent above in answer to Mario Carneiro's remark about the power of LEAN's "iterator" 'for in'.
Mac (Feb 26 2022 at 20:10):
Sebastian Ullrich said:
I'd love to see a robust external iteration library for Lean in the future.
I see that everyone just ignored my post:
Mac said:
You might find my WIP lean4-itertools library to be of interest. I was designed exactly to help do this kind of thing.
It is an external iteration library that is attempting to do much of what is being discussed here. For example:
def foo :=
range 10
|> filter (· % 2 == 0)
|> map (· * 2)
|> array
I would be really curious what people think of my approach to fill this need and how it could be improved.
Sebastian Ullrich (Feb 26 2022 at 21:07):
Am I missing something? I only see ForIn
methods in your library, which is internal iteration
Mac (Feb 26 2022 at 21:28):
@Sebastian Ullrich Huh? Does some research. Oh, sorry! I had never heard of the distinction between internal vs external iteration before (at least with those terms). I thought by "external iteration library" you meant an external library (i.e., one outside the Lean core) for iteration. My apologies for the misunderstanding.
Sebastian Ullrich (Feb 26 2022 at 21:31):
Hah, that indeed was a little ambiguous. I did mention the distinction in the very same comment though... :)
Sebastian Ullrich (Feb 26 2022 at 21:32):
The mentioned ExactSizeIterator
looks like another use case that should be hard to pull off using internal iteration btw
Sebastian Ullrich (Feb 26 2022 at 21:37):
There is also an interesting blog post about Rust switching from internal to external iteration, and then later adding back internal iteration as an optional optimization of external iterators. Some of the described problems are specific to Rust though. https://medium.com/@veedrac/rust-is-slow-and-i-am-the-cure-32facc0fdcb
Mac (Feb 26 2022 at 21:49):
@Sebastian Ullrich I would kind of expect that from an imperative low-level language like Rust. As I see it, external iteration is the natural form of iteration in imperative languages (as it is explicitly stateful) whereas internal iteration is the natural form of iteration for functional languages (as it appears stateless). External iteration is still useful in functional languages, it is just more unnatural (and, depending on the language, can be quite cumbersome).
James Gallicchio (Feb 27 2022 at 01:16):
^ Is there even a good use case for external iterators in Lean? Plenty of functional languages' collections libraries get away with just an internal iterators.
Scala collections, most of the library functions are based on internal iteration but there's a class that all collections implement to expose an external iterator. Might be a valid design for Lean as well to give flexibility when people want it, at the cost of a bit of overhead
James Gallicchio (Feb 27 2022 at 03:22):
Sebastian Ullrich said:
The mentioned
ExactSizeIterator
looks like another use case that should be hard to pull off using internal iteration btw
Also RE: this, some Scala collections have a "sizeHint" interface for mutable buffers, which I think would be fine here. Basically just lets you get a buffer given an expected size to be pre-allocated. This could probably be implemented for Lean's native arrays?
James Gallicchio (Feb 27 2022 at 03:48):
class ExpectedSize (ρ : Type u) where
expectedSize : ρ → Nat
class ToArray (ρ : Type u) (α : outParam (Type v)) where
toArray : ρ → Array α
instance [ForIn Id ρ α] [ExpectedSize ρ] : ToArray ρ α where
toArray r := Id.run do
let mut a := Array.mkEmpty (ExpectedSize.expectedSize r)
for x in r do
a := a.push x
return a
something simple like this?
Last updated: Dec 20 2023 at 11:08 UTC