Skip to content

Instantly share code, notes, and snippets.

@gosukiwi
Last active February 25, 2016 19:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gosukiwi/e5f0e8f1e7fe108c9054 to your computer and use it in GitHub Desktop.
Save gosukiwi/e5f0e8f1e7fe108c9054 to your computer and use it in GitHub Desktop.
alpha_sapphire

alpha_sapphire is a toy programming language concept. Designed for minimal syntax and fully object-oriented expressiveness a-la Smalltalk and Ruby.

Things trying to archieve:

  • Everything should be an object
  • Meta-programming should be easy
  • Syntax shold be minimal yet expressive
  • Should be inspired by Smalltalk and Ruby
  • Should give the power to the programmer, not limit him

How it looks like

Use the concept of code-blocks (lambdas) for expressive OOP power. They are a very important building block in the language.

This is an example of a class:

# `class` is a method of `identifier`
Foo.class do
  # Attribute
  bar = 'baz'
  
  # Method
  bar = do
    # arguments is an array with all parameters passed to the block
    puts arguments # In this case, it's []
  end
  
  # Methog taking named optional parameters
  greet = do |name:, age: 18|
    new_age = age + 1
    "Hello, #{name}! You will be #{new_age} next year!"
  end
end

a = Foo.new
a.bar   # 'baz'
a.bar() # parens are not required if no argument is needed or is optional
a.greet(name: 'Mike') # They are mandatory otherwise

We could also define a class like this:

"hello_#{name}".class do
  attr_reader('bar')
  foo = do
    @bar = 11 # instance variable
  end
end

As long as the object implements to_identifier. Classes can be re-opened as desired. Note that instance variables are defined using @ like Ruby. attr_reader could be implemented like this:

Object.class do
  attr_reader = do |name|
   # define instance method named `name` 
   # and return the variable at that position
   # using self.variables()[name]
   add_method(name) do
     variables[name]
   end
  end
end

add_method and variables are magic interpreter-land functions already in Object. Some more methods could be self.classname, self.methods, self.as_class.add_method, self.as_class.variables, self.as_class.methods and super.

Classes have an instance_context, each instance gets it's own copy. On the other side, they all share the same class_context. You can access the class context from an instance as such: my_obj.as_class. That's the "static" stuff.

Inheritance:

Foo.class do end
Bar.class(Foo) do
  # I inherit from Foo
end

A mixin is a set of transformations to a class and/or instances of that class.

Foo.class do
  include(SomeModule)
end

SomeModule.module do |instance_context|
  instance_context.define_method('some_method') do ... end
  instance_context.as_class.define_method('some_static_stuff') do ... end
end

Note that we are passed the instance_context of the Class, all instances will have a copy of this. They all share the class_context which is accessed from instances as instance_context.as_class or simple self.as_class.

Methods should allow named optional arguments like Ruby.

Foo.class do
  bar do |name:, age: 18|
    "You are #{name} and you are #{age} years old."
  end
end

o = Foo.new
o.bar(name: 'Mike') # "You are Mike and you are 18 years old."
o.bar(name: 'Mike', age: 22) # "You are Mike and you are 22 years old."
o.bar() # ArgumentError: parameter `name` not specified.

Minimal constructs means that things like while and for can be expressed with primitives rather than "embedded" into the language. They can be replaced with just methods and code-blocks. For example:

oranges.each do |orange|
  do_something_with(orange)
end

In that case, each is just a method that takes a code-block as argument and calls it once per orange.

You can define your own iterators, standard functional-programming collection manipulation methods should be included, they are very expressive. Things like: each, map, select, collect, reduce, etc.

alpha_sapphire gives you a lot of power. It tries to make as little distinction between the language and the programmer as possible. Conditionals, most language's if statement, could be used like this:

bar = false
bar.false? do puts "Hello!" end
bar.true?  do puts "This will not run" end

And could be implemented as such:

Boolean.class do
  true? do |block|
    value && block.call
  end
end

&& is short-circuit, so it will not call the block unless it's truthy (the only things which are false is an instance of False and an instance of Nil.

The reason why I prefer && over and is that the first one is more mathy. We are making a boolean operation afterall! So if I wrote a story and say: "Mary and Mike" it feels like I'm grouping them, but if I do "Mary && Mike" I know that given the two of them, I want a single result (that didn't sound very good). I'm performing an operation, not grouping things.

Exceptions work like this

Foo.class do
  bar do
    blah blah blah
  rescue(SomeError) do |e|
    # e is an instance of SomeError
  end
end

In that case, rescue is a special keyword which will be called by the interpreter if there's an Exception. The rescue keyword can only be unsed inside a block. There's also finally. The full syntax is below:

Foo.class do
  bar do
    blah blah blah
  rescue(SomeError) do |e|
    # e is an instance of SomeError
  rescue(OtherError) do |e|
    # another error 
  finally do
    # always executed
  end
end

Dictionaries

a = { a: 1, b: 2, f(x): g(y), do 2 end: do "bar" end }

Lambdas

lambda = do |x| x + 1 end
two = lambda.call(1) # 2
# short-form
two = lambda(1)

Method scope

Look from the block, up. If inside a class, look in instance context if not found in block. Travel up the hierarchy until Object. If the name is @foo then it's an instance variable so start looking in the instance context. To access class variables use self.as_class.my_variable, it looks in class context and up the hierarchy.

Primitives

  • Boolean (Use false instead of nil)
  • Object (All classes inherit from this implicitly)
  • Block (A block of code, maybe?)
  • Integer
  • Float
  • String
  • Symbol/Identifier
  • Array
  • Hash/Dictionary

Operators and Presedence

Follow Ruby's: http://stackoverflow.com/questions/21060234/ruby-operator-precedence-table

!     (unary NOT)
**    (exponentiation)
-     (unary minus)
* / % (multiplication, division, remainder)
+ -   (addition, substraction)
&&    (binary AND)
||    (binary OR)

Custom binary operators

Foo.class do
 val = 1
 
 op_+ do |other|
   @val + other.to_i
  end
end

foo = Foo.new
# sugar for foo.op_+(2)
foo + 2 # 3

Reflection

foo.send(:message, [arg1, arg2])
foo.methods # show all direct methods
foo.methods(:all) # show all, even inherited

Maybe Do

Optional dots?

"Hello, world!" print
# Same as
"Hello, World!".print
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment