Goby is an object-oriented language written in Go, inspired by Ruby and designed for cloud microservices.
Getting Started
Hello world
hello.gb
class Greet
attr_accessor :audience, :head, :tail
def initialize
@head = "Hello, "
@tail = "!"
end
def name
audience.name
end
def say
puts head + name + tail
end
end
module MyName
attr_reader :name
def initialize
@name = self.class.to_s
end
end
class World
include MyName
end
greet = Greet.new
greet.audience = World.new
greet.say
Then run with Goby:
$ goby hello.gb
#=> Hello, World!
REPL (igb)
$ goby -i
reset: Reset the VM stateexit: Exit the interactive REPLhelp: Show interactive helpCtrl+C: Cancel the current input buffer or exit if at top level
See the igb manual and test script for readline navigation and history.
Variables
Local Variables
zip101 = "233-7383"
magic_number = 42
Variable names follow [a-z][a-z0-9_]+ (snake_case).
Instance Variables
module State
def initialize(state)
@state = state # declaring an instance variable by assignment
end
def show
@state # accessible from other instance methods
end
end
state = State.new "success"
state.show
#=> success
Instance variables follow @[a-z][a-z0-9_]+ (prefixed with @).
Multiple Assignment
# array literal
a, b, c = [1, 2, 3]
# array with '*'
a = [1, 2, 3]
x, y, z = *a
# array literal with '*'
a, b, c = *[1, 2, 3]
# bare assignment: unsupported
a, b, c = 1, 2, 3 #=> unexpected 3 Line: 0
Discard Variable (_)
# '_' is write-only
a, _ = [1, 2]
Class Variables
Not supported.
Global Variables
Not supported.
Methods
Defining and Calling Methods
def foo_bar?(baz)
if baz == "Hi, Goby!"
true
else
false
end
end
foo_bar? "Hi, Goby!" #=> true
Method names follow [a-z][a-z0-9_]+ (snake_case). Parentheses () can be omitted when calling methods without arguments. Methods ending in ! are not supported.
Method Parameter Ordering
def foo(normal, default="value", hash={}, ary=[], keyword:, keyword_default:"key", *sprat)
end
If a parameter has a default value, it can be omitted upon invocation. Parameter order in method definitions follows these rules:
- Required positional parameters (e.g.
a) - Positional parameters with defaults (e.g.
a=1) - Optional collection parameters (e.g.
ary=[]orhs={}) - Required keyword arguments (e.g.
kwd:) - Keyword arguments with defaults (e.g.
kwd: 1orhsh: {key: "value"}) - Splat parameter (e.g.
*sp)
Violating this sequence causes a compile-time syntax error.
Keyword Arguments
def foo(process:, verb: :GET, opt:{ csp: :enabled }, ary: [1, 2, 3])
end
Return Values
PI = 3.14
def area(radius)
radius * PI # returns the result of evaluation
end
area 6 #=> 18.84
Returning Multiple Values
def my_array
[1, 2, 3]
end
my_array #=> [1, 2, 3]
Instance Methods
module Foo
def bar # defining instance method
puts "bar"
end
def baz(count, email: "[email protected]")
count.times do
puts email
end
end
end
foo = Foo.new
foo.bar #=> bar
foo.baz(3) #↓
[email protected]
[email protected]
[email protected]
Singleton Methods (Class Methods) #1
str = "Goby"
def str.foo #1 singleton method on the object
self * 2
end
str.foo
#=> GobyGoby
Singleton Methods #2
module Foo
def self.bar #2 singleton method with `self.`
92
end
end
Singleton Methods #3
module Foo
def Foo.bar #3 singleton method with a class name (unrecommended)
88
end
end
Singleton Methods #4
module Foo end
def Foo.bar #4 singleton methods outside the Foo
9999
end
Foo.bar #=> 9999
Attribute Accessors
class Foo
attr_accessor :bar, :baz
def initialize
@bar = 42
@baz = 99
end
end
foo = Foo.new
foo.bar = 77
foo.baz = 88
Declare attribute reader, writer, or accessor macros inside classes or modules:
attr_accessorattr_readerattr_writer
Private Methods (WIP)
class Foo
def bar
42
end
def _baz # leading '_' means private method
99
end
end
Modules and Classes
Module Definition and include
module Foo
def foo
"Foo's instance method"
end
end
class Bar
include Foo # to include Foo
end
Bar.new.foo #=> Foo's instance method
Module names follow [A-Z][A-Za-z0-9_]+ (UpperCamelCase). Modules cannot be inherited directly.
Module Definition and extend
module Foo
def foo
"Foo's instance method will be a singleton method"
end
end
class Bar
extend Foo # to extend Foo
end
Bar.foo #=> Foo's instance method will be a singleton method
extend imports instance methods of a module as singleton (class) methods on the target class/module.
Module Instantiation
module Foo #module definition
def foo
99
end
end
Foo.new.foo #=> 99
In Goby, modules can also be instantiated directly via .new (e.g., Foo.new).
Class Definition and Inheritance
class Foo # class definition
def bar
99
end
end
class Baz < Foo # inheritance
end
Baz.new.bar #=> 99
Class names follow UpperCamelCase. Single inheritance is supported using <.
Constants
HTTP_ERROR_404 = 404
HTTP_ERROR_404 = 500 # error
Constants follow UPPER_SNAKE_CASE. Constants are globally scoped.
Reopening Classes & Modules
class Foo
def bar
99
end
end
class Foo
def bar # redefining is possible
77
end
end
Namespaces
class Foo
module Bar
MAGIC = 99
def baz
99
end
end
end
Foo::Bar.new.baz # Use '::' for namespacing
Foo::Bar::MAGIC # Use '::' for namespacing
Loading Libraries
require
require("uri") # to activate URL class
u = URI.parse("http://example.com")
u.scheme #=> "http"
require_relative
require_relative("bar") # loading the local bar.gb
class Foo
def self.bar(x)
Bar.foo do |ten|
x * ten
end
end
def self.baz
yield(100)
end
end
Literals
Keywords
def, true, false, nil, if, elsif, else, case, when, return, self, end, while, do, yield, get_block, next, class, module, break
String Literals
"double quote"
'single quote'
Both single and double quotes are supported.
Symbol Literals
:symbol # equivalent to "symbol"
{ symbol: "value" }
Goby symbols (:name) belong to the String class.
Numeric Literals
year = 2018 # Integer
offset = -42 # Integer
PI = 3.14 # Float
G = -9.8 # Float
Array Literals
[1, 2, 3, "hello", :goby, { key: "value"}]
[1, 2, [3, 4], 5, 6]
Hash Literals
h = { key: "value", key2: "value2" }
h[:key2] #=> value2
Hash literal keys are typically symbols or strings.
Range Literals
(1..10).each do |x| # '..' represents a range
puts x*x
end
Booleans and nil
true # Boolean class
false # Boolean class
nil # Null class
!nil #=> true
In boolean contexts, only nil and false are falsy; all other values are truthy.
Operators
Arithmetic, Logical & Assignment Operators
+ # unary
** # power
- # unary
* / % # multiplication, division, modulus
+ - # addition, subtraction
! # logical inversion
> >= < <= # inequality comparison
== != # equality comparison, negative comparison
&& # logical AND
|| # logical OR
+= -= # shorthand of addition/subtraction
= # assignment
*Operator precedence is TBD
Other Operators
() # changing priority of interpretation
[] # array literal
* # multiple assignment
.. # range
*Operator precedence is TBD
Delimiters
class Foo; end # ';' to delimit
class Bar end # recommended
String Interpolation (WIP)
puts "Error: #{error_message}" # double quotation is required
Comments
puts "Goby" # comments
Use single-line # for comments and annotations.
TODOFIXMEOPTIMIZEHACKREVIEW
I/O
#putsBuilt-in special constants:
ARGV,STDIN,STDOUT,STDERR,ENV
Control Flow
if, else, elsif
def foo(str)
if str.size > 10
puts "too big!"
elsif str.size < 3
puts "too short!"
else
puts "moderate"
end
end
then keyword is not supported in if statements.
Break
def foo(tail)
(5..tail).each do |t|
if t % 2 == 0 && t % 5 == 0
puts "ouch!"
break # finish the block
else
puts t
end
end
puts "out of the block"
end
foo 20
#=> 5 6 7 8 9
#=> ouch!
#=> out of the block
Case / When
def foo(str)
case str
when "Elf"
puts "You might be Aragorn II!"
when "Aragorn"
puts "Long time no see, Aragorn!"
when "Frodo", "Sam", "Gandalf"
puts "One of us!"
else
puts "You're not yourself"
end
end
While
decr = 10
while decr do
if decr < 1
break
end
puts decr
decr -= 1
end
while loops and iteration blocks (do/end) handle repetitive execution.
Rescue
Under development. See #605.
Blocks and Procs
Blocks and Procs
def foo(ary: [1, 2, 3])
ary.each do |s| # start of the block with |block variable|
puts s
end # end of the block
end
Curly braces { } cannot be used to define code blocks; use do ... end.
yield
def foo
yield(10) # executes the block given
end
foo do |ten|
ten + 20
end
Block Objects and call
b = Block.new do
100
end
b.call #=> 100
Block.new accepts a block that can later be invoked with .call.
Passing Blocks
def baz
1000
end
class Foo
def exec_block(block)
block.call
end
def baz
100
end
end
b = Block.new do
baz
end
f = Foo.new
f.exec_block(b)
Passing Blocks with Arguments
b = Block.new do |arg, offset|
arg + 1000 - offset
end
b.call(49, 500) #=> 549
The get_block Keyword
def bar(block)
# runs the block object and the block arg simultaneously
block.call + get_block.call
end
def foo
bar(get_block) do # passes two blocks to `bar`
20
end
end
foo do
10
end
get_block is a built-in keyword that captures the passed block as a first-class Block object.
Closures
count = 0 # the declaration is used
b = Block.new do
count += 1 # the block looks preserving the `count`
end
class Foo
def bar(blk)
count = 9 # (does not affect)
puts blk.call # local variable is resolved to the one above
end
end
Foo.new.bar b #=> 1
Foo.new.bar b #=> 2
Foo.new.bar b #=> 3
Built-in Classes (Core)
Core classes that form the root object model.
Object
Bar.ancestors
#» [Bar, Foo, Object]
Bar.singleton_class.ancestors
#» [#<Class:Bar>, #<Class:Object>, Class, Object]
Object serves as the base class for objects and singletons.
Object.methods:!,!=,==,block_given?,class,exit,instance_eval,instance_variable_get,instance_variable_set,is_a?,methods,nil?,object_id,puts,raise,require,require_relative,send,singleton_class,sleep,thread,to_s,<,<=,>,>=,ancestors,attr_accessor,attr_reader,attr_writer,extend,include,name,new,superclassObject.new.methods:!,!=,==,block_given?,class,exit,instance_eval,instance_variable_get,instance_variable_set,is_a?,methods,nil?,object_id,puts,raise,require,require_relative,send,singleton_class,sleep,thread,to_s
Class
String.ancestors #=> [String, Object]
Class and Object share core dispatch mechanisms.
Class.methods:<,<=,>,>=,ancestors,attr_accessor,attr_reader,attr_writer,extend,include,name,new,superclass,!,!=,==,block_given?,class,exit,instance_eval,instance_variable_get,instance_variable_set,is_a?,methods,nil?,object_id,puts,raise,require,require_relative,send,singleton_class,sleep,thread,to_s
String
puts "Hello" + ' ' + 'world' #=> Hello world
Strings are fixed UTF-8 with full 4-byte multibyte support.
String.methods:fmt,- Remainder:
Class.methods
- Remainder:
"a".methods:!=,*,+,<,<=>,==,=~,>,[],[]=,capitalize,chop,concat,count,delete,downcase,each_byte,each_char,each_line,empty?,end_with?,eql?,fmt,include?,insert,length,ljust,match,new,replace,replace_once,reverse,rjust,size,slice,split,start_with,strip,to_a,to_bytes,to_d,to_f,to_i,to_s,upcase,- Remainder:
Object.new.methods
- Remainder:
Integer
37037 * 27 #=> 999999
Integer.methods:same asClass.methods1.methods:!=,%,*,**,+,-,/,<,<=,<=>,==,>,>=,even?,new,next,odd?,pred,ptr,times,to_f,to_float32,to_float64,to_i,to_int,to_int16,to_int32,to_int64,to_int8,to_s,to_uint,to_uint16,to_uint32,to_uint64,to_uint8- Remainder:
Object.new.methods
- Remainder:
Array
[1, "2", :card, [4, 5], { john: "doe" }]
Array.methods:same asClass.methods[1].methods:*,+,[],[]=,any?,at,clear,concat,count,delete_at,dig,each,each_index,empty?,first,flatten,include?,join,last,lazy,length,map,new,pop,push,reduce,reverse,reverse_each,rotate,select,shift,to_enum,unshift,values_at- Remainder:
Object.new.methods
- Remainder:
Hash
h = { key: "value" }
h = { "key": "value" } #=> error
h["key"] #=> value
h[:key] #=> value
Hash keys can be indexed with string or symbol identifiers.
Hash.methods:same asClass.methods{ key: "value" }.methods:[],[]=,any?,clear,default,default=,delete,delete_if,dig,each,each_key,each_value,empty?,eql?,fetch,fetch_values,has_key?,has_value?,keys,length,map_values,merge,new,select,sorted_keys,to_a,to_json,to_s,transform_values,values,values_at- Remainder:
Object.new.methods
- Remainder:
Range
(1..10).each do |i|
puts i ** 2
end
Range.methods:same asClass.methods(1..10).methods:!=,==,bsearch,each,first,include?,last,lazy,map,new,size,step,to_a,to_enum- Remainder:
Object.new.methods
- Remainder:
Block
b = Block.new do
100
end
b.call #=> 100
Block.methods:same asClass.methods(Block.new do end).methods:call- Remainder:
Object.new.methods
- Remainder:
Additional Built-in Classes
Float
1.1 + 1.1 # => -2.2
2.1 * -2.1 # => -4.41
Floating point literals like 3.14 or -273.15. Float is backed by Go float64.
Float.methods:same asClass.methods3.14.methods:!=,%,*,**,+,-,/,<,<=,<=>,==,>,>=,new,ptr,to_d,to_i- Remainder:
Object.new.methods
- Remainder:
Decimal
"3.14".to_d # => 3.14
"-0.7238943".to_d # => -0.7238943
"355/113".to_d
# => 3.1415929203539823008849557522123893805309734513274336283185840
a = "16.1".to_d
b = "1.1".to_d
e = "17.2".to_d
a + b # => 0.1
a + b == e # => true
('16.1'.to_d + "1.1".to_d).to_s #=> 17.2
('16.1'.to_f + "1.1".to_f).to_s #=> 17.200000000000003
Arbitrary-precision decimal backed by Go big.Rat. Instantiated via .to_d.
Decimal.methods:same asClass.methods(1.1).to_d.methods:!=,*,**,+,-,/,<,<=,<=>,==,>,>=,denominator,fraction,inverse- Remainder:
Object.new.methods
- Remainder:
Regexp
a = Regexp.new("orl")
a.match?("Hello World") #=> true
a.match?("Hello Regexp") #=> false
b = Regexp.new("😏")
b.match?("🤡 😏 😐") #=> true
b.match?("😝 😍 😊") #=> false
c = Regexp.new("居(ら(?=れ)|さ(?=せ)|る|ろ|れ(?=[ばる])|よ|(?=な[いかくけそ]|ま[しすせ]|そう|た|て))")
c.match?("居られればいいのに") #=> true
c.match?("居ずまいを正す") #=> false
Regex literals /pattern/ (WIP).
Regexp.methods:same asClass.methodsRegexp.new("^aa$").methods:==,match?- Remainder:
Object.new.methods
- Remainder:
MatchData
# numbered capture
'abcd'.match(Regexp.new('(b.)'))
#=> #<MatchData 0:"bc" 1:"bc">
# named capture
'abcd'.match(Regexp.new('a(?<first>b)(?<second>c)'))
#=> #<MatchData 0:"abc" first:"b" second:"c">
# converting to hash
» 'abcd'.match(Regexp.new('a(?<first>b)(?<second>c)')).to_h
#» { 0: "abc", first: "b", second: "c" }
Match captures use string keys. Key 0 holds the full matched string.
MatchData.methods:same asClass.methods'abcd'.match(Regexp.new('(b.)')).methods:captures,length,new,to_a,to_h- Remainder:
Object.new.methods
- Remainder:
File
f = File.new("../test_fixtures/file_test/size.gb")
f.name #=> "../test_fixtures/file_test/size.gb"
File.methods:basename,chmod,delete,exist?,extname,join- Remainder:
Class.methods
- Remainder:
File.new.methods:basename,chmod,close,delete,exist?,extname,join,name- Remainder:
Object.new.methods
- Remainder:
Go Interoperability Classes
GoMap
h = { foo: "bar" }
m = GoMap.new(h) # to pass values to Golang's code
h2 = m.to_hash
h2[:foo] #=> "bar"
GoMap.methods:same asClass.methodsGoMap.new.methods:get,set,to_hash- Remainder:
Object.new.methods
- Remainder:
Channel
c = Channel.new
1001.times do |i| # i start from 0 to 1000
thread do
c.deliver(i)
end
end
r = 0
1001.times do
r = r + c.receive
end
r #=> 500500
Channel wraps Go channels for concurrent message passing across threads.
Channel.methods:same asClass.methodsChannel.new.methods:close,deliver,new,receive- Remainder:
Object.new.methods
- Remainder:
Enumerators and Lazy Evaluation
Experimental library for lazy collection processing.
LazyEnumerator
# creating a lazy enumerator
enumerator = LazyEnumerator.new(ArrayEnumerator.new([1, 2, 3])) do |value|
2 * value
end
result = []
enumerator.each do |value|
result.push(value)
end
result #=> [2, 4, 6]
The .lazy method is available on Array and Range.
LazyEnumerator.methods:same asClass.methods[1, 2].lazy:each,first,has_next?,initialize,map,next- Remainder:
Object.new.methods
- Remainder:
ArrayEnumerator
iterated_values = []
enumerator = ArrayEnumerator.new([1, 2, 4])
while enumerator.has_next? do
iterated_values.push(enumerator.next)
end
iterated_values #=> [1, 2, 4]
ArrayEnumerator.methods:same asClass.methodsArrayEnumerator.new([1, 2, 3]).methods:has_next?,initialize,next- Remainder:
Object.new.methods
- Remainder:
RangeEnumerator
iterated_values = []
enumerator = RangeEnumerator.new((1..4))
while enumerator.has_next? do
iterated_values.push(enumerator.next)
end
iterated_values #=> [1, 2, 3, 4]
RangeEnumerator.methods:same asClass.methodsRangeEnumerator.new(1..2).methods:has_next?,initialize,next- Remainder:
Object.new.methods
- Remainder:
Special Classes
Boolean
true.class #=> Boolean
false.class #=> Boolean
Boolean carrier class for true and false. Cannot be instantiated directly.
Null
nil.class #=> Null
Class for nil. Cannot be instantiated directly.
Method
Special container class for user-defined Goby methods.
Diggable
Provides #dig traversal. Arrays and Hashes include Diggable.
[1, 2].dig(0, 1) #=> TypeError: Expect target to be Diggable, got Integer
Testing Framework
Spec
require "spec"
Spec.describe Spec do
it "fails and exit with code 1" do
expect(1).to eq(2)
end
end
Spec.run
Spec.methods:describe,describes,instance,run- Remainder:
Object.methods
- Remainder:
Spec.new.methods:describes,initialize,run,session_successful,session_successful=- Remainder:
Hash.new.methods
- Remainder:
Tips and Tricks
Inspecting Methods
» "string".methods
#» ["!=", "*", "+", "<", "<=>", "==", "=~", ">", "[]", "[]=", "capitalize", "chop", "concat", "count", "delete", "downcase", "each_byte", "each_char", "each_line", "empty?", "end_with?", "eql?", "fmt", "include?", "insert", "length", "ljust", "match", "new", "replace", "replace_once", "reverse", "rjust", "size", "slice", "split", "start_with", "strip", "to_a", "to_bytes", "to_d", "to_f", "to_i", "to_s", "upcase", "!", "block_given?", "class", "exit", "instance_eval", "instance_variable_get", "instance_variable_set", "is_a?", "methods", "nil?", "object_id", "puts", "raise", "require", "require_relative", "send", "singleton_class", "sleep", "thread"]
Inspecting Classes
» "string".class
#» String
Inspecting Singleton Classes
» "moji".singleton_class
#» #<Class:#<String:842352325152>>
» "moji".class.singleton_class
#» #<Class:String>
Inspecting Ancestor Classes
» Integer.ancestors
#» [Integer, Object]
» "moji".class.ancestors
#» [String, Object]
Inspecting Singleton Class Ancestors
» "moji".class.singleton_class.ancestors
#» [#<Class:String>, #<Class:Object>, Class, Object]
Inspecting Object IDs
» "moji".object_id
#» 842352977920
#to_json
h = { a: 1, b: [1, "2", [4, 5, nil]]}
h.to_json # converts hash to JSON
#=> {"a":1, "b":[1, "2", [4, 5, null]]}
Customizing #to_json
Override #to_json in your classes:
class JobTitle
def initialize(name)
@name = name
end
def to_json
{ title: @name }.to_json
end
end
class Person
def initialize(name, age)
@name = name
@age = age
@job = JobTitle.new("software engineer")
end
def to_json
{ name: @name, age: @age, job: @job }.to_json
end
end
stan = Person.new("Stan", 23)
h = { person: stan }
h.to_json #=> {"person":{"name":"Stan","job":{"title":"software engineer"},"age":23}}
Lazy Enumeration
Avoid eager evaluation overhead:
enumerator = [1, 2, 3].lazy.map do |value|
2 * value
end
result = []
enumerator.each do |value|
result.push(value)
end
result #=> [2, 4, 6]
Call .lazy.map on Arrays, Ranges, or JSON collections.
Code Style Guide
Style Guidelines
- Files must use UTF-8 encoding.
- Use 2 spaces per indentation level.
- Never use tabs for indentation.
- Follow the standard Ruby Style Guide where applicable.
Documentation Conventions
Class#instance_methoddesignates an instance method in documentationClass.class_methodModule.module_method
Syntax Highlighting
Supported in Vim and Sublime Text, or by selecting standard Ruby syntax highlighting.
References
Official Links
- Official Site: https://goby-lang.org/
- GitHub Repository: https://github.com/goby-lang/goby/