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:
$ goby hello.gb
#=> Hello, World!
REPL (igb)
$ goby -i
reset
: reset the VMexit
: exit REPLhelp
: show help- ctrl-c: cancel the block entered, or exit (on top level)
See igb manual & test script. You can use readline
features such as command history by arrow keys.
Variables
Local variable
zip101 = "233-7383"
magic_number = 42
Should be “[a-z][a-z0-9_]+
“(snake_case).
Instance variable
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
Should be “@[a-z][a-z0-9_]+
“(snake_case).
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
Black hole variable
# '_' is write-only
a, _ = [1, 2]
Class variable
Unsupported.
Global variable
Unsupported.
Method definition
Method definition and calling
def foo_bar?(baz)
if baz == "Hi, Goby!"
true
else
false
end
end
foo_bar? "Hi, Goby!" #=> true
Method name should be “[a-z][a-z0-9_]+\??
” (snake_case). You can omit the trailing “()
” only if no parameters are taken. Trailing using “!
” is unsupported.
Order of method parameter
def foo(normal, default="value", hash={}, ary=[], keyword:, keyword_default:"key", *sprat)
end
If a default value is provided to a parameter, the parameter can be omitted when calling. ()
can be omitted. The order of parameters in method definition is restricted as follows:
- normal parameters (like
a
) - normal parameters with default value (like
a=1
) - optional parameters (array or hash, like
ary=[]
orhs={}
) - keyword parameters (like
kwd:
) - keyword parameters with default value (like
kwd: 1
orary: [1,2,3]
orhsh: {key: "value"}
) - splat parameters (like
*sp
)
Or you will receive an error.
Keyword parameter (WIP)
def foo(process:, verb: :GET, opt:{ csp: :enabled }, ary: [1, 2, 3])
end
Returning value
PI = 3.14
def area(radius)
radius * PI # returns the result of evaluation
end
area 6 #=> 18.84
Returning multiple value
def my_array
[1, 2, 3]
end
my_array #=> [1, 2, 3]
Instance method
module Foo
def bar # defining instance method
puts "bar"
end
def baz(count, email: "goby@example.com")
count.times do
puts email
end
end
end
foo = Foo.new
foo.bar #=> bar
foo.baz(3) #↓
goby@example.com
goby@example.com
goby@example.com
Singleton method #1
str = "Goby"
def str.foo #1 singleton method on the object
self * 2
end
str.foo
#=> GobyGoby
Singleton method #2
module Foo
def self.bar #2 singleton method with `self.`
92
end
end
Singleton method #3
module Foo
def Foo.bar #3 singleton method with a class name (unrecommended)
88
end
end
Singleton method #4
module Foo end
def Foo.bar #4 singleton methods outside the Foo
9999
end
Foo.bar #=> 9999
Attribute accessor method
class Foo
attr_accessor :bar, :baz
def initialize
@bar = 42
@baz = 99
end
end
foo = Foo.new
foo.bar = 77
foo.baz = 88
You can use the following shorthands to declare attribute accessor methods in classes/modules:
attr_accessor
attr_reader
attr_writer
Private method (to be implemented)
class Foo
def bar
42
end
def _baz # leading '_' means private method
99
end
end
Module/Class definition
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 should be “[A-Z][A-Za-z0-9_]+
” (UpperCamelCase). Modules cannot be inherited.
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
is to use the instance methods in the specified modules as singleton methods in your class or module.
Module instantiation
module Foo #module definition
def foo
99
end
end
Foo.new.foo #=> 99
Actually, Goby’s module can be even instantiated via “new
” like “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 should be “[A-Z][A-Za-z0-9]+
” (UpperCamelCase). Inheritance with “<
” is supported.
Constants
HTTP_ERROR_404 = 404
HTTP_ERROR_404 = 500 # error
Constants should be “[A-Z][A-Za-z0-9_]+
” (UPPER_SNAKECASE). Constants are not reentrant and the scope is global.
Redefining class/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
Load library
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
Literal
Keyword
def
, true
, false
, nil
, if
, elsif
, else
, case
, when
, return
, self
, end
, while
, do
, yield
, get_block
, next
, class
, module
, break
String literal
"double quote"
'single quote'
Double and single quotation can be used.
Symbol literal
:symbol # equivalent to "symbol"
{ symbol: "value" }
Goby’s symbol (using :
) is always String
class.
Numeric literal
year = 2018 # Integer
offset = -42 # Integer
PI = 3.14 # Float
G = -9.8 # Float
Array literal
[1, 2, 3, "hello", :goby, { key: "value"}]
[1, 2, [3, 4], 5, 6]
Hash literal
h = { key: "value", key2: "value2" }
h[:key2] #=> value2
Hash literal’s keys should always be symbol literals.
Range literal
(1..10).each do |x| # '..' represents a range
puts x*x
end
Boolean and nil
true # Boolean class
false # Boolean class
nil # Null class
!nil #=> true
Any objects except nil
and false
will be treated as true
on conditionals.
Operator
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
*Priority of operators are TBD
Other operators
() # changing priority of interpretation
[] # array literal
* # multiple assignment
.. # range
*Priority of operators are TBD
Delimiter
class Foo; end # ';' to delimit
class Bar end # recommended
String interpolation (to be implemented)
puts "Error: #{error_message}" # double quotation is required
Comment
puts "Goby" # comments
Use the annotations to keep the comments concise.
TODO
FIXME
OPTIMIZE
HACK
REVIEW
I/O
-
#puts
-
special constants:
ARGV
,STDIN
,STDOUT
,STDERR
,ENV
Flow control
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
is not supported.
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
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
, conditional and a do
/end
block can be used for a loop.
Rescue
Under construction. Join #605.
Block
Block
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
{ }
cannot be used for forming a block.
yield
def foo
yield(10) # executes the block given
end
foo do |ten|
ten + 20
end
Block object and call
b = Block.new do
100
end
b.call #=> 100
Block.new
can take a block and then call
.
Passing a block
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 a block with block arguments
b = Block.new do |arg, offset|
arg + 1000 - offset
end
b.call(49, 500) #=> 549
Special 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 not a method but a keyword to retrieve a given block argument as a block object. By this, you can pass around or call
the given block arguments as block objects.
Closure
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
Native class (Primary)
Goby’s most “native” classes cannot instantiate with new
in principle.
Object
Bar.ancestors
#» [Bar, Foo, Object]
Bar.singleton_class.ancestors
#» [#<Class:Bar>, #<Class:Object>, Class, Object]
Object
is actually just for creating singleton classes. See Class
.
-
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
,superclass
-
Object.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
can actually be regarded as the same and you don’t need to distinguish them in almost all the cases.
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
Fixed to UTF-8 with mb4 support.
String.methods
:fmt
,- the rest:
Class.methods
- the rest:
"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
,- the rest:
Object.new.methods
- the rest:
Integer
37037 * 27 #=> 999999
Integer.methods
: the same asClass.methods
1.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
- the rest:
Object.new.methods
- the rest:
Array
[1, "2", :card, [4, 5], { john: "doe" }]
Array.methods
: the 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
- the rest:
Object.new.methods
- the rest:
Hash
h = { key: "value" }
h = { "key": "value" } #=> error
h["key"] #=> value
h[:key] #=> value
Keys in hash literals should be symbol literals, while Hash index can be either string or symbol literals.
Hash.methods
: the 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
- the rest:
Object.new.methods
- the rest:
Range
(1..10).each do |i|
puts i ** 2
end
Range.methods
: the same asClass.methods
(1..10).methods
:!=
,==
,bsearch
,each
,first
,include?
,last
,lazy
,map
,new
,size
,step
,to_a
,to_enum
- the rest:
Object.new.methods
- the rest:
Block
b = Block.new do
100
end
b.call #=> 100
Block.methods
: the same asClass.methods
(Block.new do end).methods
:call
- the rest:
Object.new.methods
- the rest:
Native class (secondary)
Float
1.1 + 1.1 # => -2.2
2.1 * -2.1 # => -4.41
Float literals like 3.14
or -273.15
. Float
class is based on Golang’s float64
type.
Float.methods
: the same asClass.methods
3.14.methods
:!=
,%
,*
,**
,+
,-
,/
,<
,<=
,<=>
,==
,>
,>=
,new
,ptr
,to_d
,to_i
- the rest:
Object.new.methods
- the rest:
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
Experimental: the size is arbitrary and internally a fraction from Golang’s big.Rat
type. Decimal literal is TBD for now and you can get Decimal
number via to_d
method from Integer
/Float
/String
.
Decimal.methods
: the same asClass.methods
(1.1).to_d.methods
:!=
,*
,**
,+
,-
,/
,<
,<=
,<=>
,==
,>
,>=
,denominator
,fraction
,inverse
- the rest:
Object.new.methods
- the rest:
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
Using / /
is to be implemented.
Regexp.methods
: the same asClass.methods
Regexp.new("^aa$").methods
:==
,match?
- the rest:
Object.new.methods
- the rest:
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" }
The number keys in the captures are actually String
class.The key 0
is the matched string.
MatchData.methods
: the same asClass.methods
'abcd'.match(Regexp.new('(b.)')).methods
:captures
,length
,new
,to_a
,to_h
- the rest:
Object.new.methods
- the rest:
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
- the rest:
Class.methods
- the rest:
File.new.methods
:basename
,chmod
,close
,delete
,exist?
,extname
,join
,name
- the rest:
Object.new.methods
- the rest:
Native class (Golang-oriented)
GoMap
h = { foo: "bar" }
m = GoMap.new(h) # to pass values to Golang's code
h2 = m.to_hash
h2[:foo] #=> "bar"
GoMap.methods
: the same asClass.methods
GoMap.new.methods
:get
,set
,to_hash
- the rest:
Object.new.methods
- the rest:
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
class is to hold channels to work with #thread
. See thread
.
Channel.methods
: the same asClass.methods
Channel.new.methods
:close
,deliver
,new
,receive
- the rest:
Object.new.methods
- the rest:
Enumerator & lazy
Pretty new experimental library.
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]
A shorthand #lazy
method is also provided in Array
and Range
by now. See “Tips & tricks” below.
LazyEnumerator.methods
: the same asClass.methods
[1, 2].lazy
:each
,first
,has_next?
,initialize
,map
,next
- the rest:
Object.new.methods
- the rest:
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
: the same asClass.methods
ArrayEnumerator.new([1, 2, 3]).methods
:has_next?
,initialize
,next
- the rest:
Object.new.methods
- the rest:
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
: the same asClass.methods
RangeEnumerator.new(1..2).methods
:has_next?
,initialize
,next
- the rest:
Object.new.methods
- the rest:
Special class
Boolean
true.class #=> Boolean
false.class #=> Boolean
A special class that just to hold true
and false
. Cannot be instantiate.
Null
nil.class #=> Null
A special class that just to hold nil
. Cannot be instantiate.
Method
(A special dummy class that just holds methods defined by Goby code.)
Diggable
Provides #dig
method. Currently. Array
and Hash
classes’ instance can be 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
- the rest:
Object.methods
- the rest:
Spec.new.methods
:describes
,initialize
,run
,session_successful
,session_successful=
- the rest:
Hash.new.methods
- the rest:
Tips & tricks
Showing 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"]
Showing class
» "string".class
#» String
Showing singleton class
» "moji".singleton_class
#» #<Class:#<String:842352325152>>
» "moji".class.singleton_class
#» #<Class:String>
Showing ancestors
» Integer.ancestors
#» [Integer, Object]
» "moji".class.ancestors
#» [String, Object]
Showing singleton classes’ ancestors
» "moji".class.singleton_class.ancestors
#» [#<Class:String>, #<Class:Object>, Class, Object]
Showing object’s id
» "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]]}
Customize #to_json
Overwrite the #to_json
in your class:
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
To avoid N + 1 query.
enumerator = [1, 2, 3].lazy.map do |value|
2 * value
end
result = []
enumerator.each do |value|
result.push(value)
end
result #=> [2, 4, 6]
You can call #lazy.map
on Array
, Range
, or JSON
objects.
Styling
Quick style guide
- UTF-8 should be used.
- Only two spaces ` ` should be used for one indentation.
- Tab cannot be used for indentation.
- For more, follow RuboCop’s style guide in principle.
Document notation
Class#instance_method
– use#
to represent instance methods in documentsClass.class_method
Module.module_method
Syntax highlighting
Ready for Vim and Sublime text. You can also use Ruby’s syntax highlighting so far.
References
Official
- Official site: https://goby-lang.org/
- Repository: https://github.com/goby-lang/goby/
- DevHints: https://devhints.io/goby (this page)
0 Comments for this cheatsheet. Write yours!