11Mapping Process
11.1About This Chapter
This chapter explains about Gura's mapping process, Implicit Mapping and Member Mapping. In the documentation, following terms are used to describe species of values.
- scalar — an instance of any type except for
list
anditerator
- list — an instance of
list
- iterator — an instance of
iterator
- iterable — list or iterator
11.2Implicit Mapping
11.2.1Overview
Implicit Mapping is a feature that evaluates a function or an operator repeatedly when it's given with a list or an iterator.
A function that is capable of Implicit Mapping is marked with an attribute :map
. Consider a function f(n:number):map
that takes a number value and returns a squared number of it. You can call it like f(3)
, which is expected to return a number 9
. Here, using Implicit Mapping, you can call it with a list of numbers like below:
f([2, 3, 4])
This will result in a list [4, 9, 16]
after evaluating each number in the list.
Implicit Mapping also works with operators. The example below applies an operation that adds three to each value in the list using Implicit Mapping:
[2, 3, 4] + 3
This will result in [5, 6, 7]
. Of course, you can also apply Implicit Mapping on an operation between two lists. See the following example:
[2, 3, 4] + [3, 4, 5]
As you might expect, it returns a list [5, 7, 9]
.
The above example may just look like a vector calculation. Actually, this type of operation, which applies some operations on a set of numbers at once, is known as "vectorization", and has been implemented in languages and libraries that compute vectors and matrices.
Implicit Mapping enhances that idea so that it has the following capabilities:
Implicit Mapping can handle any type of objects other than number.
Consider a function
g(str:string):map
that takes a string and returns a result after converting alphabet characters in the string to upper case. When you call it with a single value, it will return one result.str = 'hello' x = g(str) // x is 'HELLO'
You can call it with a list of string to get a list of results by using Implicit Mapping feature.
strs = ['hello', 'Gura', 'world'] x = g(strs) // x is ['HELLO', 'GURA', 'WORLD']
Implicit Mapping can operate with an iterator as well.
Consider the function
g(str:string):map
that has been mentioned above. If you call it with an iterator, it will return an iterator as its result.strs = ('hello', 'Gura', 'world') // creates an iterator x = g(strs) // x is an iterator that equivalent with ('HELLO', 'GURA', 'WORLD')
It means that the actual evaluation of the function
g()
will be postponed by the time when the created iterator is evaluated or destroyed. This is important because using an iterator will enable you to avoid unnecessary calculation and memory consumption.You can use Implicit Mapping to repeat a function call without an explicit repeat procedure.
A built-in function
println():map
prints a content of the given value before putting out a line-feed. Consider a case that you need to print each value in the liststrs
that contains['hello', 'Gura', 'world']
. With an ordinary idea, you may usefor()
to process each item in a list.for (str in strs) { println(str) }
Using Implicit Mapping, you can simply do it like below:
println(strs)
Implicit Mapping can work on any number of lists and iterators given in an argument list of a function call.
Consider that there's a function
f(a:string, b:number, c:string):map
, and you give it lists as its arguments like below:as = ['first', 'second', 'third', 'fourth'] bs = [1, 2, 3, 4] cs = ['one', 'two', 'three', 'four'] f(as, bs, cs)
This has the same effect as below:
f('first', 1, 'one') f('second', 2, 'two') f('third', 3, 'three') f('fourth', 4, 'four')
11.2.2Mapping Rule with Operator
Implicit Mapping works on most of the operators even though there are several exceptions. In the description below, a symbol o
is used to represent a certain operator.
With a Prefixed Unary Operator, species of the result is the same as that of the given value. Below is a summary table:
Operation | Result |
---|---|
o scalar |
scalar |
o list |
list |
o iterator |
iterator |
Examples are shown below:
Example | Result |
---|---|
!true |
false |
![true, true, false, true] |
[false, false, true, false] |
!(true, true, false, true) |
(false, false, true, false) |
With a Suffixed Unary Operator, species of the result is the same as that of the given value. Below is a summary table:
Operation | Result |
---|---|
scalar o |
scalar |
list o |
list |
iterator o |
iterator |
With a Binary Operator, the folloiwing rules are applied.
- If both of left and right values are of scalar species, the result becomes a scalar.
- If either of left or right value is of iterator species, the result becomes an iterator.
- Otherwise, the result becomes a list.
Below is a summary table:
Operation | Result |
---|---|
scalar o scalar |
scalar |
scalar o list |
list |
scalar o iterator |
iterator |
list o scalar |
list |
list o list |
list |
list o iterator |
iterator |
iterator o scalar |
iterator |
iterator o list |
iterator |
iterator o iterator |
iterator |
If both of left and right values are iterable and they have different length, Implicit Mapping would be applied on a range of a shorter length.
Some operators expect lists or iterators in their own operations and inhibit Implicit Mapping. See the table below:
Operation | Note |
---|---|
x? |
It deters Implicit Mapping because it needs to check if x itself can be determined as true or not. |
x* |
It expects x may take an iterator or a list. |
x * y where x is function |
It may take a list as a value of y . |
x % y where x is string |
It may take a list as a value of y . |
x in y |
It expects x and y may take list values. |
x => y |
It expects y may take a list value. |
11.2.3Mapping Rule with Function
A function with :map
attribute in its declaration is capable of Implicit Mapping.
Here are function definitions that return a square value of the given number to see the effect of :map
attribute.
f_nomap(x:number) = x * x
f_map(x:number):map = x * x
The function delcared with :map
attribute is capable of Implicit Mapping and can take a list for an argument that expects a number
value.
f_nomap([1, 2, 3]) // Error
f_map([1, 2, 3]) // Implicit Mapping works on each item and returns [1, 4, 9]
As for a function f(x):map
that takes one argument, the mapping rule is the same as that of Unary Operator. See the following summary table:
Operation | Result |
---|---|
f(scalar) |
scalar |
f(list) |
list |
f(iterator) |
iterator |
A function f(x, y):map
that takes two arguments behaves in the same manner with Binary Operator. Below is a summary table:
Operation | Result |
---|---|
f(scalar, scalar) |
scalar |
f(scalar, list) |
list |
f(scalar, iterator) |
iterator |
f(list, scalar) |
list |
f(list, list) |
list |
f(list, iterator) |
iterator |
f(iterator, scalar) |
iterator |
f(iterator, list) |
iterator |
f(iterator, iterator) |
iterator |
In general, if a function takes more than one argument, the following rules are appplied.
- If all of the argument values are of scalar species, the result becomes a scalar.
- If one of the argument values is of iterator species, the result becomes an iterator.
- Otherwise, the result becomes a list.
Here are some example cases with a function f(x, y, z):map
:
Operation | Result |
---|---|
f(scalar, scalar, sholar) |
scalar |
f(scalar, scalar, list) |
list |
f(scalar, scalar, iterator) |
iterator |
f(scalar, list, iterator) |
iterator |
If an argument list contains iterables that have different length each other, Implicit Mapping would be applied on a range of the shortest length. For example, the code below repeats the process three times.
f([1, 2, 3], ['a', 'b', 'c', 'd'], [4, 5, 6])
Implicit Mapping does not work with arguments that match the following case:
If a function contains an argument that expects
list
oriterator
, Implicit Mapping would not work with that argument. In the following example, putting a list or an iterator to argumentz
, which expects a list or an iterator as its value, is not considered as a criteria for Implicit Mapping.f(x, y, z:list):map = { /* body */ } f(x, y, z:iterator):map = { /* body */ } f(x, y, z[]):map = { /* body */ }
Putting an attribute
:nomap
to an argument declaration would exclude it from Implicit Mapping criteria. In the example below, specifying a list or an iterator to argumentz
is not considered as a criteria for Implicit Mapping.f(x, y, z:nomap):map = { /* body */ }
11.2.4Result Control on List
Consider a function f(n:number):map
that is defined as below:
f(n:number):map = println('n = ', n)
It takes a number value and just prints it.
f(3) // prints 'n = 3'
Here, function println()
is defined with an attribute :void
that is meant to always return nil
as its result. So, the function f()
that evaluates println()
at last would return nil
as well.
As the function f()
is capable of Implicit Mapping, you can call it with a list for repeating process.
f([1, 2, 3]) // prints 'n = 1', 'n = 2' and 'n = 3'
As you've already seen above, when a function with :map
attribute takes a list, it will evaluate each value in the list immediately and returns a list containing the results. Considering that rule, you may think the calling it as above could return [nil, nil, nil]
.
But, in reality, it returns a single nil
.
Implicit Mapping is designed to work as a generic repeating mechanism. If a function is expected to always return some meaningless value such as nil
, creating a list that contains such values through a repeating process absolutely makes no sense. To avoid that vain process, Implicit Mapping would only create a list when a valid value appears in the result.
Consider a function below that simply returns the given value as its result.
g(n):map = n
The table below summarizes what result you get from g()
when it's given with a list containing valid and nil
values.
Script | Result |
---|---|
g([]) |
[] |
g([nil]) |
nil |
g([nil, nil]) |
nil |
g([nil, nil, 3]) |
[nil, nil, 3] |
g([nil, nil, 3, 5]) |
[nil, nil, 3, 5] |
g([nil, nil, 3, 5, 3]) |
[nil, nil, 3, 5, 3] |
g([nil, nil, 3, 5, 3, nil]) |
[nil, nil, 3, 5, 3, nil] |
Note that, when you give an empty list to a function with Implicit Mapping, it would return an empty list as its result.
There are some attributes that control how Implicit Mapping generates the result even when it's expected to generate a list by default.
Attribute
:list
always creates a list even if it only containsnil
values.Script Result g([]):list
[]
g([nil]):list
[nil]
g([nil, nil]):list
[nil, nil]
g([nil, nil, 3]):list
[nil, nil, 3]
g([nil, nil, 3, 5]):list
[nil, nil, 3, 5]
g([nil, nil, 3, 5, 3]):list
[nil, nil, 3, 5, 3]
g([nil, nil, 3, 5, 3, nil]):list
[nil, nil, 3, 5, 3, nil]
Attribute
:xlist
always creates a list after excludingnil
value from the result.Script Result g([]):xlist
[]
g([nil]):xlist
[]
g([nil, nil]):xlist
[]
g([nil, nil, 3]):xlist
[3]
g([nil, nil, 3, 5]):xlist
[3, 5]
g([nil, nil, 3, 5, 3]):xlist
[3, 5, 3]
g([nil, nil, 3, 5, 3, nil]):xlist
[3, 5, 3]
Attribute
:set
always creates a list after excluding duplicated values.Script Result g([]):set
[]
g([nil]):set
[nil]
g([nil, nil]):set
[nil]
g([nil, nil, 3]):set
[nil, 3]
g([nil, nil, 3, 5]):set
[nil, 3, 5]
g([nil, nil, 3, 5, 3]):set
[nil, 3, 5]
g([nil, nil, 3, 5, 3, nil]):set
[nil, 3, 5]
Attribute
:xset
always creates a list after excludingnil
and duplicated values.Script Result g([]):xset
[]
g([nil]):xset
[]
g([nil, nil]):xset
[]
g([nil, nil, 3]):xset
[3]
g([nil, nil, 3, 5]):xset
[3, 5]
g([nil, nil, 3, 5, 3]):xset
[3, 5]
g([nil, nil, 3, 5, 3, nil]):xset
[3, 5]
Attribute
:iter
creates an iterator.Script Result g([]):iter
equivalent of [].each()
g([nil]):iter
equivalent of (nil,)
g([nil, nil]):iter
equivalent of (nil, nil)
g([nil, nil, 3]):iter
equivalent of (nil, nil, 3)
g([nil, nil, 3, 5]):iter
equivalent of (nil, nil, 3, 5)
g([nil, nil, 3, 5, 3]):iter
equivalent of (nil, nil, 3, 5, 3)
g([nil, nil, 3, 5, 3, nil]):iter
equivalent of (nil, nil, 3, 5, 3, nil)
Attribute
:xiter
creates an iterator that excludesnil
value from the result.Script Result g([]):xiter
equivalent of [].each()
g([nil]):xiter
equivalent of [].each()
g([nil, nil]):xiter
equivalent of [].each()
g([nil, nil, 3]):xiter
equivalent of (3,)
g([nil, nil, 3, 5]):xiter
equivalent of (3, 5)
g([nil, nil, 3, 5, 3]):xiter
equivalent of (3, 5, 3)
g([nil, nil, 3, 5, 3, nil]):xiter
equivalent of (3, 5, 3)
Attribute
:void
indicates the function always returnsnil
regardless of its original result.Script Result g([]):void
nil
g([nil]):void
nil
g([nil, nil]):void
nil
g([nil, nil, 3]):void
nil
g([nil, nil, 3, 5]):void
nil
g([nil, nil, 3, 5, 3]):void
nil
g([nil, nil, 3, 5, 3, nil]):void
nil
Attribute
:reduce
indicates the function returns the last evaluated value and doesn't create a list.Script Result g([]):reduce
nil
g([nil]):reduce
nil
g([nil, nil]):reduce
nil
g([nil, nil, 3]):reduce
3
g([nil, nil, 3, 5]):reduce
5
g([nil, nil, 3, 5, 3]):reduce
3
g([nil, nil, 3, 5, 3, nil]):reduce
nil
Attribute
:xreduce
indicates the function returns the last evaluated value and doesn't create a list. The returned value is updated only when the result is valid.Script Result g([]):xreduce
nil
g([nil]):xreduce
nil
g([nil, nil]):xreduce
nil
g([nil, nil, 3]):xreduce
3
g([nil, nil, 3, 5]):xreduce
5
g([nil, nil, 3, 5, 3]):xreduce
3
g([nil, nil, 3, 5, 3, nil]):xreduce
3
11.2.5Result Control on Iterator
Consider a function below that simply returns the given value as its result.
g(n):map = n
When you give it an iterator, it would return an iterator as well following after Implicit Mapping rule.
Script | Result |
---|---|
g([].each()) |
equivalent of [].each() |
g((nil,)) |
equivalent of (nil,) |
g((nil, nil)) |
equivalent of (nil, nil) |
g((nil, nil, 3)) |
equivalent of (nil, nil, 3) |
g((nil, nil, 3, 5)) |
equivalent of (nil, nil, 3, 5) |
g((nil, nil, 3, 5, 3)) |
equivalent of (nil, nil, 3, 5, 3) |
g((nil, nil, 3, 5, 3, nil)) |
equivalent of (nil, nil, 3, 5, 3, nil) |
There are some attributes that control how Implicit Mapping generates the result even when it's expected to generate an iterator by default.
Attribute
:list
creates a list.Script Result g([].each()):list
[]
g((nil,)):list
[nil]
g((nil, nil)):list
[nil, nil]
g((nil, nil, 3)):list
[nil, nil, 3]
g((nil, nil, 3, 5)):list
[nil, nil, 3, 5]
g((nil, nil, 3, 5, 3)):list
[nil, nil, 3, 5, 3]
g((nil, nil, 3, 5, 3, nil)):list
[nil, nil, 3, 5, 3, nil]
Attribute
:xlist
creates a list after excludingnil
value from the result.Script Result g([].each()):xlist
[]
g((nil,)):xlist
[]
g((nil, nil)):xlist
[]
g((nil, nil, 3)):xlist
[3]
g((nil, nil, 3, 5)):xlist
[3, 5]
g((nil, nil, 3, 5, 3)):xlist
[3, 5, 3]
g((nil, nil, 3, 5, 3, nil)):xlist
[3, 5, 3]
Attribute
:set
creates a list after excluding duplicated values.Script Result g([].each()):set
[]
g((nil,)):set
[nil]
g((nil, nil)):set
[nil]
g((nil, nil, 3)):set
[nil, 3]
g((nil, nil, 3, 5)):set
[nil, 3, 5]
g((nil, nil, 3, 5, 3)):set
[nil, 3, 5]
g((nil, nil, 3, 5, 3, nil)):set
[nil, 3, 5]
Attribute
:xset
creates a list after excludingnil
and duplicated values.Script Result g([].each()):xset
[]
g((nil,)):xset
[]
g((nil, nil)):xset
[]
g((nil, nil, 3)):xset
[3]
g((nil, nil, 3, 5)):xset
[3, 5]
g((nil, nil, 3, 5, 3)):xset
[3, 5]
g((nil, nil, 3, 5, 3, nil)):xset
[3, 5]
- Attribute
:iter
creates an iterator. This is a default behavior of Implicit Mapping for an iterator. Attribute
:xiter
creates an iterator that excludesnil
value from the result.Script Result g([].each()):xiter
equivalent of [].each()
g((nil,)):xiter
equivalent of [].each()
g((nil, nil)):xiter
equivalent of [].each()
g((nil, nil, 3)):xiter
equivalent of (3,)
g((nil, nil, 3, 5)):xiter
equivalent of (3, 5)
g((nil, nil, 3, 5, 3)):xiter
equivalent of (3, 5, 3)
g((nil, nil, 3, 5, 3, nil)):xiter
equivalent of (3, 5, 3)
Attribute
:void
indicates the function will always returnnil
regardless of its original result.Script Result g([].each()):void
nil
g((nil,)):void
nil
g((nil, nil)):void
nil
g((nil, nil, 3)):void
nil
g((nil, nil, 3, 5)):void
nil
g((nil, nil, 3, 5, 3)):void
nil
g((nil, nil, 3, 5, 3, nil)):void
nil
Attribute
:reduce
indicates the function returns the last evaluated value and doesn't create an iterator.Script Result g([].each()):reduce
nil
g((nil)):reduce
nil
g((nil, nil)):reduce
nil
g((nil, nil, 3)):reduce
3
g((nil, nil, 3, 5)):reduce
5
g((nil, nil, 3, 5, 3)):reduce
3
g((nil, nil, 3, 5, 3, nil)):reduce
nil
Attribute
:xreduce
indicates the function returns the last evaluated value and doesn't create an iterator. The returned value is updated only when the result is valid.Script Result g([].each()):xreduce
nil
g((nil)):xreduce
nil
g((nil, nil)):xreduce
nil
g((nil, nil, 3)):xreduce
3
g((nil, nil, 3, 5)):xreduce
5
g((nil, nil, 3, 5, 3)):xreduce
3
g((nil, nil, 3, 5, 3, nil)):xreduce
3
An iterator created by Implicit Mapping has a special feature; it will be evaluated automatically when it's destroyed. Consider the following function:
f(n:number):map = println('n = ', n)
And call it as below:
f((3, 1, 4))
In Implicit Mapping rule, the call above would simply return an iterator and is supposed not do any process unless the iterator is actually evaluated. But usually, the above case is expected to print values in the iterator at the timing of the function call.
Actually, the code above works as expected because the returned iterator loses any reference from others and is evaluated before destroyed. The script below shows what happens in the above.
x = f((3, 1, 4))
x = nil // iterator is destroyed after printing 'n = 3', 'n = 1' and 'n = 4'.
However, the timing to destroy an instance is sometimes unpredictable. It's recommended that you specify :void
attribute for an instant evaluation.
f((3, 1, 4)):void
Attributes :void
, :reduce
and :xreduce
don't return an iterator, which cause the actual process on given values done immediately.
It may be the best that you specify :void
, :reduce
or :xreduce
attribute in the function definition if you know beforehand that the function always returns nil
or other unchanged value.
f(n:number):map:void = println('n = ', n)
Then, you can call the function with an iterator through Implicit Mapping without any worry.
f((3, 1, 4))
11.2.6Suspend Implicit Mapping
A function call with an attribute :nomap
would suspend Implicit Mapping.
Consider a case that you need to print a content of x
that contains [1, 2, 3, 4]
as a list instance. Simply executing println(x)
would just print each value in the list through Implicit Mapping. To avoid it, put :nomap
for the call as below.
println(x):nomap
11.3Member Mapping
11.3.1Overview
Member Mapping is a feature to access members of instances that are stored in a list or are generated from an iterator.
There's an instance method string#len()
that retrieves a length of a string. With a single instance, you can call it like below:
x = 'first'
n = x.len()
// n is 5
Using a member accessor ::
, you can apply the method on multiple instances in a list.
xs = ['first', 'second', 'third', 'fourth']
ns = xs::len()
// ns is [5, 6, 5, 6]
A member accessor :*
creates an iterator that gets results of member access.
xs = ['first', 'second', 'third', 'fourth']
ns = xs:*len()
// ns is an iterator that generates (5, 6, 5, 6)
11.3.2Mapping Rule
There are three member accessors in Member Mapping as shown below:
Member Accessor | Name |
---|---|
:: |
map-to-list |
:* |
map-to-iterator |
:& |
map-along |
A map-to-list accessor ::
applies a member method or looks up a member variable on instances in an iterable, a list or an iterator, and creates a list of the results. Below shows examples:
xs::variable
xs::func()
A map-to-iterator accessor :*
creates an iterator that applies a member method or looks up a member variable on instances in an iterable, a list or an iterator. Below shows examples:
xs:*variable
xs:*func()
A map-along accessor :&
only has effect with a member method. It iterates the iterable on the left along with iterables in its argument list following after Iterator Mapping rule. See the following example:
xs = [x1, x2, x3]
as = ['first', 'second', 'third']
bs = [3, 1, 4]
xs:&func(as, bs)
This has the same effect with shown below:
[x1.func('first', 3), x2.func('second', 1), x3.func('third', 4)]
The mapping rule with map-along accessor is summarized below:
- If the target iterable or one of the argument values is of iterator species, the result becomes an iterator.
- Otherwise, the result becomes a list.