@Kha I had some unexpected surprises, but it is a good change.
Here is the summary.
1- We could get rid of `a %ₙ b` and `ModN` class. We can use `HMod`
instead. It was a positive surprise since I didn't remember we had
this `ModN` class.
2- Coercions are never used in heterogeneous operators. This is
expected since `a * b` is now notation for `HMul.hMul a b`, and
`a` and `b` may have different types. I manually added instances such
as `HMul Nat Int Int`. However, I did not try to add generic instances
such as
```
instance [Coe a b] [Mul b] : HMul a b b where
hMul x y := mul (coe x) y
```
I will try later.
3- Give `h : cs.size > 0`, I got a type error at
```
let idx : Fin cs.size := ⟨cs.size - 1, Nat.predLt h⟩
```
`Nat.predLt h` has type `Nat.pred cs.size < cs.size`
However, `Nat.pred cs.size` doesn't unify with `cs.size - 1`.
The problem is that we can't synthesize the `HSub` instance until
we apply the default instances.
It worked before because `isDefEq` would force the pending TC
problem `Sub Nat` to be resolved, and after that we would be able
to reduce `cs.size - 1` and establish that it is definitionally
equal to `Nat.pred cs.size`.
I considered two possible workarounds
a) `let idx : Fin cs.size := ⟨cs.size - (1:Nat), Nat.predLt h⟩`
b) `let idx : Fin cs.size := ⟨cs.size - 1, by exact Nat.predLt h⟩`
The first one works because we are not providing enough information
for synthesizing the `HSub` instance. The second works because it
postpones the elaboration of `Nat.predLt h`. The default instances
will be applied before we start applying tactics.
4- The `.` notation is affected too. For example, `(x + 1).toUInt8`
doesn't work since we don't know the type of `x+1` until we apply
default instances. I fixed it by using `(x + (1:Nat)).toUInt8`.
Another possible fix is `Nat.toUInt8 (x + 1)`.
Similarly, `(x+1).fold ...` doesn't work.
5- The following code failed to be elaborated
```
indent (push s!"{ss'}\n") (some (0 - Format.getIndent (← getOptions)))
```
It was working before, but it relied on how the expected type is
propagated. The elaborator process
```
some (0 - Format.getIndent (← getOptions))
```
with expected type `(Option Int)`. So, the `-` is interpreted as
`Int.sub` although `Format.getIndent (← getOptions)` has type `Nat`.
In the new `HSub`, the expected type doesn't really influence TC
resolution since it is an `outparam`. So, we failed with the error
failed to synthesize `HSub Nat Nat Int`.
One possible fix was to add the instance `HSub Nat Nat Int` with
`Int.sub`, but I used the following fix
```
some ((0 : Int) - Format.getIndent (← getOptions))
```
which makes it clear that we want the `Int.sub` operator instead of
`Nat.sub`.
167 lines
5.9 KiB
Text
167 lines
5.9 KiB
Text
/-
|
||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Author: Leonardo de Moura
|
||
-/
|
||
namespace Std
|
||
universes u v w
|
||
|
||
def HashSetBucket (α : Type u) :=
|
||
{ b : Array (List α) // b.size > 0 }
|
||
|
||
def HashSetBucket.update {α : Type u} (data : HashSetBucket α) (i : USize) (d : List α) (h : i.toNat < data.val.size) : HashSetBucket α :=
|
||
⟨ data.val.uset i d h,
|
||
by erw [Array.sizeSetEq]; exact data.property ⟩
|
||
|
||
structure HashSetImp (α : Type u) where
|
||
size : Nat
|
||
buckets : HashSetBucket α
|
||
|
||
def mkHashSetImp {α : Type u} (nbuckets := 8) : HashSetImp α :=
|
||
let n := if nbuckets = 0 then 8 else nbuckets
|
||
{ size := 0,
|
||
buckets :=
|
||
⟨ mkArray n [],
|
||
by rw [Array.sizeMkArrayEq]; cases nbuckets; decide!; apply Nat.zeroLtSucc ⟩ }
|
||
|
||
namespace HashSetImp
|
||
variables {α : Type u}
|
||
|
||
def mkIdx {n : Nat} (h : n > 0) (u : USize) : { u : USize // u.toNat < n } :=
|
||
⟨u % n, USize.modnLt _ h⟩
|
||
|
||
@[inline] def reinsertAux (hashFn : α → USize) (data : HashSetBucket α) (a : α) : HashSetBucket α :=
|
||
let ⟨i, h⟩ := mkIdx data.property (hashFn a)
|
||
data.update i (a :: data.val.uget i h) h
|
||
|
||
@[inline] def foldBucketsM {δ : Type w} {m : Type w → Type w} [Monad m] (data : HashSetBucket α) (d : δ) (f : δ → α → m δ) : m δ :=
|
||
data.val.foldlM (init := d) fun d as => as.foldlM f d
|
||
|
||
@[inline] def foldBuckets {δ : Type w} (data : HashSetBucket α) (d : δ) (f : δ → α → δ) : δ :=
|
||
Id.run $ foldBucketsM data d f
|
||
|
||
@[inline] def foldM {δ : Type w} {m : Type w → Type w} [Monad m] (f : δ → α → m δ) (d : δ) (h : HashSetImp α) : m δ :=
|
||
foldBucketsM h.buckets d f
|
||
|
||
@[inline] def fold {δ : Type w} (f : δ → α → δ) (d : δ) (m : HashSetImp α) : δ :=
|
||
foldBuckets m.buckets d f
|
||
|
||
def find? [BEq α] [Hashable α] (m : HashSetImp α) (a : α) : Option α :=
|
||
match m with
|
||
| ⟨_, buckets⟩ =>
|
||
let ⟨i, h⟩ := mkIdx buckets.property (hash a)
|
||
(buckets.val.uget i h).find? (fun a' => a == a')
|
||
|
||
def contains [BEq α] [Hashable α] (m : HashSetImp α) (a : α) : Bool :=
|
||
match m with
|
||
| ⟨_, buckets⟩ =>
|
||
let ⟨i, h⟩ := mkIdx buckets.property (hash a)
|
||
(buckets.val.uget i h).contains a
|
||
|
||
-- TODO: remove `partial` by using well-founded recursion
|
||
partial def moveEntries [Hashable α] (i : Nat) (source : Array (List α)) (target : HashSetBucket α) : HashSetBucket α :=
|
||
if h : i < source.size then
|
||
let idx : Fin source.size := ⟨i, h⟩
|
||
let es : List α := source.get idx
|
||
-- We remove `es` from `source` to make sure we can reuse its memory cells when performing es.foldl
|
||
let source := source.set idx []
|
||
let target := es.foldl (reinsertAux hash) target
|
||
moveEntries (i+1) source target
|
||
else target
|
||
|
||
def expand [Hashable α] (size : Nat) (buckets : HashSetBucket α) : HashSetImp α :=
|
||
let nbuckets := buckets.val.size * 2
|
||
have nbuckets > 0 from Nat.mulPos buckets.property (decide! : 2 > 0)
|
||
let new_buckets : HashSetBucket α := ⟨mkArray nbuckets [], by rw [Array.sizeMkArrayEq]; assumption⟩
|
||
{ size := size,
|
||
buckets := moveEntries 0 buckets.val new_buckets }
|
||
|
||
def insert [BEq α] [Hashable α] (m : HashSetImp α) (a : α) : HashSetImp α :=
|
||
match m with
|
||
| ⟨size, buckets⟩ =>
|
||
let ⟨i, h⟩ := mkIdx buckets.property (hash a)
|
||
let bkt := buckets.val.uget i h
|
||
if bkt.contains a
|
||
then ⟨size, buckets.update i (bkt.replace a a) h⟩
|
||
else
|
||
let size' := size + 1
|
||
let buckets' := buckets.update i (a :: bkt) h
|
||
if size' ≤ buckets.val.size
|
||
then { size := size', buckets := buckets' }
|
||
else expand size' buckets'
|
||
|
||
def erase [BEq α] [Hashable α] (m : HashSetImp α) (a : α) : HashSetImp α :=
|
||
match m with
|
||
| ⟨ size, buckets ⟩ =>
|
||
let ⟨i, h⟩ := mkIdx buckets.property (hash a)
|
||
let bkt := buckets.val.uget i h
|
||
if bkt.contains a then ⟨size - 1, buckets.update i (bkt.erase a) h⟩
|
||
else m
|
||
|
||
inductive WellFormed [BEq α] [Hashable α] : HashSetImp α → Prop where
|
||
| mkWff : ∀ n, WellFormed (mkHashSetImp n)
|
||
| insertWff : ∀ m a, WellFormed m → WellFormed (insert m a)
|
||
| eraseWff : ∀ m a, WellFormed m → WellFormed (erase m a)
|
||
|
||
end HashSetImp
|
||
|
||
def HashSet (α : Type u) [BEq α] [Hashable α] :=
|
||
{ m : HashSetImp α // m.WellFormed }
|
||
|
||
open HashSetImp
|
||
|
||
def mkHashSet {α : Type u} [BEq α] [Hashable α] (nbuckets := 8) : HashSet α :=
|
||
⟨ mkHashSetImp nbuckets, WellFormed.mkWff nbuckets ⟩
|
||
|
||
namespace HashSet
|
||
variables {α : Type u} [BEq α] [Hashable α]
|
||
|
||
instance : Inhabited (HashSet α) := ⟨mkHashSet⟩
|
||
|
||
instance : EmptyCollection (HashSet α) := ⟨mkHashSet⟩
|
||
|
||
@[inline] def insert (m : HashSet α) (a : α) : HashSet α :=
|
||
match m with
|
||
| ⟨ m, hw ⟩ => ⟨ m.insert a, WellFormed.insertWff m a hw ⟩
|
||
|
||
@[inline] def erase (m : HashSet α) (a : α) : HashSet α :=
|
||
match m with
|
||
| ⟨ m, hw ⟩ => ⟨ m.erase a, WellFormed.eraseWff m a hw ⟩
|
||
|
||
@[inline] def find? (m : HashSet α) (a : α) : Option α :=
|
||
match m with
|
||
| ⟨ m, _ ⟩ => m.find? a
|
||
|
||
@[inline] def contains (m : HashSet α) (a : α) : Bool :=
|
||
match m with
|
||
| ⟨ m, _ ⟩ => m.contains a
|
||
|
||
@[inline] def foldM {δ : Type w} {m : Type w → Type w} [Monad m] (f : δ → α → m δ) (init : δ) (h : HashSet α) : m δ :=
|
||
match h with
|
||
| ⟨ h, _ ⟩ => h.foldM f init
|
||
|
||
@[inline] def fold {δ : Type w} (f : δ → α → δ) (init : δ) (m : HashSet α) : δ :=
|
||
match m with
|
||
| ⟨ m, _ ⟩ => m.fold f init
|
||
|
||
@[inline] def size (m : HashSet α) : Nat :=
|
||
match m with
|
||
| ⟨ {size := sz, ..}, _ ⟩ => sz
|
||
|
||
@[inline] def isEmpty (m : HashSet α) : Bool :=
|
||
m.size = 0
|
||
|
||
@[inline] def empty : HashSet α :=
|
||
mkHashSet
|
||
|
||
def toList (m : HashSet α) : List α :=
|
||
m.fold (init := []) fun r a => a::r
|
||
|
||
def toArray (m : HashSet α) : Array α :=
|
||
m.fold (init := #[]) fun r a => r.push a
|
||
|
||
def numBuckets (m : HashSet α) : Nat :=
|
||
m.val.buckets.val.size
|
||
|
||
end HashSet
|
||
end Std
|