Each Pangaea object inherits one object (prototype).
New inherited objects can be created by bear
method.
parent := {a: 1}
# inherit parent
child := parent.bear({b: 2})
# get prototype by proto method
child.proto == parent # true
Object literal is a syntax sugar of Obj.bear
.
# they are equivalent
parent := {a: 1}
parent := Obj.bear({a: 1})
If an object does not have the same property, prototype's one is used instead (Calls).
parent := {a: 1, b: 2}
child := parent.bear({b: 10, c: 20})
# call child's property
child.c # 20
# if child does not have the property, parent's one is used
child.a # 1
# if both child and parent have the property, child's one is used
child.b # 10
# if parent does not have the property, parent's prototype's one is used
# (`keys` is defined in Obj)
child.keys # ["b", "c"]
You can find the specific property's owner by Obj#which
.
child.which('a) == parent # true
child.which('c) == child # true
child.which('keys) == Obj # true
Obj#bear
is powerful but buggy to create children with specific properties.
You can use Kernel._init
to create object constructors instead.
Person := {
# make a constructor of Person
new: _init('name, 'age),
canDrink?: m{.age >= 20},
hello: m{"I am #{.name}".p},
}
taro := Person.new("Taro", 20)
jiro := Person.new("Jiro", 18)
taro.hello # I am Taro
jiro.hello # I am Jiro
taro.canDrink? # true
jiro.canDrink? # false
Since Pangaea objects are immutable, state changes are described as new objects.
Obj#bro
generates updated new object (bro
means brother, because self and the new value have the same prototype).
Account := {
new: _init('name, 'deposit),
withdraw: m{|amount| .bro({deposit: .deposit - amount})},
}
account := Account.new("Taro", 10000)
account.deposit # 10000
account := account.withdraw(3000)
account.deposit # 7000
bear
generates a self's child. If deposit
is called twice, returned value is a prototype of a prototype of account
. This makes prototype chains verbose.
For the same reason, the most of built-in methods returns bro
of self (ex: infix +
).
Using Account.new
in deposit
seems good. But it does not work in Account
's children.
Account := {
new: _init('name, '_deposit),
# use new instead of bro
# NOTE: if you use self.new, the problem of bear comes up again
withdraw: m{|amount| Account.new(.name, ._deposit - amount)},
}
# inherit Account
GreatAccount := Account.bear({
deposit: m{|amount| GreatAccount.new(.name, ._deposit - amount)},
})
account := GreatAccount.new("Taro", 10000)
account := account.deposit(2000)
account._deposit # 12000
account := account.withdraw(3000) # withdraw returns Account!
account := account.deposit(2000) # NoPropErr: property `deposit` is not defined.
Since Pangaea does not have classes, prototypes should be able to use as values. For that reason, prototypes of literal objects can be used as zero values.
# Int is the prototype of integers. So Int itself should be an integer!
1.proto # Int
# Int behaves as integer zero value 0
Int + 3 # 3
# Str behaves as string zero value ""
"abc" + Str # "abc"