Skip to content

Commit

Permalink
feat: add .values() alias for .vals() (#4876)
Browse files Browse the repository at this point in the history
Makes it possible to write `x.values()` in place of `x.vals()` for an array or `Blob`. This is motivated by the upcoming base library changes in [this repository](https://github.com/dfinity/new-motoko-base).

`values()` is used in place of `vals()` for a large number of popular PLs (JS, Python, Java, C#, Rust, Swift, Kotlin, etc.), so this change would enable developers and LLMs to better transfer knowledge from other languages.

**Progress:**

* [x] Add alias in type checker
* [x] Update documentation and examples
* [x] Update unit tests to cover both `.vals()` and `.values()`
  • Loading branch information
rvanasa authored Feb 5, 2025
1 parent 45d1190 commit 34fa015
Show file tree
Hide file tree
Showing 81 changed files with 461 additions and 190 deletions.
36 changes: 19 additions & 17 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@

* motoko (`moc`)

* Support explicit, safe migration of persistent data allowing arbitrary
transformations on a selected subset of stable variables.
Additional static checks warn against possible data loss (#4812).
* Add `.values()` as an alias to `.vals()` for arrays and `Blob`s (#4876).

As a very simple example:
```
import Nat32 "mo:base/Nat32";
* Support explicit, safe migration of persistent data allowing arbitrary
transformations on a selected subset of stable variables.
Additional static checks warn against possible data loss (#4812).

As a very simple example:
```
import Nat32 "mo:base/Nat32";
(with migration =
func (old : { var size : Nat32 }) : { var length : Nat } {
{ var length = Nat32.toNat(old.size) }
(with migration =
func (old : { var size : Nat32 }) : { var length : Nat } {
{ var length = Nat32.toNat(old.size) }
}
)
persistent actor {
var length : Nat = 0;
}
)
persistent actor {
var length : Nat = 0;
}
```
may be used during an upgrade to rename the stable field `size` to `length`,
and change its type from `Nat32` to `Nat`.
```
may be used during an upgrade to rename the stable field `size` to `length`,
and change its type from `Nat32` to `Nat`.
See the documentation for full details.
See the documentation for full details.
## 0.13.7 (2025-02-03)
Expand Down
2 changes: 1 addition & 1 deletion design/WhitePaper.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ This extension is easy to implement for type definitions, but much more involved
On the plus side, the same mechanism can then be used to express any abstractions over shared types. For example, it would be possible to use equality over generic types:
```
func contained<shared A>(x : A, ys : [A]) : Bool {
for (y in ys.vals()) { if (x == y) return true };
for (y in ys.values()) { if (x == y) return true };
return false;
}
```
Expand Down
2 changes: 1 addition & 1 deletion design/scoped-await.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ Assuming the following requests:
for (i in os.keys()) {
os[i] := ? (Ack());
};
for (o in os.vals()) {
for (o in os.values()) {
switch o {
case (? a) await a;
case null (assert false);
Expand Down
8 changes: 4 additions & 4 deletions doc/attic/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ assert(days[1] == "Tue");
// days[7] will trap (fixed size)
for (d in days.vals()) { Debug.print(d) };
for (d in days.values()) { Debug.print(d) };
```

## Arrays (mutable)
Expand Down Expand Up @@ -623,7 +623,7 @@ actor Broadcast {
public func send(t : Text) : async Nat {
var sum = 0;
for (a in r.vals()) {
for (a in r.values()) {
sum += await a.recv(t);
};
return sum;
Expand All @@ -650,7 +650,7 @@ if the result is an `Error`, `throw`s the error.
``` motoko no-repl
public func send(t : Text) : async Nat {
var sum = 0;
for (a in r.vals()) {
for (a in r.values()) {
sum += await a.recv(t); // may return Nat or `throw` error
};
return sum;
Expand All @@ -670,7 +670,7 @@ A bad implementation of `send`:
var sum = 0; // shared state!
public func send(t : Text) : async Nat {
sum := 0;
for (a in r.vals()) {
for (a in r.values()) {
sum += await a.recv(t);
};
return sum;
Expand Down
2 changes: 1 addition & 1 deletion doc/md/examples/CardShuffle.mo
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ persistent actor {
public query func show() : async Text {
let ?cards = deck else throw Error.reject("shuffle in progress");
var t = "";
for (card in cards.vals()) {
for (card in cards.values()) {
t #= Char.toText(card);
};
t;
Expand Down
2 changes: 1 addition & 1 deletion doc/md/examples/CounterWithCompositeQuery.mo
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ persistent actor class Counter () {

public shared composite query func sum(counters : [Counter]) : async Nat {
var sum = 0;
for (counter in counters.vals()) {
for (counter in counters.values()) {
sum += await counter.peek();
};
sum
Expand Down
2 changes: 1 addition & 1 deletion doc/md/examples/StableRegistry.mo
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ persistent actor Registry {
var entries : [(Text, Nat)] = []; // implicitly `stable`

transient let map = Map.fromIter<Text,Nat>(
entries.vals(), 10, Text.equal, Text.hash);
entries.values(), 10, Text.equal, Text.hash);

public func register(name : Text) : async () {
switch (map.get(name)) {
Expand Down
2 changes: 1 addition & 1 deletion doc/md/examples/WeatherActor.mo
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ persistent actor {
public func averageTemperature() : Float {
var sum = 0.0;
var count = 0.0;
for (value in temperatures.vals()) {
for (value in temperatures.values()) {
sum += value;
count += 1;
};
Expand Down
4 changes: 2 additions & 2 deletions doc/md/reference/language-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ The corresponding module in the base library provides conversion functions:

### Type [`Blob`](../base/Blob.md)

The type [`Blob`](../base/Blob.md) of category O (Ordered) represents binary blobs or sequences of bytes. Function `b.size` returns the number of characters in [`Blob`](../base/Blob.md) value `b`. Operations on blob values include sequential iteration over bytes via function `b.vals` as in `for (v : Nat8 in b.vals()) { …​ v …​ }`.
The type [`Blob`](../base/Blob.md) of category O (Ordered) represents binary blobs or sequences of bytes. Function `b.size()` returns the number of characters in [`Blob`](../base/Blob.md) value `b`. Operations on blob values include sequential iteration over bytes via function `b.values()` as in `for (v : Nat8 in b.values()) { …​ v …​ }`.

### Type [`Principal`](../base/Principal.md)

Expand Down Expand Up @@ -2415,7 +2415,7 @@ In particular, the `for` loop will trap if evaluation of `<exp1>` traps; as soon

:::note

Although general purpose, `for` loops are commonly used to consume iterators produced by [special member access](#special-member-access) to, for example, loop over the indices (`a.keys()`) or values (`a.vals()`) of some array, `a`.
Although general purpose, `for` loops are commonly used to consume iterators produced by [special member access](#special-member-access) to, for example, loop over the indices (`a.keys()`) or values (`a.values()`) of some array, `a`.

:::

Expand Down
6 changes: 3 additions & 3 deletions doc/md/reference/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ To increase readability and uniformity of Motoko source code, the style guide pr
``` motoko no-repl
if (f()) A else B;
for (x in xs.vals()) { ... };
for (x in xs.values()) { ... };
switch (compare(x, y)) {
case (#less) { A };
case (_) { B };
Expand Down Expand Up @@ -624,7 +624,7 @@ Rationale: `g[1]` in particular will be misparsed as an indexing operation.
``` motoko no-repl
func foreach<X>(xs : [X], f : X -> ()) {
for (x in xs.vals()) { f(x) }
for (x in xs.values()) { f(x) }
}
```
Expand Down Expand Up @@ -792,7 +792,7 @@ Rationale: `g[1]` in particular will be misparsed as an indexing operation.
``` motoko no-repl
for (i in Iter.range(1, 10)) { ... };
for (x in array.vals()) { ... };
for (x in array.values()) { ... };
```
Rationale: For loops are less error-prone and easier to read.
Expand Down
2 changes: 1 addition & 1 deletion doc/md/writing-motoko/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ persistent actor {
public func location_pretty(cities : [Text]) : async Text {
var str = "Hello from ";
for (city in cities.vals()) {
for (city in cities.values()) {
str := str # city # ", ";
};
return str # "bon voyage!";
Expand Down
2 changes: 1 addition & 1 deletion doc/md/writing-motoko/candid.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ persistent actor This {
public func concat(ts : [Text]) : async Text {
var r = "";
for (t in ts.vals()) { r #= t };
for (t in ts.values()) { r #= t };
r
};
Expand Down
2 changes: 1 addition & 1 deletion doc/md/writing-motoko/control-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ let carsInStock = [
("Audi", 2020, 34.900)
];
var inventory : { var value : Float } = { var value = 0.0 };
for ((model, year, price) in carsInStock.vals()) {
for ((model, year, price) in carsInStock.values()) {
inventory.value += price;
};
inventory
Expand Down
4 changes: 2 additions & 2 deletions doc/md/writing-motoko/sharing.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ persistent actor Publisher {
};
public func publish() {
for (sub in subs.vals()) {
for (sub in subs.values()) {
sub.notify();
};
};
Expand Down Expand Up @@ -167,7 +167,7 @@ persistent actor Publisher {
};
public func publish() {
for (sub in subs.vals()) {
for (sub in subs.values()) {
sub.callback();
};
};
Expand Down
2 changes: 1 addition & 1 deletion doc/overview-slides.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ let days = ["Monday", "Tuesday", … ];
assert(days.len() == 7);
assert(days[1] == "Tuesday");
// days[7] will trap (fixed size)
for (d in days.vals()) { Debug.print(d) };
for (d in days.values()) { Debug.print(d) };
```

## Arrays (mutable)
Expand Down
12 changes: 6 additions & 6 deletions src/lowering/desugar.ml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ and exp' at note = function
| S.LoopE (e1, None) -> I.LoopE (exp e1)
| S.LoopE (e1, Some e2) -> (loopWhileE (exp e1) (exp e2)).it
| S.ForE (p, {it=S.CallE ({it=S.DotE (arr, proj); _}, _, e1); _}, e2)
when T.is_array arr.note.S.note_typ && (proj.it = "vals" || proj.it = "keys")
when T.is_array arr.note.S.note_typ && (proj.it = "vals" || proj.it = "values" || proj.it = "keys")
-> (transform_for_to_while p arr proj e1 e2).it
| S.ForE (p, e1, e2) -> (forE (pat p) (exp e1) (exp e2)).it
| S.DebugE e -> if !Mo_config.Flags.release_mode then (unitE ()).it else (exp e).it
Expand Down Expand Up @@ -279,7 +279,7 @@ and lexp' = function
| _ -> raise (Invalid_argument ("Unexpected expression as lvalue"))

and transform_for_to_while p arr_exp proj e1 e2 =
(* for (p in (arr_exp : [_]).proj(e1)) e2 when proj in {"keys", "vals"}
(* for (p in (arr_exp : [_]).proj(e1)) e2 when proj in {"keys", "vals", "values"}
~~>
let arr = arr_exp ;
let last = arr.size(e1) : Int - 1 ;
Expand All @@ -299,7 +299,7 @@ and transform_for_to_while p arr_exp proj e1 e2 =
let arrv = fresh_var "arr" arr_typ in
let indx = fresh_var "indx" T.(Mut nat) in
let indexing_exp = match proj.it with
| "vals" -> primE I.DerefArrayOffset [varE arrv; varE indx]
| "vals" | "values" -> primE I.DerefArrayOffset [varE arrv; varE indx]
| "keys" -> varE indx
| _ -> assert false in
let last = fresh_var "last" T.int in
Expand Down Expand Up @@ -837,8 +837,8 @@ and array_dotE array_ty proj e =
| true, "put" -> call "@mut_array_put" [T.nat; varA] []
| true, "keys" -> call "@mut_array_keys" [] [T.iter_obj T.nat]
| false, "keys" -> call "@immut_array_keys" [] [T.iter_obj T.nat]
| true, "vals" -> call "@mut_array_vals" [] [T.iter_obj varA]
| false, "vals" -> call "@immut_array_vals" [] [T.iter_obj varA]
| true, ("vals" | "values") -> call "@mut_array_vals" [] [T.iter_obj varA]
| false, ("vals" | "values") -> call "@immut_array_vals" [] [T.iter_obj varA]
| _, _ -> assert false

and blob_dotE proj e =
Expand All @@ -848,7 +848,7 @@ and blob_dotE proj e =
callE (varE f) [] e in
match proj with
| "size" -> call "@blob_size" [] [T.nat]
| "vals" -> call "@blob_vals" [] [T.iter_obj T.(Prim Nat8)]
| "vals" | "values" -> call "@blob_vals" [] [T.iter_obj T.(Prim Nat8)]
| _ -> assert false

and text_dotE proj e =
Expand Down
2 changes: 2 additions & 0 deletions src/mo_frontend/typing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ let array_obj t =
{lab = "size"; typ = Func (Local, Returns, [], [], [Prim Nat]); src = empty_src};
{lab = "keys"; typ = Func (Local, Returns, [], [], [iter_obj (Prim Nat)]); src = empty_src};
{lab = "vals"; typ = Func (Local, Returns, [], [], [iter_obj t]); src = empty_src};
{lab = "values"; typ = Func (Local, Returns, [], [], [iter_obj t]); src = empty_src};
] in
let mut t = immut t @
[ {lab = "put"; typ = Func (Local, Returns, [], [Prim Nat; t], []); src = empty_src} ] in
Expand All @@ -1168,6 +1169,7 @@ let blob_obj () =
let open T in
Object,
[ {lab = "vals"; typ = Func (Local, Returns, [], [], [iter_obj (Prim Nat8)]); src = empty_src};
{lab = "values"; typ = Func (Local, Returns, [], [], [iter_obj (Prim Nat8)]); src = empty_src};
{lab = "size"; typ = Func (Local, Returns, [], [], [Prim Nat]); src = empty_src};
]

Expand Down
4 changes: 2 additions & 2 deletions src/mo_interpreter/interpret.ml
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
| "get" -> array_get
| "put" -> array_put
| "keys" -> array_keys
| "vals" -> array_vals
| "vals" | "values" -> array_vals
| s -> assert false
in k (f vs exp.at)
| V.Text s ->
Expand All @@ -538,7 +538,7 @@ and interpret_exp_mut env exp (k : V.value V.cont) =
| V.Blob b when T.sub exp1.note.note_typ (T.blob)->
let f = match id.it with
| "size" -> blob_size
| "vals" -> blob_vals
| "vals" | "values" -> blob_vals
| s -> assert false
in k (f b exp.at)
| _ -> assert false
Expand Down
2 changes: 1 addition & 1 deletion src/mo_values/value.ml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ and value =
| Async of async
| Comp of comp
| Mut of value ref
| Iter of value Seq.t ref (* internal to {b.vals(), t.chars()} iterator *)
| Iter of value Seq.t ref (* internal to {b.values(), t.chars()} iterator *)

and res = Ok of value | Error of value
and async = {result : res Lib.Promise.t ; mutable waiters : (value cont * value cont) list}
Expand Down
2 changes: 1 addition & 1 deletion src/mo_values/value.mli
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ and value =
| Async of async
| Comp of comp
| Mut of value ref
| Iter of value Seq.t ref (* internal to {b.vals(), t.chars()} iterator *)
| Iter of value Seq.t ref (* internal to {b.values(), t.chars()} iterator *)

and res = Ok of value | Error of value
and async = {result : res Lib.Promise.t ; mutable waiters : (value cont * value cont) list}
Expand Down
8 changes: 4 additions & 4 deletions src/prelude/internals.mo
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func @text_of_Char(c : Char) : Text {
func @text_of_Blob(blob : Blob) : Text {
var t = "\"";
for (b in blob.vals()) {
for (b in blob.values()) {
// Could do more clever escaping, e.g. leave ascii and utf8 in place
t #= "\\" # @left_pad(2, "0", @text_of_num(@nat8ToNat b, 16, 0, @digits_hex));
};
Expand Down Expand Up @@ -243,7 +243,7 @@ func @text_of_variant<T>(l : Text, f : T -> Text, x : T) : Text {
func @text_of_array<T>(f : T -> Text, xs : [T]) : Text {
var text = "[";
var first = true;
for (x in xs.vals()) {
for (x in xs.values()) {
if first {
first := false;
} else {
Expand All @@ -257,7 +257,7 @@ func @text_of_array<T>(f : T -> Text, xs : [T]) : Text {
func @text_of_array_mut<T>(f : T -> Text, xs : [var T]) : Text {
var text = "[var";
var first = true;
for (x in xs.vals()) {
for (x in xs.values()) {
if first {
first := false;
text #= " ";
Expand Down Expand Up @@ -632,7 +632,7 @@ func @timer_helper() : async () {
})
};
for (o in thunks.vals()) {
for (o in thunks.values()) {
switch o {
case (?thunk) try ignore thunk() catch _ reinsert thunk;
case _ return
Expand Down
2 changes: 1 addition & 1 deletion test/fail/objpat-infer.mo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ignore (switch (object {}) { case {a} 42 });

// checks

for ({} in [object {}].vals()) { Prim.debugPrint "hey" };
for ({} in [object {}].values()) { Prim.debugPrint "hey" };

// infers

Expand Down
Loading

0 comments on commit 34fa015

Please sign in to comment.