Lenses

Lenses

When dealing with an immutable record, you may want to update just one field and leave others alone.

main void() a thing = name: "alice", size: 1 log change-size a, 2 thing record(name string, size nat) change-size thing(a thing, new-size nat) name: a.name size: new-size

Writing functions like change-size is tedious, so Keen will automatically generate a lens for every record field.

main void() a thing = name: "alice", size: 1 log set a, size, 2 thing record(name string, size float)

set is a function that takes a record, a lens, and the new value.
In this case, it uses the size lens and sets the new size to 2.

If the new value depends on the old value, use modify.

main void() a thing = name: "alice", size: 1 log modify a, size, it + 1 thing record(name string, size float)

modify works like set but takes a lambda to compute the new value from the old.

Combining lenses

a + b is a lens that gets / sets both a and b as a tuple.

main void() a r = x: 1, y: 2, z: 3 log modify a, x + z, it.swap r record(x nat, y nat, z nat)

a * b is a lens that delves into a then b.

main void() a hands = left: thumb: true, fingers: 4 right: thumb: true, fingers: 5 log set a, left * thumb, false hands record(left hand, right hand) hand record(thumb bool, fingers nat)

Defining lenses

lens is an interface (see Interfaces and variants) and you can define your own.

main void() a hands = is-left-handed: true, left: "a", right: "b" log modify a, dominant-hand, "{it}!" hands record(is-left-handed bool, left string, right string) dominant-hand (hands, string) lens() get: of x if is-left-handed x then x.left else x.right set: of x, value set x, (if is-left-handed x then left else right), value

The above example is simpler using computed-lens.

main void() a hands = is-left-handed: true, left: "a", right: "b" log modify a, dominant-hand, "{it}!" hands record(is-left-handed bool, left string, right string) dominant-hand (hands, string) lens() computed-lens {if is-left-handed it then left else right}

"then"

The then keyword provides a syntax for chaining several updates.

main void() log 0 .. 10 then to::nat[] then filter::nat[] it.is-even then map::nat[] it + 100

Each then must start on a new line.
It is followed by a function name, whose first argument is the previous line, then the rest of the arguments (if any).
This is equivalent to:

main void() log v0 = 0 .. 10 v1 = to::nat[] v0 v2 = filter::nat[] v1, it.is-even map::nat[] v2, it + 100

then can be used with any function, but it's particularly suited to set and modify.

import keen/math/geom2d/aa-rect main void() a aa-rect32 = left: 0, right: 10, bottom: 0, top: 10 log a then set left, 3 then modify right, it - 3

Using then if, the change can be conditional.

main void() log foo 10, false, false log foo 10, false, true log foo 10, true, false log foo 10, true, true foo nat(a nat, incr bool, double bool) a then if incr then + 1 then if double then * 2