Zulip Chat Archive

Stream: general

Topic: A little project of mine


Joseph O (Mar 21 2022 at 18:28):

hello, I want to share a little project I did in lean 4. Its nowhere near complete, nor do i plan on finishing it. It has some useful elements, that could be used in a project, like heterogeneous lists, range list syntax, accessing nth element of product; but in truth this was just a project to get some experience in lean 4, and I think it did just that. I also want to thank everyone who helped me on the zulip. This used to be lean4-Tuple, but now it is much more. Here is the repo: https://github.com/crabbo-rave/Soup

Joseph O (Mar 21 2022 at 18:29):

I wouldn't mind any feadback

Arthur Paulino (Mar 21 2022 at 18:34):

Nice! Good learning experience. I'm glad to see your questions and the problems you face motivating some improvements on Lean 4. The piece of feedback that I have, just from looking at the README, is that you ate up multiple syntax variations to do the same thing here:

#eval 1,"2",3.0⟫.nth 1 -- "2"
#eval 1,"2",3.0⟫#1
#eval 1,"2",3.0⟫@1
#eval 1,"2",3.0[1]
#eval 1,"2",3.0::1
#eval 1,"2",3.0⟫∧1

It's usually better to standardize an unique notation to avoid coding styles hell

Joseph O (Mar 21 2022 at 18:35):

Oh yeah, the readme is very old

Joseph O (Mar 21 2022 at 18:35):

The syntax has changed. It just uses brackets now.

Joseph O (Mar 21 2022 at 18:36):

I updated it

Joseph O (Mar 21 2022 at 18:39):

Arthur Paulino said:

Nice! Good learning experience. I'm glad to see your questions and the problems you face motivating some improvements on Lean 4. The piece of feedback that I have, just from looking at the README, is that you ate up multiple syntax variations to do the same thing here:

#eval 1,"2",3.0⟫.nth 1 -- "2"
#eval 1,"2",3.0⟫#1
#eval 1,"2",3.0⟫@1
#eval 1,"2",3.0[1]
#eval 1,"2",3.0::1
#eval 1,"2",3.0⟫∧1

It's usually better to standardize an unique notation to avoid coding styles hell

Now i only have 3

notation:90 p "#" i => nth p #i
notation:90 p "@" i => nth p #i
notation:90 p "[" i "]" => nth p #i

Just for different programmers

Arthur Paulino (Mar 21 2022 at 18:51):

Languages that allow the creation of your own syntax/macros are usually feared because "Just for different programmers" can turn into "Just for different programmers not to understand each other". Taking Lean 4 source code as an example, I like that #[...] is the way to represent arrays and that I don't need to know other syntax to recognize them

Arthur Paulino (Mar 21 2022 at 18:57):

I don't mean to sound blunt though :sweat_smile:
It's your creation afterall, your library. I'm just expressing a worry that would come to my mind if I were to use it as a dependency

Joseph O (Mar 21 2022 at 19:06):

I see. Which do you suggest I stick to?

Arthur Paulino (Mar 21 2022 at 19:10):

That's totally up to you :smiley:

Joseph O (Mar 21 2022 at 19:12):

@ is pretty neat

Mario Carneiro (Mar 21 2022 at 22:16):

I would suggest only l[i] if you want a notation like this, and you should use getOp which magically enables this notation

Mario Carneiro (Mar 21 2022 at 22:17):

the reason is that, like Arthur says, there is importance in consistency across the language to help people learn things

Mario Carneiro (Mar 21 2022 at 22:18):

and this syntax is already used for getting elements of an array

Joseph O (Mar 22 2022 at 00:27):

Mario Carneiro said:

I would suggest only l[i] if you want a notation like this, and you should use getOp which magically enables this notation

What is getOp?

Arthur Paulino (Mar 22 2022 at 00:58):

Example. Apparently defining/implementing a getOp function in the namespace of your type allows you to do [i] on the terms of your type automagically:

inductive MyType | hi

def MyType.getOp (self : MyType) (idx : Nat) : Nat := idx

#eval MyType.hi[2] -- 2
#eval MyType.hi[3] -- 3

Joseph O (Mar 22 2022 at 01:04):

Arthur Paulino said:

Example. Apparently defining/implementing a getOp function in the namespace of your type allows you to do [i] on the terms of your type automagically:

inductive MyType | hi

def MyType.getOp (self : MyType) (idx : Nat) : Nat := idx

#eval MyType.hi[2] -- 2
#eval MyType.hi[3] -- 3

That wont really work for me tbh

Joseph O (Mar 22 2022 at 01:04):

Since i take in Fin' rather than Nat

Joseph O (Mar 22 2022 at 01:05):

