macro of scheme

intro

大家对宏(特指C/C++的宏)应该不陌生, 甚至被这个东西坑过无数次, 究其原因, C/C++的宏本质上只是文本替换而已, 没有任何语法层面的信息, 因而也就做不到类型检查语法检查这些事情; 而scheme的宏是真正工作在语法树上的, 甚至可以对其进行修改!

define-syntax

在R5RS以前, scheme通过define-macro来定义宏, 形式如下:

1
2
3
4
5
6
7
8
(define-macro (..)
; usually
`(..
,(eval-to-single-var)
,@(eval-to-list)
)
; so use it as **template**!
)

行为上其实和C/C++的宏是差不多的, 在transform阶段简单的做了token的替换, 看一个简单的例子:

1
2
3
4
5
6
7
8
9
(define-macro
myor
(lambda (a b)
`(let ([tmp ,a])
(if tmp #t ,b)
)
)
)
(let ([tmp #t]) (myor #f tmp)) ; oops, here gives #f, not expected

这个坑很明显, 和C/C++一样, 宏内部出现的名字tmp被实际使用宏的代码污染了. 不过聪明的scheme实现会提供一个gensym方法, 用来生成一个独一无二的名字, 好我们试试看改进版本:

1
2
3
4
5
6
7
8
9
10
(define-macro
myor
(lambda (a b)
(let ([t (gensym)])
`(let ([,t ,a]) ; what if `a` used variable named `t`? it's ok, `a` is lexical scoping
(if ,t ,t ,b)
)
)
)
)

虽然通过gensym解决了临时变量的名字问题, 但是依然没办法阻止用户污染其他名字, 比如重新定义if..于是我们意识到:

  1. 名字(binding)在上下文环境里的重要性
  2. 被定义时的环境和被执行的环境是两个完全不同的概念

回头来看define-macro的行为, 其实它的工作只是替换代码, 本身并不携带任何binding信息, 或者说, 它的binding信息来源于真正被执行的环境, 这个环境我们叫做动态作用域

那么, 显然在这种情况下, 我们需要一种能够拥有自己独立binding环境的宏. scheme在R5RS后提供了新的语法

define-syntax

从名字可以感觉出来, 这种宏应当是工作在语法层面, 形式大致如下

1
2
3
4
5
(define-syntax name
(syntax-rules ( reserved words )
((_ arg) .. deal with arg ..)
)
)
  • _表示macro本身的placeholder
  • syntax-rules则支持模式匹配

syntax-rules

模式匹配是一个很强大的功能, 可以很方便的定义一些含有递归结构的逻辑(scala中的pattern matching甚至能够一定程度上实现语义的匹配, 用起来也是非常顺手)

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; (let*
; ((..) (..))
; body
; )
(define-syntax let*
(syntax-rules
()
((_ ((p v)) b ...)
(let ((p v)) b ...)
)
((_ ((p1 v1) (p2 v2) ...) b ...)
(let ((p1 v1)))
(let* ((p2 v2) ...) b ...)
)
)
)

可以看到let*的定义非常简单, 甚至可以像普通方法一样进行递归定义

hygienic

读者可以尝试一下在let*中进行各种”名字污染”行为(比如重新定义let), 结果当然是可以正确执行. 因为define-syntax引入了hygiene macro(卫生宏)的概念, 即: 宏内部使用的binding信息来源于被定义时的环境, 而不受到运行环境的影响, 这也叫作referential transparency. 对应的, 我们称hygiene macro工作在词法作用域

这是不是意味有了卫生宏的特性我们不需要动态作用域的功能了呢? 也不尽然, 看一个场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(define-macro
show-vars
(lambda (. vars)
`(begin
(display
(list
; question about ',x and ,x?
; remember we are generating code here!
; ,x -> gives the name, just like we input `x` in repl, and `x` will be evaluated as variable
; ',x -> gives `'some-symbol`, so it is evaluated as symbol!
,@(map (lambda (x) `(list ',x ,x)) vars))
)
(newline)
)
)
)
(let ([i 1] [j 3]) (show-vars i j)) ; gives ((i 1) (j 3))

show-vars展示了当前环境下定义的变量的名字和内容, 而这是一个运行时的环境, 恰好define-macro能够做到, 这也是词法作用域和动态作用域的区别

syntax-case

scheme也提供了比syntax-rules更细粒度的语法控制能力(为什么这么说?), 其形式如下:

1
2
3
4
5
6
7
8
9
10
11
(define-syntax some-macro
(lambda (syntax-form)
(syntax-case syntax-form ()
[(_ pattern ...)
<fender>
<expr> ...
]
...
)
)
)

关于fender的概念(不过这里没有用到):

If the optional is present, it serves as an additional constraint on acceptance of a clause. If the of a given matches the input value, the corresponding is evaluated. If evaluates to a true value, the clause is accepted; otherwise, the clause is rejected as if the pattern had failed to match the value. Fenders are logically a part of the matching process, i.e., they specify additional matching constraints beyond the basic structure of the input.

对比一下两者的特点:

1
2
3
4
5
6
7
8
9
10
(define-syntax when
(syntax-rules ()
((_ test e e* ...)
(if test (begin e e* ...)))))
(define-syntax when
(lambda (x)
(syntax-case x ()
((_ test e e* ...)
#'(if test (begin e e* ...))))))
  • 都支持pattern matching
  • syntax-case的返回有#'前缀: 实际上被用来替换在pattern matching里被捕获的模式变量
  • (语法上看不出来的)syntax-case提供了拆解重组语法对象的能力, 即操作syntax-object的能力(什么是syntax-object? 我们先往下看)

datum & syntax object

比如我们想实现这样一个宏aif:

(aif (getuid) (display it) (display "none")), it是一个动态的binding, 显然aif需要工作在动态作用域

版本1
1
2
3
4
5
6
7
;; doesn't work
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then else)
#'(let ((it test))
(if it then else))))))

then else 都是syntax-object, 在syntax-form中作为模板变量被替换时仅仅保留了各自的词法上下文(lexical scope), 因此它们都不能访问it (因为在他们定义的环境中并没有it, 这也是referential transparency的体现)

版本2
1
2
3
4
5
6
7
8
;; doesn't work either
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then else)
(let ((it (datum->syntax x 'it))) `it`(1)
#'(let ((it test)) ; this `it`(2)
(if it then else))))))) ; and this `it`(2), both not references `it`(1) (there are diffrerent objects! or sth)
  • datum->syntax用于把一个symbol变成给定syntax-form中的syntax-object
  • #'内部只会替换在pattern match里被捕获的模式变量, 其他的名字则引用自定义该macro时的词法上下文

虽然通过datum->syntax引入了一层lexical scope, 但是请注意该scope是相对于x(即整个(aif ..)调用的syntax-object)来说的, 换句话说是aif调用的上下文(例如(let ([..]) (aif ..)), 则aif的上下文即let以及let的外层环境); 而在#'(let ((it test)) ..)'中的it仅仅是展开后的一个名字, 与datum->syntax引入的it并不是同一个东西, 尽管后者确实能被then/else引用到(如果有被定义的话, 而在这个例子里, 尽管它拥有可以被then/else访问的lexical scope, 但实际上环境里并没有定义它, 因而会出现unbound variable错误)
(hint: 在drracket里可以很方便的看到referencing的情况)

版本3
1
2
3
4
5
6
7
8
9
10
11
12
;; works, but is obtuse
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then else)
;; invoking syntax-case on the generated
;; syntax object to expose it to `syntax'
(syntax-case (datum->syntax x 'it) ()
; following `it` is not relevant with (datum->syntax ..), so you can name it `yy` sth..
(it
#'(let ((it test))
(if it then else))))))))

引入一层定义it的环境, 然后再通过syntax-case来捕捉到这个模式变量, 此时then/else所处的环境是在it被引入且定义的环境中:

为了方便说明, 使用yy替换

1
2
3
(yy ; yy 作为模式变量, 捕获了 (datum->syntax x 'it), 这个syntax-object在将要发生的调用中的名字就是it
#'(let ((yy test)) ; expansion后实际上变成了 ((it <test所代表的表达式>))
(if yy then else))) ; 同理这里的yy也变成了it, 而此时it在let的环境中, 可以被then/else所引用

所以实际上, 我们是在then/else调用时能访问到的环境中引入了it这个名字(并通过let定义); 假设我们不是通过syntax-case来捕获到yy, 那么在模板中yy仅仅是一个名字(就像我们在repl里直接输入yy), 显然在then/else的lexical socpe里当然引用不到yy.
由此可以知道syntax-object是一个存在于某个上下文环境(有意义)的名字!

aif的例子也可以看到, define-syntax也可以具有动态作用域的能力, 实际上我们想一下scheme里面为什么把syntax-rules/case叫做transformer? 因为它们能够:

  1. 通过pattern matching来捕捉syntax-object(是不是像在分析语法树?)
  2. 捕捉到的syntax-object可以被直接eval, 也可以被再次拆解/修改/引入新的syntax-object, 并且它们都属于当前操作的syntax-form的binding(是不是感觉像在编辑语法树?)
  3. 没有被捕捉到的对象则保持定义时的binding, 从而避免了污染问题

Continuation at First Glimpse

继续tyss notes前先简单整理一下continuation的概念(偷懒把以前的笔记直接贴上来, 大概有不太准确的地方, 但应该不影响理解)

基本概念

continuation的中文一般都叫做”续延”(还蛮好听的), 不过解释起来比较麻烦, 还是结合代码来看更好理解一点.

首先scheme里的continuation(太长了, 下文缩写为cont吧)的基本形式是长这个样子的:

1
2
3
4
(call/cc
(lambda (cc)
; do some computation)
)

call/cccall-with-current-continuation的缩写, lambda的参数cc就是当前环境的一个cont, 可以理解为当前的调用栈, 它知道自己未来需要执行的过程. 那么怎么表示”未来的计算”这样一个概念呢? 在编程语言中自然就是函数了, 而在scheme中就是过程(procedure), 更一般的就用lambda来表示. cc就是这样一个只接受一个参数的lambda, 而调用(cc val)则是整个cont动作的关键—-它将流程回cont定义的位置, 并以val代换为其计算出的结果.

来暴力理解一下, 把(call/cc ..) (rest code..)看做这样一个结构[] (rest code ..), 在(cc val)的时候直接回到了[]的地方, 并将其替换为了val.

看一个简单(似乎不是特别简单)的例子:

1
2
3
(+ 1 (call/cc)
(lambda (cc) (+ 2 (cc 3)))
)

一步步来:

  1. 整个block可以看做(+ 1 [])
  2. 再看[]内部: (lambda (cc) (+ 2 (cc 3))), 这个lambda的body是会被执行的
  3. (cc 3)以3为参数调用cont, 则跳转到其定义的地方[]将其替换
  4. 最后变为(+ 1 3), 结果为4
  5. (+ 2 ..)的部分并没有luan3用 :P

基本用法

Jump out

这个用法有个名字叫”非本地退出(non-local exit)”, 说白了就是现代编程语言里break啦, exit啦这些东西.

  • scheme没有这些关键字
  • scheme觉得它们不够old-school
  • scheme决定这么搞
1
2
3
4
5
6
7
8
9
10
(define (search wanted? lst)
(call/cc
(lambda (return)
(for-each (lambda (e)
(if (wanted? e) (return e)))
lst)
#f
)
)
)

功能很直白, 从一个list里找到符合条件的元素, 不存在就返回#f. return的用法是直接从for-each的循环中跳出了. 这里最后的#f可不可以写成(return #f)? 当然可以, 不过整个lambda都计算完了, 自然是返回最后一个计算值, 所以可以直接省略啦.

另一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
(define (list-product list)
(call/cc
(lambda (exit)
(let iter ((rest lst))
(cond
((null? rest) 1)
((zero? (car rest)) (exit 0))
(else (* (car rest) (iter (cdr rest))))
)
)
)
)
)

对一个list完成fold计算乘积的操作, 中途如果遇到0则立即退出, 避免了不必要的遍历.

Jump back

在scheme里, cont和lambda一样都是一等公民, 它自然也可以被当做参数抛来抛去, 或者跟其他变量进行绑定.

1
2
3
4
5
(define return #f)
(+ 1 (call/cc
(lambda (cont) (set! return cont) 1)
)
)

老样子一步一步看:

  1. 全局定义一个return(绑定啥值无所谓)
  2. 替换cont形式: (+ 1 [])
  3. 在lambda body中, return与cont[]绑定!
  4. 1作为返回值, 则(+ 1 [])结果为2

然而并没有结束, 此时return本身已经作为一个”+1器”存在了, 它代表的cont即(+ 1 []), 每次调用(return v)便会得到v + 1.

Jump out and Jump back

看到这里, 可以感觉到cont是能作为程序流控制的手段的, 用来完成一些比较时髦的动作, 比如大家都喜欢的协程 :P

所以接下来看一个模拟协程计算的较复杂的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(define (hefty-computation do-other-stuff) ; 主要复杂计算
(let loop ((n 5))
(display "Hefty computation: ")(display n)(newline)
(set! do-other-stuff (call/cc do-other-stuff)) ; (1)
(display "Hefty computation (b)\n")
(set! do-other-stuff (call/cc do-other-stuff)) ; (3)
(display "Hefty computation (c)\n")
(set! do-other-stuff (call/cc do-other-stuff))
(if (> n 0) (loop (- n 1)))
)
)
(define (superfluous-computation do-other-stuff) ; 次要计算
(let loop ()
(for-each (lambda (item)
(display item)(newline)
(set! do-other-stuff (call/cc do-other-stuff))) ; (2)
'("Straight up." "Quarter after." "Half past." "Quarter til.")
)
(loop) ; this trigger inf loop
)
)
(hefty-computation superfluous-computation)

为了大家(我自己)能简单的看懂, 我们走的慢一些:

  1. 分别定义了两个过程: 主要计算和次要计算; 以调用主要计算开始, 参数为次要计算本身
  2. 开始主要计算, 进入loop(第一次), 打印”Hefty computation: 5”
  3. (1)处call/cc以次要计算为参数, 结合后者定义, 则有(为了方便, 分别记两个两个过程的参数为do1和do2):
  4. 主要计算产生第一个cont, (set! do1 []-1)
    1. 开始次要计算, 参数do2为上一步的[]-1, 进入loop(第一次), for-each打印列表中第一个”Straight up.”
    2. 到达(2) (call/cc do2), 此时次要计算产生一个cont, 记为[]-2, 同时完成do2也就是[]-1的计算, 流程回到了(1), 且将do1绑定到了[]-2
  5. 从(1)后继续主要计算, 打印”Hefty computation (b)”
  6. 主要计算(3)处产生第二个cont[]-3, 形式和上次一样; 由于参数do1实际上是[]-2, 则:
    1. 跳转到[]-2即(2)处继续执行, 此时set![]-2的返回值即[]-3绑定到do2
    2. for-each的第二个循环打印”Quarter after.”
    3. 流程又到(2)处, (call/cc []-3), 并产生当前的cont[]-4, 作为[]-3的参数; 执行[]-3的计算, 于是又回到了(3)处
  7. do1绑定为[]-4, 且打印”Hefty computation (c)”
  8. 接下来的过程类似上面, 即不断的在主要计算和次要计算之间跳来跳去(和协程的切换是一样的)

看上去似乎有点晕, 总结一下这个用法的关键在于:

(call/cc cc)实际上是将当前的cont作为参数contcc的结果, 然后通过执行cc跳转到cont创建时的地方; 之后通过set!将之前的cont(即cc的结果)保存下来, 然后再次通过(call/cc cont)跳回去, 如此往复.

最后一个例子

1
2
3
4
5
6
(let* ((yin ((lambda (cc) (write-char #\@) cc) ; proc
(call/cc (lambda (c) c)))) ; arg
(yang ((lambda (cc) (write-char #\*) cc) ; proc
(call/cc (lambda (c) c))))) ; arg
(yin yang)
)

这是网上流传很久的yin-yang puzzle, 程序会不停的交替打印”@*@**@***@ …”. 这个例子大家可以尝试自己拿纸笔推一下(虽然过程特别的绕, 但是通过不断代换的笨办法还是可以一战的), 或者可以看一下这个解答就比较好理解了, stay calm :P


ref:

Notes for TYSS

baka notes for teach-yourself-scheme-in-fixnum-days

Q: why abbr as TYSS not TYSIFD?
A: i like it :P

this (these) notes may not contain all elements of scheme (e.g. define-syntax&syntax-rules), and some chaps are skipped.

i just keep what i’ve got.


Ch2

type

literal cons pair: '(1 . 2), we need quote to avoid being evaluated as proc apply

concept

Port:

Ports are usually associated with files and consoles.

(format (current-output-port) "im format string" args...)


Ch8

macro

looks like

1
2
3
4
5
6
7
8
(define-macro (..)
; usually
`(..
,(eval-to-single-var)
,@(eval-to-list)
)
; so use it as **template**!
)

