|
# A new language based off of ruby |
|
class X |
|
|
|
accessor :foo, :bar # creates methods like `def a() @a`, `def a=(value) : @a = value`, `private self.@a` |
|
|
|
# which naming is better? |
|
|
|
reader :a, :b, :c |
|
writer :y, :z |
|
|
|
# I like this, and it's consistent with the |
|
|
|
values format I like below |
|
reader a, b, c |
|
writer a, b, c |
|
|
|
getter :a, :b, :c |
|
setter :y, :z |
|
|
|
# |
|
# getting/setting default values |
|
# which naming is better??? |
|
# |
|
|
|
reader :x, -> { @x ??= "Hello" } |
|
writer :x, ->(value) { @x = value } |
|
|
|
reader x => @x ??= "Hello" |
|
writer x(value) => @x = value |
|
|
|
# this one is like the above, but the value is implicit rather than explicit |
|
reader x => @x ??= "Hello" |
|
writer x => @x = value |
|
|
|
# this one is like the above, but showing how multiple accessors can be defined on a single line |
|
reader x => @x ??= "Hello", y => @y ??= "Yello", foo, bar |
|
writer x => @x = value ?? "Hello", y => @x = value ?? "Yello", foo, bar, baz |
|
|
|
getter :x, -> { @x ??= "Hello" } |
|
setter :x, ->(value) { @x = value } |
|
|
|
# Notes: some ideas for shorthand for @x |
|
reader x => @ ??= "Hello" |
|
writer x => @ = value |
|
|
|
reader x => attribute ??= "Hello" |
|
writer x => attribute = value |
|
|
|
reader x => attr ??= "Hello" |
|
writer x => attr = value |
|
|
|
get x => @ ??= "Hello" |
|
set x => @ = value |
|
|
|
x { get => @ ??= "Hello", set => @ = value } |
|
x => @ ??= "Hello" |
|
|
|
# |
|
# multi-line reader/writer |
|
# rather than mapping assignment to def x=, just continue to use reader/writer instead |
|
# |
|
|
|
reader x |
|
@ ?? do_some_things |
|
end |
|
|
|
reader x |
|
attribute ?? do_some_things |
|
end |
|
|
|
writer x |
|
@ = do_some_things_with(value) |
|
end |
|
|
|
writer x |
|
attribute = do_some_things_with(value) |
|
end |
|
|
|
# |
|
# single line method definitions |
|
# |
|
|
|
# I like this because method is the same length as reader/writer |
|
method x => @x ??= "Hello" |
|
method x=(value) => @x = value ?? "Hello" |
|
|
|
def x() @x ??= "Hello" |
|
def x=(value) @x = value |
|
|
|
# winner! |
|
def x() => @x ??= "Hello" |
|
def x=(value) => @x = value |
|
|
|
def x() -> @x ??= "Hello" |
|
def x=(value) -> @x = value |
|
|
|
def x() : @x ??= "Hello" |
|
def x=(value) : @x = value |
|
|
|
def x ->() { @x ??= "Hello" } |
|
def x= ->(value) { value } |
|
|
|
# |
|
# constructors |
|
# |
|
|
|
# set attributes directly using positional params |
|
init(@a, @b, @c) |
|
|
|
# setting attributes directly using named params |
|
init(@x x:, @y y:, @z z:) # X.new(x: 1, y: 2, z: 3) |
|
|
|
# or, would this be better? |
|
init(x: @x, y: @y, z: @z) # X.new(x: 1, y: 2, z: 3) |
|
|
|
# and then have optional/default values: |
|
init(x: @x = 1, y: @y ||= 2, z: @z ??= :hello) # X.new(x: 1, y: 2, z: 3) |
|
|
|
init() |
|
|
|
# |
|
# methods |
|
# |
|
|
|
def hello |
|
do_some_stuff |
|
|
|
# method overloading |
|
def hello(a) |
|
do_some_other_stuff |
|
|
|
def hello(a = 7) # can't do this as the optional param makes it hide hello() |
|
|
|
def another_method => do_another_thing |
|
|
|
# if end is optional, it means we can't have nested classes and methods maybe just have optional ends for defs? |
|
|
|
# |
|
# syntactic sugar |
|
# |
|
|
|
i ||= 1 # sets i to 1 if i is nullable |
|
i ??= 1 # sets i to 1 if i is empty |
|
|
|
# |
|
# default method parameter values |
|
# |
|
|
|
# option arguments - optional arguments must always go last |
|
# a == 1 if a is not supplied |
|
def method(a = 1) |
|
|
|
# default if parameter is nullable - params are required for default values, optional arguments must go after |
|
# a == 1 if a is nullable |
|
def method(a ||= 1) |
|
|
|
# default if parameter is empty - params are required for default values, optional arguments must go after |
|
# a == 1 if a is empty |
|
def method(a ??= 1) |
|
|
|
# can also set instance variable |
|
def method(@a = 1) |
|
def method(@a ||= 1) |
|
def method(@a ??= 1) |
|
|
|
|
|
|
|
# |
|
# nullable/boolean objects (trinary logic, trivalent, ternary, or trilean) |
|
# can only be one of nil, true, false |
|
# |
|
|
|
NullPerson.new == nil # true |
|
TruePerson.new == true # true |
|
FalsePerson.new == false # true |
|
|
|
# |
|
# switch statements |
|
# can we introduce some of the C# switch matching funcationality? |
|
# |
|
|
|
case var |
|
when User u and u.id == 123 : u |
|
when AccessToken t : t |
|
end |
|
|
|
# I also like the use of the parenthases for each case in this example inspired by bash: |
|
case os |
|
Linux) do_something_1 |
|
FreeBSD|OpenBSD) do_something_2 |
|
SunOS) do_something_3 |
|
*) do_default |
|
end |
|
|
|
end |
|
|
|
# extendable methods |
|
# methods can be extended instead of overridden |
|
|
|
class A |
|
def some_method |
|
puts "before entension" |
|
extension |
|
puts "after extension" |
|
end |
|
|
|
# This method doesnt use the `extension` keyword |
|
def some_other_method |
|
puts "in some other method" |
|
end |
|
end |
|
|
|
class B > A |
|
extend some_method |
|
puts "in extension" |
|
end |
|
|
|
extend some_other_method |
|
puts "in extension" |
|
end |
|
end |
|
|
|
B.new.some_method |
|
# "before entension" |
|
# "in extension" |
|
# "after extension" |
|
|
|
# if the base method has no `extension` keyword, no code can be inserted from the subclass |
|
B.new.some_other_method |
|
# "in some other method" |
|
|
|
# decorators |
|
|
|
example 1: decorators wrap objects |
|
|
|
decorator A |
|
# decorates an object with additional properties or methods |
|
def m2 => 'hi!' |
|
end |
|
|
|
class B |
|
def m1 => 'blah!' |
|
end |
|
|
|
c = A.decorate(B.new) |
|
c.m1 # 'blah!' |
|
c.m2 # 'hi!' |
|
|
|
# refinements |
|
|
|
example 1: refinements insert methods into the call chain of specific objects within a given context |
|
|
|
refinement TemporaryPatch |
|
decorate Hash |
|
def my_refined_method => '' |
|
end |
|
end |
|
|
|
class Z |
|
using TemporaryPatch |
|
def do_stuff => {}.my_refined_method |
|
end |
|
|
|
example 2: refinements can override methods |
|
|
|
refinement TemporaryPatch |
|
decorate A |
|
def m1 => 'hi!' |
|
end |
|
end |
|
|
|
class A |
|
def m1 => 'hello' |
|
end |
|
|
|
class B |
|
using TemporaryPatch |
|
def m2 => A.new.m1 # 'hi!' |
|
end |
|
|
|
# class inheritance |
|
|
|
C > B > A |
|
v v v |
|
+---+ +---+ |
|
| | | | |
|
+---+ +---+ |
|
|
|
|
|
# static inheritance |
|
static method inheritance/overrides |
|
static property inheritance/overrides |
|
static methods and properties can call the base method/property |
|
|
|
class B < A |
|
self.method() |
|
# which one? or all? |
|
base() # calls the current method on base? |
|
base.method() # calls the specific method on base |
|
base[A]() # calls a specific method from a specific ancestor -- does this allow multiple inheritance??? |
|
end |
|
end |
|
|
|
# multiple inheritance |
|
|
|
class C < B, A, X, Y, Z |
|
# inherited methods are overridden from right to left (eg B overrides A) |
|
# method overriding occurs when method signatures match |
|
|
|
# base methods of each class can be accessed like this: |
|
def overridden_method |
|
base[B]() |
|
base[A]() |
|
base[X]() |
|
base[Y]() |
|
base[Z]() |
|
base[B].overridden_method |
|
base[A].overridden_method |
|
base[X].overridden_method |
|
base[Y].overridden_method |
|
base[Z].overridden_method |
|
end |
|
end |
|
|
|
# interfaces and abstract classes |
|
|
|
|
|
# generics (covariance and contravariance?) |
|
|
|
# type conversion |
|
|
|
converting a base class into a subclass, or a subclass into a sibbling subclass. |
|
need a way to pass the additional required values |
|
does this construct indicate that all subclasses are mearly wrappers around parent classes? |
|
|
|
class Subclass |
|
init(a, b, c) |
|
super(a, b) |
|
@c = c |
|
# normal init stuff |
|
end |
|
|
|
init(from ParentClass, c) |
|
this/self is the parent object |
|
# a and b should already be set on parent class at this point |
|
# set the additional attributes here |
|
@c = c |
|
end |
|
end |
|
|
|
Subclass.new(parentObj, c) |
|
|
|
# higher order functions - passing methods to functions |
|
|
|
example 1: passing methods in the same context |
|
|
|
def m1 => 'hi!' |
|
def m2(func) => func() |
|
|
|
m2(&m1) # 'hi!' |
|
m2(-> { 'hi!' }) # 'hi!' |
|
m2(proc { 'hi!' }) # 'hi!' |
|
|
|
example 2: calling methods on enumerated objects - not sure what the correct syntax should be |
|
|
|
[1, 2, 3].map(&:to_s) # ['1', '2', '3'] |
|
|
|
|
|
example 3: recusive calls to the function |
|
# self is the function |
|
# not sure which syntax to use |
|
f = (a, b) => a + b > 0 ? self(a - 1, b - 1) : 0 |
|
f = ->(a, b) a + b > 0 ? self(a - 1, b - 1) : 0 |
|
f = ->(a, b) { a + b > 0 ? self(a - 1, b - 1) : 0 } |
|
|
|
|
|
# compiling and type checking |
|
|
|
example 1: |
|
def m1(b) => b.m2 |
|
|
|
m1(a) |
|
|
|
in the above example, object `b` must have the `m2` method defined. |
|
|
|
if object `a` does not have the `m2` method defined, a compile error is raised |
|
|
|
|
|
example 2: |
|
def m1(c) |
|
c.m2 |
|
m3(c) |
|
end |
|
|
|
def m3(d) => d.m4 |
|
|
|
m1(a) |
|
m3(b) |
|
|
|
in the above example, |
|
object `d` must have the `m4` method defined. |
|
object `c` must have the `m4` and `m2` methods defined. |
|
|
|
if object `a` does not have the `m4` and `m2` methods defined, a compile error is raised |
|
if object `b` does not have the `m4` method defined, a compile error is raised |
|
|
|
|
|
# Singular Piping for Enumerators |
|
|
|
A method can return an enumerator, and enumerators can be chained |
|
|
|
In ruby, and probably most languages, the first enumerator completes, then the second, then the third, and so on... |
|
|
|
I would like the ability to pipe a value to the next enumerator per yield, rather than on completion of the iteration. |
|
`break` in any chained enumerator could end the entire enumeration |
|
`next` would move to the next item |
|
`yield` would yield the value to the next iterator in the chain |
|
|
|
So something like this would be handy: |
|
|
|
``` |
|
[1, 2, 3, 4, 5] -> select {|v| v > 3 } -> each_with_index -> collect {|v, i| [v, i] } -> each {|v, i| puts v } |
|
``` |
|
|
|
With this construct, the value of `1` gets puts before `2` gets processed by the chain |
|
The `->` indicates the return value of the current enumerator will be yielded to the next enumerator in the chain |
|
|
|
Alternatively, using the `|>` |
|
|
|
``` |
|
[1, 2, 3, 4, 5] |> select {|v| v > 3 } |> each_with_index |> collect {|v, i| [v, i] } |> each {|v, i| puts v } |
|
``` |
|
|
|
The `->` or `|>` operator enumerates an array/collection (or value?), passes it to the next method in the chain, |
|
and waits for the method to complete. Once the method completes, the next element is passed to the next method in the chain, |
|
and again waits. This process is continued until all elements have been enumerated. |
|
|
|
In theory, each element could be processed in parallel! |
|
|
|
The above would be equivalent to: |
|
|
|
[1, 2, 3, 4, 5].each do |v1| |
|
[v1].select do |v2| |
|
if v > 8 |
|
[v2].each_with_index do |v3, i3| |
|
[[v3, i3]].collect do |v4, i4| |
|
[[v4, i4]].each do |v5, i5| |
|
puts v5 |
|
end |
|
[v4, i4] |
|
end |
|
[v3, i3] |
|
end |
|
end |
|
|
|
v > 8 |
|
end |
|
v1 |
|
end |
|
|
|
The return type would be [1, 2, 3, 4, 5] because of the initial `each` |
|
The return values are basically ignored... which possibly isnt the most intuitive behaviour |
|
But at the same time, this is about processing the values in the initial enumerator... not returning or extracting values |
|
Think of it like an ETL pipeline! |
|
|
|
|
|
Additionally, it would be nice to have a construct where values are yielded from only the first iterator. |
|
So rather than |
|
```ruby |
|
[1, 2, 3, 4, 5, 6].each do |v| |
|
do_something(v) |
|
do_another_thing(v) |
|
end |
|
``` |
|
|
|
you could do: |
|
[1, 2, 3, 4, 5, 6].each -| do_something -| do_another_thing # [1, 2, 3, 4, 5, 6] |
|
|
|
[1, 2, 3, 4, 5, 6].each -| do_something -| do_another_thing -| select {|v| v > 3 } # [1, 2, 3, 4, 5, 6] |
|
|
|
[1, 2, 3, 4, 5, 6].each -| do_something -| select {|v| v > 3 } -| do_another_thing # [1, 2, 3, 4, 5, 6] |
|
|
|
For the last two, the `select` doesn't have any effect as the return value is coming from the initial iterator |
|
The `-|` is like a wall, the value from the iterator goes in, but the return value is ignored |
|
Likewise, `break` will only break the chain to the last `-|` |
|
|
|
|
|
|
|
|