Zulip Chat Archive

Stream: lean4

Topic: Joining an Array of `BitVec` into a larger `BitVec`


dxo (Apr 09 2024 at 16:01):

I'm trying to write a function that iterates onver a Subarray of BitVec and appends them all together. I think I have something that should in principle work, but fails to typecheck because (I think) lean4 is unable to convince itself of the equality of syntactically different expressions in various BitVec indicies. Is there a way to help it along / is there a better way to write this?

def joinBytes (bytes : Subarray (BitVec b)) : BitVec (bytes.size * b) :=
  let rec go {m : Nat} (n : Fin (bytes.size + 1)) (res : BitVec m) : BitVec (m + (n * b))
    := if n = (0 : Nat)
       /-
       application type mismatch
         ite (n = ↑0) res
       argument
         res
       has type
         BitVec m : Type
       but is expected to have type
         BitVec (m + ↑n * b) : Type
       -/
       then res
       else go (n - 1) (BitVec.append res (bytes[bytes.size - n]'(by sorry)))
  /-
  type mismatch
    go (Fin.last (Subarray.size bytes)) nil
  has type
    BitVec (0 + ↑(Fin.last (Subarray.size bytes)) * b) : Type
  but is expected to have type
    BitVec (Subarray.size bytes * b) : Type
  -/
  go (Fin.last bytes.size) BitVec.nil

dxo (Apr 09 2024 at 17:30):

ah, I have discovered the operator! this seems to be exactly what I need

Timo Carlin-Burns (Apr 09 2024 at 18:28):

I think you want docs#BitVec.cast instead. Eq.rec (which that operator is notation for) tends to block reduction and be hard to prove equalities about, so we often use type-specific versions

dxo (Apr 09 2024 at 18:30):

oh, nice tip thanks!

dxo (Apr 09 2024 at 18:30):

that cleaned things up a bit

dxo (Apr 09 2024 at 18:31):

I managed to get everything working with , but I'm still struggling with the proof of safe indexing

Eric Wieser (Apr 09 2024 at 18:44):

I think we have a version of this in mathlib called docs#finPiFinEquiv

Eric Wieser (Apr 09 2024 at 18:45):

(though to use it you need to pull the sum through the exponent via a cast)

dxo (Apr 09 2024 at 18:55):

Finally have a version that type checks:

def joinBytes (bytes : Subarray (BitVec b)) : BitVec (bytes.size * b) :=
  BitVec.cast (by simp) $ go bytes.size (by rfl) BitVec.nil
  where
    go {m : } (n : Nat) (thm : n <= bytes.size) (res : BitVec m) : BitVec (m + (n * b))
    := match n with
    | 0 => BitVec.cast (by simp) res
    | Nat.succ x
      => let n := Nat.succ x
         have add_thm : m + b + (x * b) = m + Nat.succ x * b := by
           simp [Nat.succ_mul, Nat.add_assoc]
           simp [Nat.add_comm]
         have idx_thm : bytes.size - n < bytes.size := by
           have npos : 0 < n := by simp
           have szpos : 0 < bytes.size := Nat.lt_of_lt_of_le npos thm
           apply Nat.sub_lt
           apply szpos
           assumption
         let next := BitVec.append res (bytes[bytes.size - n]'(idx_thm))
         BitVec.cast add_thm $ go x (Nat.le_of_succ_le thm) next

dxo (Apr 09 2024 at 18:56):

Eric Wieser said:

I think we have a version of this in mathlib called docs#finPiFinEquiv

Interesting! Thank you. I got rid of the Fin in the end, it was kinda too much to think about :grinning:. Maybe I'll try again now I have a working version with nats and evidence passing.

Eric Wieser said:

(though to use it you need to pull the sum through the exponent via a cast)

I don't understand this sentance at all I'm afraid :sweat_smile:

dxo (Apr 10 2024 at 23:05):

Figured out a much cleaner version based on a dependent fold:

import Mathlib.Data.Vector
open Vector
open Nat

def dfoldr {α : Type}
           : {sz : }
            (tp :   Type)
            (step : (idx : )  α  tp idx  tp (idx + 1))
            (init : tp 0)
            Vector α sz
            tp sz
| 0, _, _, base, Vector.nil => base
| n + 1, p, step, base, v => step n (head v) (dfoldr p step base (tail v))


def joinBytes (bytes : Vector (BitVec b) sz) : BitVec (sz * b) :=
  have cast_thm {i} : b + i * b = (i + 1) * b := by rw [add_comm, mul_comm, mul_succ, mul_comm]
  dfoldr
    (λ i => BitVec (i * b))
    (λ i val acc => BitVec.cast cast_thm (BitVec.append val acc))
    (BitVec.cast (by simp) BitVec.nil)
    bytes

dxo (Apr 10 2024 at 23:13):

I couldn't find dfoldr or something like it anywhere in Init / Std / Mathlib.Am I missing it somewhere? joinBytes was so much easier to write once I understood how to define dfoldr.

Kim Morrison (Apr 17 2024 at 03:22):

@dxo, I have a PR about this at https://github.com/leanprover/lean4/pull/3727. It's currently a bit abandoned, but if you wanted to review it / suggest changes, etc, perhaps we can resume progress on it.


Last updated: May 02 2025 at 03:31 UTC