so they would have to do h[#i], but my notation abstracts that

notation:90 p "[" i "]" => nth p #i

Mario Carneiro (Mar 22 2022 at 02:13):

What if they have a Fin' that they want to pass in though? I'm not sure hiding the # is a good idea

Tomas Skrivan (Mar 22 2022 at 09:22):

The proof by decide inside of # might not work if you do a little bit more complicated operations on indices. As an experiment, try to implement bubble sort with two for loops(not with recursion), would you run into a problem or not?

This might not apply to heterogenous lists, but for homogenous ones it is really nice to have i in Fin n. I often write a sum of all elements in a list: sum fun i => a[i]. In you notation l, i would be Nat and sum would need range specified. As I said, the use case for heterogeneous lists might be completely different and this might not apply.

Joseph O (Mar 23 2022 at 13:48):

Mario Carneiro said:

What if they have a Fin' that they want to pass in though? I'm not sure hiding the # is a good idea

Hmm.

Tomas Skrivan (Mar 24 2022 at 21:50):

@Joseph O For quite some time I wanted some mechanism for n-fold currying and uncurrying. For example, turning f : Nat -> Int -> (Unit -> Nat) -> Nat to fun x => f x[0] x[1] x[2] of type HList [Nat,Int,Unit->Nat] -> Nat. I got inspired and motivated by your project, so I finally did it.

I has to do some tricky typeclasses but I have a function huncurry n that uncurries n arguments:

example : huncurry 3 (λ (i j k : Nat) => i + j)
          =
          λ xs => xs[#0] + xs[#1] :=
by rfl

example : huncurry 2 (λ (i j k : Nat) => i + j)
          =
          λ xs k => xs[#0] + xs[#1] :=
by  rfl

What I particularly like is that the above is solved by rfl, so writing huncurry 3 f is identical to λ xs => f xs[#0] xs[#1] xs[#2]

The full code(have skipped most of the proofs):

structure HArray (Ts : List Type) where
  data : Array (Sigma (λ T : Type => T))
  h_len : Ts.length = data.size
  typed :  i : Fin Ts.length, (data.get (h_len  i)).1 = Ts.get i

namespace HArray

  variable {n} {Ts : List Type}

  def get (u : HArray Ts) (i : Fin Ts.length) : Ts.get i
    := u.typed i  (u.data.get (u.h_len  i)).2

  def getOp (self : HArray Ts) (idx : Fin Ts.length) : Ts.get idx
    := self.typed idx  (self.data.get (self.h_len  idx)).2

  def set (u : HArray Ts) (i : Fin Ts.length) (x : Ts.get i) : HArray Ts
    := u.data.set (u.h_len  i) (⟨_, x⟩), sorry, sorry

end HArray

class HCurryType (n : Nat) (F : Type) where
  Xs : List Type
  Y  : Type

attribute [reducible] HCurryType.Xs HCurryType.Y

@[reducible]
instance : HCurryType 0 Y where
  Xs := []
  Y := Y

@[reducible]
instance {X Y : Type} [t : HCurryType n Y] : HCurryType (n + 1) (X  Y) where
  Xs := X::t.Xs
  Y := t.Y

class HCurry (i : Nat) (Xs' Xs : List Type) (Y : Type) where
  index_valid : Xs'.length + i = Xs.length
  types_valid :  j, i + j < Xs.length  Xs'.get j, sorry = Xs.get i + j, sorry
  F : Type
  uncurry : F  (HArray Xs  Y)

attribute [reducible] HCurry.F HCurry.uncurry

@[reducible]
instance (Xs : List Type) (Y : Type) : HCurry n [] Xs Y where
  index_valid := sorry
  types_valid := sorry
  F := Y
  uncurry := λ y xs => y

@[reducible]
instance [c : HCurry (i+1) (Xs') Xs Y] : HCurry (i) (X'::Xs') Xs Y where
  index_valid := sorry
  types_valid := sorry
  F := X'  c.F
  uncurry := λ f xs =>
    let h : (Xs.get i,sorry = X') := sorry
    let xi : X' := (h  xs[⟨i,sorry⟩])
    c.uncurry (f xi) xs

def huncurry (n : Nat) {F : Type} [HCurryType n F]
  [ci : HCurry 0 (HCurryType.Xs n F) (HCurryType.Xs n F) (HCurryType.Y n F)]
  (f : F) :=
    let h : F = ci.F := sorry
    ci.uncurry (h  f)

macro:max "#" noWs t:term : term => `(⟨$t, by decide⟩)

example : huncurry 3 (λ (i j k : Nat) => i + j)
          =
          λ xs => xs[#0] + xs[#1] :=
by  rfl

example : huncurry 2 (λ (i j k : Nat) => i + j)
          =
          λ xs k => xs[#0] + xs[#1] :=
by rfl

Patrick Massot (Mar 24 2022 at 21:54):

Do you know about docs#function.has_uncurry in Lean 3?

Tomas Skrivan (Mar 24 2022 at 22:01):

Patrick Massot said:

Do you know about docs#function.has_uncurry in Lean 3?

Now I do :) thanks. I guess I did it partially as an exercise, but I will look into mathlib's implementation to see how it is done there.

Tomas Skrivan (Mar 24 2022 at 22:08):

One of my motivation was that I wanted to end up with fun x => f x[0] x[1] x[2] instead of fun x => f x.1 x.2.1 x.2.2.

Eric Wieser (Mar 25 2022 at 11:13):

I find the docstring for has_uncurry very confusing, as it uses alpha/beta/gamma in a totally different way to the lean code it describes.

Joseph O (Apr 22 2022 at 23:26):

@Tomas Skrivan wow looks nice. Glad to hear you got inspired, though the code is perhaps a bit too complicated for me to understand yet. I hope to get there soon.

Joseph O (Apr 22 2022 at 23:28):

Mario Carneiro said:

What if they have a Fin' that they want to pass in though? I'm not sure hiding the # is a good idea

ok I implemented getOp instead now.
The current way to access the index is

#eval [1,"2",3.0][#1]

Last updated: Dec 20 2023 at 11:08 UTC