Chains are an expanded concept of .
in OOP language method call syntax foo.bar()
.
Pangaea can describe how to deal with receiver by chains (chain context).
Dot chain foo.bar
is just one of the method chains in Pangaea.
There are some kinds of chain styles, and each one shows different "context".
chain | name | receiver of the call |
---|---|---|
foo.bar |
scalar chain | left-side value |
foo@bar |
list chain | each element of left-side value iterator |
foo$bar |
reduce chain | an accumulated value and each element of left-side value iterator |
The receiver is left-side value, which is ordinary method chain.
10.p # 10
The receiver is each element of left-side value iterator. This can be used as "map" or "filter" in other languages.
[1, 2, 3]@{|i| i * 2}.p # [2, 4, 6]
["foo", "var", "hoge"]@capital.p # ["Foo", "Var", "Hoge"]
In list chains, evaluated nil
elements are ignored.
Combining to if expression, you can filter generated elements (If).
# nil elements are ignored (because `i if i.even? == nil` if `i.even?` is false)
(1:10)@{|i| i if i.even?}.p # [2, 4, 6, 8]
Technically, _iter
method of left-side value is called to obtain its iterator.
Built-in objects generates its iterator respectively.
# int: 1 to self
3@{|e| e} # [1, 2, 3]
# str: each character
"abc"@{|e| e} # ["a", "b", "c"]
# range: each element within self
(1:7:2)@{|e| e} # [1, 3, 5]
# arr: each element in self
[1, 2, 3]@{|e| e} # [1, 2, 3]
# obj: each pair of self
{a: 1, b: 2}@{|e| e} # [["a", 1], ["b", 2]]
# map: each pair of self
%{'a: 1, 'b: 2}@{|e| e} # [["a", 1], ["b", 2]]
_iter
is nothing to do with indexing!
_iter
describes how to iterate over the value in loop.
On the other hand, at
(used for indexing) describes how to show internal structures of the value. obj[0]
is usually obj._iter.next
, but not always.
# Int#at returns n-th bit of self
4[0] # 0
# Int#_iter returns iterator of 1 to self
4._iter.next # 1
Obj#_iter
ignores private keys and inherited keys. This prevents chain's unexpected behaviour.
If _iter
returned private keys, meta-properties not intended to be used as data would get into list chains.
# you don't be bothered by _name prop!
infixes := {_name: "infixes", add: {\1 + \2}, sub: {\1 - \2}, mul: {\1 * \2}, div: {\1 / \2}}
infixes@{|k, v| "#{k}: #{v(6, 3)}"}@p
# add: 9
# div: 2.0
# mul: 18
# sub: 3
Also, if _iter
returned inherited keys, you would have to eliminate prototype's methods.
# you don't have to worry about tons of Obj's methods!
Obj.keys.p # ["A", "B", "S", "acc",...]
obj := {a: 1, b: 2, c: 3}
obj@p
# ["a", 1]
# ["b", 2]
# ["c", 3]
# on the other hand, you can access Obj's method by `at`
obj['A].p # {|self| @{|| \}}
The receiver is each element of left-side value iterator. Also, returned value of previous call is passed to 2nd argument (In short, it's reduce!).
# reduce chain can hold initial value.
[1, 2, 3]$(0){|acc, i| acc+i} # 6
# same as above
[1, 2, 3]$(0)+ # 6
Technically, _iter
method of left-side value is called to obtain its iterator.
Additional context can be prepended by main chain context.
There are 3 kinds of additional chain context(&
, =
, ~
).
Thus, there are 9 kinds (3 additional * 3 main) of context.
This chain ignores call and return nil
if its receiver is nil
.
# nil.capital.puts # NoPropErr: property `capital` is not defined.
nil&.capital.puts # nil
[1, 2, nil, 4]&@F.puts # [1.000000, 2.000000, 4.000000]
This chain returns receiver instead if returned value is nil
or the call raises an error.
(1:16)~@{|i| ['fizz][i%3] + ['buzz][i%5]}.puts # [1, 2, "fizz", 4, "buzz", ..., "fizzbuzz"]
(3:20)~$([2]){|acc, n| [*acc, n] if acc.all? {|p| n % p}}.puts # [2, 3, 5, ..., 19]
# (Of course you can use built-in prime function)
20.select {.prime?}.puts # [2, 3, 5, ..., 19]
# rollback error
6~./(0) # 6
This is useful for logics like .{|x| f(x) if cond else x}
. You can rewrite this to ~.{|x| f(x) if cond}
.
This chain keeps returned nil
value.
This is useful only in list context, which removes returned nil
.
(1:10)@{|i| i if i.even?}.puts # [2, 4, 6, 8]
(1:10)=@{|i| i if i.even?}.puts # [nil, 2, nil, 4, nil, 6, nil, 8, nil]
Because that's why Pangaea was made!
A main purpose of Pangaea design is "Can chain context shorten one-liner effectively?".
(In some cases, @
and $
is shorter than map
, filter
, and reduce
)
Chains can take 1 argument for specific use.
List chain can only generate an array by default. Chain argument enables to convert generated array into specific types.
# arr by default
(?a:?d)@{|c| [c, .uc]} # [["a", "A"], ["b", "B"], ["c", "C"]]
# convert to obj
(?a:?d)@({}){|c| [c, .uc]} # {"a": "A", "b": "B", "c": "C"}
# convert to map
(?a:?d)@(%{}){|c| [c, .uc]} # %{"a": "A", "b": "B", "c": "C"}
Technically, the chain argument's digest
method is called to convert the evaluated array (Metaprogramming).
Chain argument is used for an initial accumulator.
If no arguments are passed, the initinal value is nil
.
# initial accumulator: "initial"
(?a:?e)$("initial")+ # "initialabcd"
# initial accumulator: nil
(?a:?e)$+ # "abcd" (note that nil + "a" == "a")
If chain does not have a receiver, it uses the 1st argument of the current function instead. This is handy for property calls in methods (note that the receiver is the 1st argument of the method (Function)).
# anonymous chain in a function
# .name is same as o.name
showName := {|o| .name.p}
showName({name: "Taro"}) # Taro
# property call of a method
square := {
side: 10,
# .side is same as self.side
area: m{.side ** 2},
}
square.area # 100
If anonymous chain were not permitted,
it would be annoying that you have to write self
anywhere you refer properties and call private methods.
There was another option to omit self
; self
properties can be referred as variables in self
's methods. But this confuses properties and local variables.
# REJECTED SYNTAX
Square := {
# self.side can be referred as side
area: m{
side ** 2
},
new: m{|side|
# Is side a method? or a local variable?
"side is #{side}".p
square.bear({side: side})
},
}