one class/object maker example illustrated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
(define ufo (if #f #f))
;; (defstruct name . field-decl)
;; : field-decl := f-name | (f-name default-val)
;; => (make-$name [f-name f-val])
;; => ($name.$f-name obj)
(define-macro (defstruct st-name . st-fields)
(let* (
;; fields-related
(symbol-of (lambda (x) (if (pair? x) (car x) x)))
(value-of (lambda (x) (if (pair? x) (cadr x) ufo)))
(st-field-syms (map symbol-of st-fields))
(st-field-count (length st-fields))
(st-field-getter (lambda (f-name) (symbol-append st-name '. f-name)))
(st-field-setter (lambda (f-name) (symbol-append 'set! st-name '. f-name)))
;; struct-name-related
(st-maker (symbol-append 'make- st-name))
(st-identifier (symbol-append st-name '?))
;; storage
(st-size (1+ st-field-count))
(st-fields-default-vals (map value-of st-fields))
)
`(begin
;; maker
(define (,st-maker . field-val-list)
(let (
;; holder (init with header + defaults)
(data (list->vector (cons ',st-name ',st-fields-default-vals)))
)
;; override if arg provided
(let loop ((rest-pairs field-val-list))
(if (not (null? rest-pairs))
(begin
(let (
(arg-name (car rest-pairs))
(arg-val (cadr rest-pairs))
(list-find-index
(lambda (x lst)
(begin
(define (iter i rest)
(cond
((null? rest) ufo)
((eq? (car rest) x) i)
(else (iter (1+ i) (cdr rest)))
)
)
(iter 0 lst)
)
)
))
(vector-set! data (1+ (list-find-index arg-name ',st-field-syms)) arg-val)
(loop (cddr rest-pairs))
)
)
)
)
;; return vector
data
)
)
;; identifier
(define (,st-identifier st-obj)
(and
(vector? st-obj)
(eq? ,st-name (vector-ref st-obj 0))
)
)
;; getter&setter
,@(let loop ((i 0) (procs '()))
(if (< i st-field-count)
(loop (1+ i)
(let ((f-name (list-ref st-field-syms i)))
(cons
`(define ,(st-field-getter f-name) (lambda (st-obj) (vector-ref st-obj ,(1+ i))))
(cons
`(define ,(st-field-setter f-name) (lambda (st-obj val) (vector-set! st-obj ,(1+ i) val)))
procs
)
)
)
)
procs
)
)
)
)
)

points:

  • storage as tagged vector
  • init with default values (ufo if not provided)
  • override with real values

Ch10

alist

sth like ((a . 1) (b . 2))

(assv key alist) find cons cell in alist with key


つづく

Papas's room

QUARTET PAPAS クァルテットパパス

Quartet Papas

1stviolin 粟津惇
2ndviolin 青山英里香
viola 武田麻耶
cello 奥村景

2012年、桐朋学園大学の同期生で結成。
クラシック音楽と共に、映画、ゲーム音楽、プログレ、アイリッシュ。メンバーの好物をあらゆる方面から探し出し、オリジナルアレンジを基本にレパートリーを広げている。
定期コンサートの傍ら、アウトリーチコンサートツアー、アーティストのレコーディング、現代曲初演など精力的に活動中。

ruby programming language(ch8)

Ch8

type,class,module

ancestry

1
2
3
4
5
<module>.ancestors # => modules
<class>.ancestors # => self, modules, superclass .. Object, Kernel(module)
<module>.included_modules # => ..
<class>.included_modules # => .. Kernel

def

1
2
M = Module.new
D = Class.new(C) { include M }

eval

binding & eval

eval 1 [2]:

  1. eval string
  2. Binding object
1
2
eval(.., obj.binding) # obj.binding.eval(..), make sure `binding` return the binding object
eval(..) # Kernel.eval(..) or eval(.., Kernel.binding)

instance_eval & clas_eval

  • code evaluated in correspond context (to instance or class object)
  • both can accept block instead of string

reflect var

  • <instance>.instance_variables
  • <class>.class_variables
  • class.constants

  • query: eval(".. #{..} ..")

  • attr: o.instance_variable_set(:@x, 0) o.instance_variable_get(:@x), similar class_xxx const_xxx
  • test: _defined? _missing?

method

  • .methods == .public_methods
  • .protected_methods .private_methods
  • <class>.instance_methods alternative prefix: public protected private
  • .singleton_methods
    • trivial for instance
    • class methods for class
  • methods gives list, with (false) to exclude inherited
  • method(:sym) to get method object
    • <method>.call ..
    • <object>.send(:sym, ..)

define

instance:

1
2
3
4
5
6
7
8
9
10
def add_method(c, m, &b)
c.class_eval {
# private, must be inside class/module
# `.class_eval` provides this context
define_method(m, &b)
}
end
add_method(String, :greet) { "Hello, " + self }
"world".greet # => "Hello, world"

class:

1
2
3
4
5
6
7
8
9
def add_class_method(c, m, &b)
eigenclass = class << c; self; end
eigenclass.class_eval {
define_method(m, &b)
}
end
add_class_method(..)
String.greet("world") # => "Hello, world"

or

1
String.define_singleton_method(:greet) {..}

eigenclass gets the class object, recall a specified class is a instance

hook

when:

  • classes are subclassed
  • modules are included
  • methods are defined
  • ..
ed method when param extra
inherited new class defined new class object inherited by subclass
included module included class or module object included in
extended object extended ..
method_added instance method :”new method name”
singleton_.. singleton/class method :”singleton_method” self invoked when define

others:

  • removed/undefined
  • singleton_removed/_undefined

tracing

  • __FILE__
  • __LINE__
  • SCRIPT_LINES__: hash { filename: array of lines of source file } filled when require and load
    • include main file: SCRIPT_LINES__ = {__FILE__ => File.readlines(__FILE__)}
    • refer line of code: SCRIPT_LINES__[__FILE__][__LINE__-1]
  • trace_var(:var): when changed

ObjectSpace

  • .each_object
  • .id2ref inverse to .object_id
  • ..

def method dynamically

class_eval

directly interpolate identifiers into method body

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Module
private
# alias attr_reader
def readonly(*syms)
return if syms.size == 0
code = "" # final code to eval
syms.each {|s| code << "def #{s}; @#{s}; end\n"} # code for getter
class_eval code # eval to define
end
# alias attr_accessor
def readwrite(*syms)
return if syms.size == 0
code = ""
syms.each do |s|
code << "def #{s}; @#{s}; end\n"
code << "def #{s}=(val); @#{s} = val; end\n"
end
class_eval code
end
end

define_method

rely on reflective methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Module
# init INSTANCE vars with default
# e.g: attributes x:0, y:0
def attributes(hash)
hash.each_pair do |symbol, default|
# symbol name for getter/setter
getter = symbol
setter = :"#{symbol}="
# symbol name for member var
variable = :"@#{symbol}"
# define getter via reflective
define_method getter do
if instance_variable_defined? variable
instance_variable_get variable
else
default
end
end
# define setter
define_method setter do |val|
instance_variable_set variable,val
end
end
end
# init CLASS vars
def class_attr(hash)
eigenclass = class << self; self; end
eigenclass.class_eval { attributes(hash) }
end
private :attributes, :class_attrs
end

DSL

A DSL is just an extension of Ruby syntax (with methods that look like keywords)
or API that allows to solve problem or represent data more naturally.

XML as example utilized ruby’s:

  1. block
  2. parentheses-optinal invoke
  3. passing hash literals without curly braces
  4. method_missing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
class XMLGrammar
def initialize(out)
@out = out
end
# output as CDATA
def content(text)
@out << text.to_s
nil
end
def comment(text)
@out << "<!-- #{text} -->"
nil
end
def tag(tagname, attributes={})
# open tag
@out << "<#{tagname}"
# attr
attributes.each {|attr,val| @out << " #{attr}='#{val}'" }
# content
if block_given?
@out << ">"
content = yield # invoke block returned as content
if content
@out << content.to_s
end
# close pair
@out << "</#{tagname}>"
else
# close single
@out << "/>"
end
nil
end
# ordinary class ==> DSL
# Sol 1: { any unknown method is treated as name of tag
alias method_missing tag
# }
# run block in new instance of XML class
def self.generate(out, &block)
XML.new(out).instance_eval(&block)
end
# or
# Sol2: { add validation without using `method_missing`
# define an allowed element(tag) in one (XML) grammar
def self.element(tagname, attributes={})
@allowed_attributes ||= {}
@allowed_attributes[tagname] = attributes
class_eval %Q{
def #{tagname}(attributes={}, &block)
tag(:#{tagname}, attributes, &block)
end
}
# constants indicate type of attribute
OPT = :opt # optional
REQ = :req # required
BOOL = :bool # value is own name
def self.allowed_attributes
@allowed_attributes
end
def tag(tagname, attributes={})
@out << "<#{tagname}"
# get allowed for current tag
allowed = self.class.allowed_attributes[tagname]
# check attribute exist
attributes.each_pair do |k,v|
raise "unknown attribute: #{k}" unless allowed.included? k
@out << " #{k}='#{v}'"
end
# check attribute type
allowed.each_pair do |k,v|
# already output
next if attributes.has_key? k
if (v == REQ)
raise "required attribute '#{k}' missing in <#{tagname}>"
elsif v.is_a? String
@out << " #{k} = '#{v}'"
end
end
if block_given?
# same as above
end
end
end
# }
end

usage example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# define
class HTMLForm < XMLGrammar
element :form, :action => REQ,
:method => "GET",
:enctype => "application/x-www-form-urlencoded",
:name => OPT
element :input, :type => "text", :name => OPT, :value => OPT,
:maxlength => OPT, :size => OPT, :src => OPT,
:checked => BOOL, :disabled => BOOL, :readonly => BOOL
element :textarea, :rows => REQ, :cols => REQ, :name => OPT,
:disabled => BOOL, :readonly => BOOL
element :button, :name => OPT, :value => OPT,
:type => "submit", :disabled => OPT
end
# use
HTMLForm.generate(STDOUT) do
comment "This is a simple HTML form"
form :name => "registration",
:action => "http://www.example.com/register.cgi" do
content "Name:"
input :name => "name"
content "Address:"
textarea :name => "address", :rows=>6, :cols=>40 do
"Please enter your mailing address here"
end
button { "Submit" }
end
end

ruby programming language(ch7)

Ch7

class def

last expression in def is the value of class def(nil if last is a method def)

init

1
2
3
4
5
class Point
def initialize(x,y)
@x,@y = x,y
end
end

attr

  • attr_reader :x, :y
  • attr_accessor "x", "y"

duck type

loosen:

1
2
3
def +(other)
Point.new(@x + other.x, @y + other.y)
end

strict(for duck type):

1
2
3
4
def +(other)
raise TypeError, "Not Point-like" unless other.respond_to? :x and other.respond_to? :y
Point.new(..)
end

coerce

class operation cannot hanle like other_class + self_class:

1
2
3
4
def coerce(other)
# simply swap self and other, then apply self + other is ok
[self, other]
end

enum

1
2
3
4
def each
yield @x
yield @y
end

thus utilize methods in include Enumerable based on each

equality

equal:

1
2
3
4
5
def ==(o)
@x == o.x && @y == o.y
rescue
false
end

identity:

1
2
3
4
5
6
7
8
9
10
11
def eql?(o)
if o.instance_of? Point
@x.eql?(o.x) and @y.eql?(o.y)
else
false
end
end
def hash
# trick for hash code generation
end

order

1
2
3
4
5
6
include Comparable
def <=>(other)
return nil unless other.instance_of? Point
# compare strategy
end

quick mutable

via Struct(core ruby class):

1
2
Struct.new("Point", :x, :y) # Struct::Point
Point = Struct.new(:x, :y)

class method

1
2
3
4
5
6
7
def self.foo
end
# or
class << self
def foo; ..; end
end

var

constant:

  • FOO = Point.new(..) (initialize must be defined before)
  • Point::BAR = .. outside define is ok

class var:

  • @@ init in class def
  • available in both instance & class method

class instance var:

  • @ init in class def
  • only available in class method
  • class << self; attr_accessor :..; end

method visibility

1
2
3
4
5
6
7
8
9
10
class Point
# public goes here
protected
# protected goes here
# like c++
private
# private goes here
end

inherit

tips:

  • class derive < base only ‘public’ inherit
  • any_class.class # => Class instance_of Class
  • any_class.superclass # => Object subclass_of Object

override

  • alias :super_inv :super_method_to_be_overrided to call method of superclass
  • only subclass when familiar with it (private methods can be occasionaly overrided!), or encapsulate/delegate if only to use public API
  • class method also override, better to invoke it through the class which defines it

var

  • instance var
    • not inherited
    • create on assign(usually done by method invoke)
    • if same name, that value is overwritten not shadowed
  • class instance var
    • not inherited
  • class var
    • inherited, so alterable in subclass
    • better to use class instance var instead, to avoid the above case
  • constant
    • bound to lexical scope within method(inherited method use constant of superclass)

object create

  • clone can keep the frozen state while dup not
  • both use initialize_copy (consider copy-constructor)
limit-creation
1
2
private_class_method :new,:allocate
private :dup,:clone

module

A named group of methods, contants and class vars.

1
2
3
4
5
6
7
module Base64
def self.encode
end
def self.decode
end
end

allow nesting with classes

mixin

  1. module defines bunch of method rely on some class instance methods(as interface)
  2. include module in class
  3. define the relied methods
  • include module in module
  • object.extend(module) apply on a object

loading

term require load
allow binary Y N
path library name exact filename
repeat once mutiple times
$SAVE level 0 current

lazyload

1
2
# require 'socket' if TCPSocket first used
autoload :TCPSocket, "socket"

eigenclass

  • anonymouse class attached to an object, to open it class << some; def .. end end
  • class << class_name define class method of class_name or within class def class << self
  • class << object define singleton method of object

method lookup

take o.m, search sequently:

  1. eigenclass of o for singleton method m
  2. instance method m
  3. modules (reverse the include order)
  4. inheritance (repeat 2-3)
  5. lookup method_missing

constant lookup

  1. lexical
  2. inheritance: .. -> Object(as top level) -> Kernel

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module Kernel
# Constants defined in Kernel
A = B = C = D = E = F = "defined in kernel"
end
# Top-level or "global" constants defined in Object
A = B = C = D = E = "defined at toplevel"
class Super
# Constants defined in a superclass
A = B = C = D = "defined in superclass"
end
module Included
# Constants defined in an included module
A = B = C = "defined in included module"
end
module Enclosing
# Constants defined in an enclosing module
A = B = "defined in enclosing module"
class Local < Super
include Included
# Locally defined constant
A = "defined locally"
# The list of modules searched, in the order searched
# [Enclosing::Local, Enclosing, Included, Super, Object, Kernel]
search = (Module.nesting + self.ancestors + Object.ancestors).uniq
puts A # => "defined locally"
puts B # => "defined in enclosing module"
puts C # => "defined in include module"
puts D # => "defined in superclass"
puts E # => "defined toplevel"
puts F # => "defined kernel"
end
end

ruby programming language(ch6)

Ch6

method

singleton method

1
2
3
4
5
o = "some object"
def o.echo
puts self
end
o.echo # invoke

alias

alias <new> <origin>

take an example:

1
2
3
4
5
6
7
8
9
10
def foo
# ..
end
alias real_foo foo
def foo
# ..
real_foo # it's ok
end

default argument

  • default arguments must be adjacent
  • left-to-right assign

block as argument

1
2
3
4
def foo(.., &b)
# ..
b.call(..) # &b get to a Proc object, this replaces yield
end
  • block argument and real block cannot be given at same time

proc as argument

1
2
3
4
5
6
7
p = Proc.new {..}
def foo(.., p) end
def bar(.., &b) end
# invoke
foo(.., p)
foo(.., &p)

using &

  • method symbol has .to_proc to Proc object
  • & before Proc object make it applicapable by iterator, lst.map &:upcase
  • &:sym = &:sym.to_proc in invocation

Proc & Lambda

  • both is-a Proc
  • == only via dup,clone

create proc

from block:

1
2
3
def makeproc(&p)
p
end

or:

  • Proc.new
  • lambda
    • succ = lambda {|x| x+1}
    • succ = ->(x){ x+1 }
    • f = ->(x,y; i,j,k) {..} or f = -> x,y; i,j,k {..} arg: x,y; local-var: i,j,k (minial lambda: ->{})

invoke

for both proc and lambda:

  • f.call (..)
  • f[..]
  • f.(..)

diff

return

proc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def makeproc(msg)
p = Proc.new { puts msg; return } # return to makeproc
puts "return from proc"
p # but already return to caller(here test)
end
def test
puts "enter method"
p = makeproc "enter proc"
p.call
puts "exit method" # so never executed
end
test
# enter method
# return from proc
# enter proc
# >> LocalJumpError

lambda:

1
2
3
4
5
6
7
8
9
10
def makelambda(msg) .. end
def test .. end
test
# enter method
# return from lambda
# enter lambda
# exit method
# (no LocalJumpError)

break

  • proc: as block break out whole iterator
  • lambda: normal break, only make sense within enclosing loop or iteration

arguments

  • proc: no number match, auto pack/unpack, life easier
  • lambda: exact match, no auto splat

closure

  • proc and lambda are closures
  • closure possess the copy of local context when created
  • alter via binding:
    1. def foo(n); lambda {..}; end
    2. then f2 = foo(2)
    3. f2 can altered to eval("n=3", f2) or eval("n=3", f2.binding) (complete form)

method object

1
2
3
m = 0.method :succ
m.call or m[]

not closure

unbound method object:

1
2
3
4
unbound_plus = Fixnum.instance_method "+"
plus_2 = unbound_plus.bind 2
plus_2[2] # => 4
plus_3 = plus_2.unbound.bind 3

functional

enumerable as example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module Functional
# some_method | enumerable: apply some_method to enumerable as map
def apply(enum)
enum.map &self
end
alias | apply
# some_method <= enumerable: .. as reduce
def reduce(enum)
enum.inject &self
end
alias <= reduce
end
# mixin to Proc & Method
class Proc; include Functional; end
class Method; include Functional; end
# take standard deriv as example
stddev = -> a {
sum = -> x,y { x+y }
mean = (sum <= a) / a.size # if you want to make it lambda, should ensure it can refer `a` in invoking deriv
deriv = -> x { x - mean }
square = -> x { x*x }
Math.sqrt( (sum <= square|(deriv|a)) / (a.size - 1) )
}
stddev[[1,2,3,4]]
# => 1.2909944487358056

compose

1
2
3
4
5
6
7
8
def compose f
if self.respond_to?(:arity) && self.arity == 1
-> {|*args| self[f[*args]]}
else
-> {|*args| self[*f[*args]]} # handles more than one args, if self is defined like that
end
end
alias * compose

partial apply

1
2
3
4
5
6
7
def apply_head(*first); -> *rest { self[*first.concat rest] }; end
def apply_tail(*last); -> *rest { self[*rest.concat last] }; end
alias >> apply_head
alias << apply_tail
g = f >> 2 # set 1st arg to 2
g = f << 2 # set last arg to 2

memorize

1
2
3
4
5
6
7
8
9
10
11
12
13
def memoize
cache = {} # An empty cache. The lambda captures this in its closure.
lambda {|*args|
# notice that the hash key is the entire array of arguments!
unless cache.has_key?(args) # If no cached result for these args
cache[args] = self[*args] # Compute and cache the result
end
cache[args] # Return result from cache
}
end
# A (probably unnecessary) unary + operator for memoization
# Mnemonic: the + operator means "improved"
alias +@ memoize # cached_f = +f @ for prefix unary

example:

1
2
3
# recursive
factorial = ->x {..}.memorize or +lambda {|x|..} # ok, cached
factorial = ->x {..}; cached_f = +factorial # not cached, only you called each x once, that x is cahced!

with symbol

1
2
3
4
5
# ..
alias [] bind
String[:reverse]["hello"][] # => "olleh"
# unbound bind-to invoke
# method "hello"

ruby programming language(ch4-5)

Ch4

parallel assign

  • x,y,z = 1,2,3 same as x,y,z = [1,2,3] or x,y,z=*[1,2,3](splat)
  • swap: x,y = y,x(like python)
  • lvalues can only have one splat:
    • *x,y = 1,2,3 [1,2],3
    • x,y,*z = 1,*[2,3,4] 1,2,[3,4]
    • x,y,z = 1,[2,3] 1,[2,3],nil, but x,y,z=1,*[2,3] 1,2,3
    • * on lvalue as compress, on rvalue as decompress

flip-flop

  • (1..10).each {|x| print x if x==3..x>=3} 3 because .. does evaluation and righthand expr check(to flop out) at same time
  • (1..10).each {|x| print x if x==3...x>=3} 34 because ... waits evaluation first, then check and flop out

oneline expr

  • (expr) if (expr)
  • (in python (expr) if (expr) else (expr) valid, while not in ruby)

Ch5

condition

if modifier

1
2
3
4
begin
line1
line2
end if expr
1
2
( line1
line2 ) if expr

unless

  • if-not
  • no allow elsif

case

  • case [when+] end no need break
  • can be used as expr value: x = (case .. end)

while/until as modifier

  • expr while(until) expr
  • or as if

iterate

  • integral: .upto, .downto, .times
  • continous: start.step(end, step)

  • .collect (like .map?) traversal and process each

  • .select out subset meet requirements, .reject as inverse
  • .inject as reduce

custom

  • yield values to block as parameter, say invoke the block with yielded values
  • if block_given? to determine whether invoke or not
  • return as enumerator: self.to_enum(:yout_yield_method), which can then invoke .each, etc.
  • as external iterator: loop {iterator.next}, which raise StopIteration

alter control flow

  • return: allow return multi values from block, but jumps out of current enclosing method, thus return values as method values
  • break: like return can have a value, but jumps out of whole iteration but not method scope
  • next: allow return values, jumps out current iteration
  • redo: restart current iteration, skipping checking the condition

exception

raise

  • raise "bad argument" if <cond>
  • raise RuntimeError, "bad argument" if <cond>
  • raise RuntimeError.new "bad argument" if <cond>
  • raise RuntimeError.exception "bad argument" if <cond>

rescue

1
2
3
4
5
6
7
8
9
begin
# code may raise exception
rescue => e # global var $! also handles it
# handle it
else
# other uncatched
ensure
# always run
end

variables in rescue scope are visiable to rest after

  • rescue Exception
  • rescue ArgumentError => e
  • rescue ArgumentError, TypeError => e or-match

as modifier: y = factorial(x) rescue 0, handles any StandardError and return the default follows rescue

BEGIN/END

  • BEGIN {..} executes before anything else only once without considering conditions
  • END {..} consider conditions; first register(encounter), last execute; only once even in loop

thread

1
2
3
4
5
6
def readfiles(filenames)
threads = filenames.map do |f|
Thread.new { File.read(f) } # return value of block stores in thread.value, which is a sync method(will block if not complete)
end
threads.map {|t| t.value}
end

fiber

lightweight thread

normal

  • require 'fiber'
  • .yield transfers control back to caller, .resume to return

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
f = Fiber.new {
puts "fiber hel" # 3.
Fiber.yield # 4. goto line 9
puts "fiber bye" # 7. goto line 11
}
puts "caller hel" # 1.
f.resume # 2. goto line 2
puts "caller bye" # 5.
f.resume # 6. goto line 4
# end
# results:
# caller hel
# fiber hel
# caller bye
# fiber bye

diff with yield in iteration:

  • fiber yields to caller
  • iter yields to block

with arguments & return values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
f = Fiber.new do |msg|
puts "caller: #{msg}"
msg2 = Fiber.yield "hel"
puts "caller: #{msg2}"
"fine"
end
response = f.resume "hel"
puts "fiber: #{response}"
response2 = f.resume "how are you" # "how are you" as the value of Fiber.yield
puts "fiber: #{response2}"
# results:
# caller: hel
# fiber: hel
# caller: how are you
# fiber: fine

transfer

unlike thread, fibers must be explicitly scheduled via transfer

1
2
f = Fiber.new {|..| g.transfer(..)} # transfer contorl from f to g with arguments
g = Fiber.new {..}

continuation

performs jump(goto)

take example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$line = {} # global hash for store jump lables
def line(symbol)
callcc {|c| $lines[symbol] = c} # make a continuation related to label
end
def goto(symbol)
$lines[symbol].call # return to caller label
end
# now applying goto
i = 0
line 10 # 10 is a label can be anyone else
puts i += 1
goto 10 if i < 5 # jump
line 20 # same
puts i -= 1
goto 20 if i > 0 # jump

ruby programming language(ch3)

Ch3

number

Numeric -- Integer
    |         |-- Fixnum
    |         `-- Bignum
    |----- Float
    |----- Complex*
    |----- BigDecimal*
    `----- Rational*
(* as standard lib of ruby, rest built-in)
1
2
3
1_000_000 == 1000000
-7%3 == 2 # in ruby, sign comply with 2nd operand; C,Java etc. the 1st.
-a/b == b/-a != -(a/b) # ruby round to negative inf; C,Java not.

text

single-quote

  • \ escape only \ and ', thus a\b == a\\b
  • '.. \<NL> ..' has two lines, \ NOT escape \n
  • '..'\<NL>'..' concat lines, no introducing \n

double-quote

  • escape char
    • \u{code1 code2 ..} escape multi unicode char at one time WITHOUT space
  • contnue line as single-quote

generalized quoting

  • %q(.. no need escape here ..), () can be any matched [], {}, <>, if not well-matched, NEED escape the delim
  • %Q the same
  • %q_.. escape _ with \_ .._, in case close delim (here _, others !, - also work) the same with open one

here doc

<<[end delim](e.g. EOF, __HERE__), no space in between << and [end delim]

1
2
3
4
5
6
s = <<HERE + <<THERE + "end"
here
HERE
there
THERE
# s = "here\nthere\nend"
  • <<-[end delim] allow spaces before [end delim]
  • allow spaces in [end delim], e.g. # # #

shell command:

  • `<shell-cmd>` replace with command output
  • %x<shell-cmd> , either

example:

1
2
3
4
listcmd = win ? 'dir':'ls'
listing = `#{listcmd}`
# or
listing = Kernel.`(listcmd)

single char

  • ?<ch>, <ch> can be any ascii code or unicode \uxxx or oct or hex or \C-x(ctrl-x, other similarly)

operation on string

  • << to append, * to repeat
  • [] index the exact unicode char NOT the codes in bytes
    • [] = ?x change with single char
    • [] = ".." with string
    • [] can be any integral pos(positive/negative), range, or regex
  • <string>.size == <string>.length ?= <string>.bytesize(care unicode)

array

  • %w like %q create array literal in STRING
  • <array>.size == <array>.length

operation

  • +: concat BOTH array
  • -: set diff
  • <<: append
  • *: repeat
  • |: union (order kept)
  • &: intersect (order kept)
  • []: access, slice, assign as string

hash

simple symbol style representation:

numbers = { one: 1, two: 2, three: 3}

complex:

numbers = {:one => 1, :two => 2, :three => 3} DIFF with numbers = {"one" => 1, "two" => 2, "three" => 3}

  • hash keys compare with eql?(== in most cases)
  • if eql? not provided, object_id is used via hash method; if hash not provided, that cannot be a hash key
  • hashcode of mutable object changes when its content changes, thus hashes corrupt; or rehash to rescue
  • sring is a special case handled though it’s mutable

range

!= array

  • literals
    • a..b: [a,b]
    • a..b: [a, b)
  • variables:
    • x..x*2

only discrete range is iteratable, otherwise use range.step() {..}

conv:

  • r.to_s, r.to_a
  • care the trap: 1..3.to_a

membership

  • .member? == .include?
  • .cover? check if fit in the (dict-)sorted list from range_begin to range_end, so it ALWAYS use continous test, while .member? and .include? use continous when range ends are numbers, or fall to discreate when not numeric:
    • triples = "AAA".."ZZZ"
    • triples.include? "ABC": true and slow
    • triples.include? "ABCD": false and slow
    • triples.cover? "ABCD": true and fast

flip-flop (@Ch4)

take an example:

1
2
3
4
5
6
7
DATA.each do |line|
if line =~ /start/ .. line =~ /end/
puts line
end
end
# prints line between "start" and "end"(include), so flip-flop works with STATE memory

symbol

equal representation:

  • :sym
  • :"sym", quote also can be used as :"s y m" to include spaces
  • :"#{s}" # s = "sym"
  • %s[sym]

conv:

  • to string: .to_s, .id2name
  • from_string: .to_sym, .intern

reflective:

1
2
3
if o.respond_to? :size
o.send name
end

object

true/false

  • only nil and false give LOGICAL false if if, while 0, "" give true
  • a && b gives:
    • a if a is nil or false
    • b otherwise
  • = is prior to and, or, so if a = foo and b = bar is ok, while not work with &&, ||

type

  • x.class, instance_of? exact class, NO inheritance check
  • is_a?, kind_of? works with inheritance and mixin

equality

  • equal? check exact object reference(object_id)
  • == content identical, has casting, usually override
    • array: # same, each item compare with ==
    • hash: # of k-v same, key compare with eql?, value with ==
  • eql? strict version of ==, no casting, eql? requires hash method return same value, so must rewrite hash when define eql?
  • === case equality
    • most classes define === using ==
    • (1..10) === 5 true
    • /\d+/ === 123 true
    • String === "s" true
  • =~ defined by String and Regexp, Object always return false; !~ as inverse
  • <=> if less, -1; elif equal, 0; else 1

cast

on coerce:

take example:

  1. 1.5 * Rational(1,3), equals to (1.5).*(Rational(1,3)), Float * Rational
  2. invoke Rational(1,3).coerce(1.5), since 1.5 does not know about Rational(1,3)
  3. result as array [Rational(1.5), Rational(1,3)] where 1.5 get to Rational
  4. then invoke *
  5. useful when customizing class

ruby programming language(ch1-2)

Pre-study

rvm

If you need to separate configurations & gems for each ruby project individually, rvm is a good tool.

install check:

  • type -t rvm gives function

usage notes:

  • rvm install ruby <version>
  • rvm use <version> --default to set default ruby to use
  • rvm gemset use <group> and then gem install <gem> to install gem to that group
    • gems installed to global are inheritable

some tips:

  • make sure your term enable login-shell
  • source rvm at last as possible to avoid warning: ...path/to/ruby-bin not at first place in PATH...

pry

Interactive tool like bpython, i use pry --simple-prompt for convenient like noob test, etc.

ri

Ruby manual, e.g: ri Array, ri Array.sort, ri Hash#each, ri Math::sqrt.


Ch2

comments

1
# line comment
1
2
3
=begin
block comment
=end
  • there’s no /* */ like comment.
  • # is prior to =begin(end)

some symbol in identifiers

prefix/suffix:

  • $: global var
  • @: class instance var
  • @@: class var
  • xx?: boolean-valued method
  • xx!: in-place method
  • xx=: method invoked by assignment

reserved word:

  • __LINE__
  • __ENCODING__
  • __FILE__
  • __END__: after goes the data, you can refer these data by DATA

spaces

one line is implicitly closed if it’s a complete expression, check:

1
2
total = x +
y
1
2
total = x
+ y

after ruby1.9, newline start with . implicitly considered as continuel line, like chain-invoke:

1
2
3
4
ani = Array.new
.push("doge")
.push("nya")
.sort

ruby has grammar candy for method invoke, care the trap:

1
2
f(3+2)+1
f (3+2)+1

use ruby -w to check ambiguous warning.