A subset is a definition which specifies the upper limit of inheritance, with optional argument validation.
subset Integer < Number { |n| n.is_int }
subset Natural < Integer { |n| n.is_pos }
subset EvenNatural < Natural { |n| n.is_even }
func foo(n < EvenNatural) {
say n
}
foo(42) # ok
foo(43) # failed assertion at run-time
In some sense, a subset is the opposite of a type. For example, let's consider the following class hierarchy:
class Hello(name) {
method greet { say "Hello, #{self.name}!" }
}
class Hi < Hello {
method greet { say "Hi, #{self.name}!" }
}
class Hey < Hi {
method greet { say "Hey, #{self.name}!" }
}
If we declare a function that accepts a subset of Hi
, it will accept Hello
, but it cannot accept Hey
:
func greet(obj < Hi) { obj.greet } # `Hi` is the upper limit
greet(Hi("Foo")) # ok
greet(Hello("Bar")) # ok
greet(Hey("Baz")) # fail: `Hey` is too evolved
On the other hand, if we use Hi
as a type assertion, it will accept Hey
, but not Hello
:
func greet(Hi obj) { obj.greet } # `Hi` is the lower limit
greet(Hi("Foo")) # ok
greet(Hey("Baz")) # ok
greet(Hello("Bar")) # fail: `Hello` is too primitive
Subsets can also be used for combining multiple types into one type, creating an union type:
subset StrNum < String, Number
func concat(a < StrNum, b < StrNum) {
a + b
}
say concat("o", "k") # ok
say concat(13, 29) # 42
say concat([41], [42]) # runtime error