maps
algebra of maps
Maps can be used as partial functions from key terms to value terms. Maps have their domain (i.e. a set of keys) and image (i.e. a set of values).
Maps are more complex than just partial functions, though: they can have linear/ownership semantics. Maps are objects + (linear?) lenses over their structure:
Maps are monoids: they have the neutral element {}
and can be concatenated, producing a map (~
for concatenation? But, e.g. HOCON uses juxtaposition for monoidal concatenation):
Inspirations:
- ECMAScript objects: almost everything is an object
- ECMAScript prototype chains (which are monoids in disguise)
- Record subtyping in Types and Programming Languages
- Haskell's Data.Map and linear-base.Data.HashMap.Mutable.Linear
- Functional logic programming in Verse.
operations over values
Given an existing map, do something to its values without changing its set of keys ("structure").
// exactly one value cell:
m.a.b
// a prism to get zero or one cell:
m{.a.b}
// a prism to a reference to zero or one cell:
ref m{.a.b}
// a traversal to exactly two cells,
// (`val` uses an implicitly given allocator to make a copy):
#example
val {a=1, b=2, c=3}.(a, b) = {a=1, b=2}
#example
{a=1, c=3}.(a, b) = ERROR "b=?" // static or dynamic error
// a prism to three values or less:
#example
{a=1, b=2, c=3}{.a, .b, .d} = {a=1, b=2}
// setting a value:
at m.a.b \set 42
// at <cell> <op> <args>...
// popping a value from a map:
// a linear(?) lens that consumes and splits a map
v = at m \split k
operations over structure
- projecting into a submap (also: by key)
- replacing a submap (also: by key)
- removing a submap (also: by key)
Constructing maps:
// an empty map
{}
// adding a pair to a map:
a' = a ~ {k: v}
// merging two maps:
m = m1 ~ m2
#example
{a=1, b=2} ~ {b=3, c=5} = {a=1, b=3, c=5}
// the last key wins
Monoidal concatenation of maps is abstract, it does not necessarily create a new flat map. It can be a kind of ECMAScript-like prototype chaining with a [[proto]]
slot.
Additional operations:
#example
{a=1, b=2} \split .a = {b=2}
THINK: addressing keys vs addressing values, e.g.
// an iterator over all values < 2:
#example
{a=1, b=2, c=3}[v => v < 2] = ???
{1=a, b=2, c=3}.[k => ]
abstract value and self
(?) Every value v
is equivalent to notation { v }
, which is a shortcut for a pair of { self = v }
. self
is a special key which refers to the "default" value of the map. When a map is used as a value, it dereferences to its last self
key.
TODO: it
or self
?
TODO: what is a map without self
? something that cannot be constructed at runtime without a container?. If the maps does not have self
, it cannot be used as a single value?
Properties:
- ${\tt v} \equiv {\tt { v}} \equiv {\tt {{v}}} \equiv ...$
- ${\tt v} \equiv {\tt v.self} \equiv {\tt v.self.self} \equiv ...$
self
really looks like an identity lens.
TODO: think if this is in any way consistent.
THINK: can partiality be seen as {}
instead of a value? {}
becomes nil
, it is smelly.
runtime reification
The notation {...}
is an abstract comptime map. It can be used as a blueprint for a runtime value:
// given a runtime container type `HashMap`,
// construct a HashMap from an abstract map:
m = HashMap { one = 1, two = 2, three = 3}
// Q: is this { self = HashMap, ... } ?
Q: empty runtime maps? HashMap {}
is valid?
HOAS
Wiki: higher-order abstract syntax
When we have maps, we can have nested maps (where #<metainfo> <item>
):
#outer {
x = 1
// #inner is not bound, it's the default value of #outer
#inner {
// Q: how this automatic chaining happens? Implicit `self = outer`?
// here x is 1
x = 2
// here x is 2
#last {
x
// currently bound x becomes the default value
// of both inner and outer map
}
}
// here x is 1, but no one is able to see this
}
Nesting maps can be seen a sequencing mechanism. The inner map without a key can be seen as a temporary scope inside of it, chained to / inheriting the outer map. Lookups of symbol can use the current chain.
TODO: non-shadowable definitions, #const
?
scopes
The vanilla map notation is very awkward for this, though. Scopes are notations for sequences of nested pairs, each inheriting scope of its parent pair.
This scope corresponds to the example map above:
do
x = 1 // the body of #outer
// the value of #outer:
x = 2 // the body of #inner
// the value of #inner:
x // #last
end // "evaluates" to 2
Scopes can branch: they can inherit the outer scope, but end up as a separate, non-default value:
do
one = 1
three = do two = 1; one + two; end
three
end
<=>
_ ~ // outer context
{ one = 1 } ~
{ three = self ~ { two = 2 } ~ { one + two } } ~
{ three }
Multiple sequential default values:
do 1; 2; end
<=>
_ ~ // outer scope
{ /* self = */ 1 } ~
{
// the last `self` binding wins due to monoidal concatenation:
/* self = */ { /* self = */ 2 }
}
encapsulation
E.g. you have a helper or a value you don't want to expose:
(range: Nat) => (Rand) do
num = randInt range // unexported
#export {
guess = (n) => n == num
range = range
}
end
Importing multiple other things from other scope:
// sometimes you want to bring another scope in:
math = @include("math.iota")
at self ~ math.{
ln, log2, sqrt
}
// or just import everything from another scope:
at self ~ @include("math.iota")
do-notation
aka "programmable scope".
Examples to think of:
// Implicit Identity monad:
do
one = 1
three = do two = 2; one + two; end
three
end
// <=> [by monad expansion]
(ctx: Id _) =>
Id.lift 1 \Id.bind one =>
Id.lift (
Id.lift 2 \Id.bind two =>
Id.pure (one + two)
) \Id.bind three => Id.pure
three
// Id.pure : a -> Id a
// Id.bind : Id a -> (a -> Id b) -> Id b
// Id.lift : {Has Id m} -> Id a -> m a
// <=> (by definitons of Id.lift)
// (self : Id _) => { ...
(_: Id _) =>
1 \Id.bind one =>
(self \Id.bind
(_: Id _) => {two = 2} ~ Id.pure { one + two }
) \Id.bind three =>
Id.pure three
// <=> [ by def. of `Id.bind` ]
(_: Id _) =>
{ one = 1 } ~
{ three = self ~ {two = 2} ~ {self = one + two} } ~
{ three }
// <=> [no values from `Id` anymore]
{ one = 1 } ~
{ three = {two = 2} ~ { one + two }} ~
{ three }
// <=> [scope reduction]
{ {two = 2} ~ { 1 + two } }
// <=> [scope reduction]
{ 1 + 2 }
// State monad:
St = State
(St) do
x = get ()
x = x + 1
put x
x
end
// <=> [monad expansion]
(st : St _) =>
// lift is closed over the runtime `st`:
// St.bind does not, it goes straight to the type
// TODO: think about comptime/runtime binding more.
st.lift get () \St.bind x =>
st.lift add x 1 \St.bind x =>
st.lift put x \St.bind _ =>
st.lift x \St.bind id
// <=> [expand `st.lift` to `St.lift st`]
(st : St _) =>
St.lift st get () \St.bind x =>
St.lift st (add x 1) \St.bind x =>
St.lift st put x \St.bind _ =>
St.lift st x \St.bind id
// <=> [State.mk, State.get, State.put]
State.mk (st: _ : Add) =>
(st, st) \St.bind (x, st) =>
(add x 1, st) \St.bind (x, st) =>
((), x) \St.bind (_, st) =>
(x, st)
// <=> [State.bind]
State.mk st =>
{(x, st) = (st, st)} ~
{(x, st) = (add x 1, st)} ~
{(_, st) = ((), x)} ~
(x, st)
// <=>
State.mk st => {x = st} ~ {x = add x 1} ~ {st = x} ~ (x, st)
// <=>
State.mk st => { x = add st 1 } ~ (x, x)
// IO:
(IO) do
x = read \ getLine ()
putStr "the number is "
print x
end
// <=> [monad expansion]
(io: IO) =>
IO.lift io.getLine () \IO.bind $0 =>
IO.lift read $0 \IO.bind x =>
IO.lift io.putStr "the number is " \bind _ =>
IO.lift (io.print x)
// <=>
// TODO
// An effect bag, (State, IO):
(State | IO) do
x = get ()
x = x + 1
put x
print x
end
// TODO
misc
- interplay with XML attributes/children distinction?