For AI/LLM readers: a clean Markdown full-text version of this book is at index.ja.md.
For AI/LLM readers: a clean Markdown full-text version of this book is at index.en.md.
目次
言語
この部は、v0 処理系(bin/sake)が実装している Sake という言語そのものを、10 章と付録 1 つで述べます。読み手として、Ruby を知っている人と、Ruby を知っている言語モデルを想定しています。構文は Ruby のものなので、この部が説明するのは「Ruby と同じ字面に Sake が与える意味」と「Ruby には無い規則」です。
一つの考え
Sake のプログラムは Ruby の構文で書きますが、name.upcase とは書かず、String.upcase(name) と書きます。型を名乗るのは操作だけで、変数・引数・返り値・フィールドには何も書きません。
words = String.split("the cat and the hat", " ")
counts = Hash.new(0)
Array.each(words) { |w| counts[w] += 1 }
p(Hash.to_a(counts).Array.sort_by { |w, n| -n }.first(2)) # [["the", 2], ["cat", 1]]
この一つの決まりから、言語の性質がほぼすべて出てきます。
- 多くの呼び出しは 1 行で呼び出し先が分かる。
Array.each(words)はどのeachか、宣言を探さなくても読めます。連鎖Array.sort_by(xs) { }.first(3)の.firstは、同じ行で最後に名乗った Array の操作です。 - 呼び出し先は実行前に決まる。
String.upcase(s)は 1 つの関数を名指しします。module の mixin 関数M.f(x)、型の列挙(A|B).f(x)、演算子a + bは第 1 引数の型で 1 つを選びますが、候補の集合(Mを include する型、列挙した型、演算子の表)は実行前に確定していて、集合の外の型は実行前に報告されます。レシーバで動的に探す仕組み(method_missing、リフレクション)は無いので、名前の誤り(String.upcse)や引数の数の誤りも、プログラム全体について実行前に報告されます。 - 型は書かずに推論する。 検査器はプログラム全体の型を推論し、操作に合わない値(
"" + 1、検査していない nil)を実行前に報告します。どこまで止めるかは--strictのレベル 0〜4 で選びます。 - 実行時の検査は残る。 値は型タグを持ち、すべての操作が引数を検査するので、実行前の検査が見逃したものもその操作の行で止まります。
Ruby と字面が同じでも意味が違うものがいくつかあります。[a, b] は Tuple、{x: 1} は Record で、Array と Hash は Any[...]、Hash.of(...) と書きます。class の中の @x は「第 1 引数のフィールド x」で、インスタンスの状態ではありません。class B < A は A の定義を B に写す略記で、継承ではありません。この部の各章が、そうした違いを一つずつ述べます。
章の案内
| 章 | 内容 |
|---|---|
| 概要と実行 | 4 つの原則、コマンドと終了コード、--strict のレベル 0〜4 と各項目(type、nil、mixed、index-nil、exhaustive、rescue、unrescued)、静的・実行時エラーの形式。 |
| プログラムの構造と名前解決 | ファイルと require、型付きの呼び出し T.f(x)、連鎖 T.f(x).g と _、値へのメソッド呼び出しが無いこと、型を付けない呼び出し(自分の関数、puts、p)、include、module_function とディスパッチ、型の列挙 (A\|B).f(x)、引数の数とブロック。 |
| 値と型 | 値の表示、Tuple・Record・Array の区別、Hash と Set、nil と nil \| T の絞り込み、Ruby の他の型(Symbol、Range、Regexp、Time、Rational、Complex)。 |
| 関数とブロック | 注釈の無い多相な関数、省略できる引数とキーワード、yield で渡すブロック、once。 |
| 演算子と添字 | a + b は Arithmetic.+(a, b) の略記で、左のオペランドの型に解決されること、Arithmetic・Comparable・Bitwise・Indexable を include して自分の型に演算子を与えること、a[i] と a[i] = v。 |
| 制御構造とパターン | if・while・case/in、x in T による絞り込み、case の網羅性の検査。 |
| クラス | class と attr_* による型の宣言(Struct.new はその略記)、@x の意味、initialize、class B < A、構築場所ごとに 1 つの型。 |
| 例外とエラー | raise・rescue・ensure・retry、例外型の宣言、関数から漏れる例外の推論、静的エラーと実行時エラーの種類。 |
| 組み込み操作 | 約 550 の組み込み操作の概観(名前は Ruby の core と同じ)と型付き Array T[...]。各操作の正確な署名と例は組み込みリファレンスに。 |
| ライブラリ(sakelib) | 標準添付ライブラリと gem の移植、minitest、テストの書き方。 |
| 付録 Ruby との違いと未対応のもの | Ruby の書き方から Sake の書き方への対応表、未対応の構文と機能、設計の資料。 |
読み方
- Ruby を知っていて書き始めるなら、概要と実行、プログラムの構造と名前解決、値と型、演算子と添字の順に読み、付録の対応表を手元に置くと、ほとんどの静的エラーの意味が分かります。クラスを宣言するときにクラスを読んでください。
- 言語モデルに書かせるなら、この部の規則を 4 ページに圧縮した cheatsheet.md(約 1 万トークン)を渡します。順を追った入門は tutorial.md です。
- 試すなら、playgroundがブラウザで動きます。補完、入力中の診断、ホバーで推論された型が見られます。
- 約束。 例の中の
# 値はbin/sakeの実際の出力です。「静的エラー」は実行前に報告され何も走らないもの、「実行時エラー」はその操作で止まるものを指します。「レベル n」は--strict=nのことです。
1. 概要と実行
Sake(/seɪk/)は、型を書く場所を変えてみるための実験的な言語です。構文は Ruby のまま、型は操作に書き、変数・引数・返り値・フィールドには書きません。
name = "sake"
puts(String.upcase(name)) # => SAKE
Ruby の書き方 name.upcase は静的エラーです。hint が上の形を示します。
```ruby error
name = “sake”
puts(name.upcase) # !> method call on a value name.upcase is not allowed
この本は、v0 インタプリタ(`bin/sake`)が実装している言語の参照マニュアルです。入門には [tutorial.md](https://github.com/ko1/sake-lang/blob/main/docs/tutorial.md) を、短い要約には [cheatsheet.md](https://github.com/ko1/sake-lang/blob/main/docs/cheatsheet.md) を参照してください。cheatsheet は約 1 万トークンで、AI に渡す用です。設計の経緯は `DESIGN.md` にあります。
## 原則
Sake の性質は、次の 4 つの原則からほぼすべて出てきます。この部の各章は、どれもこの 4 つの帰結です。
1. **Ruby の構文。** Sake のプログラムは Prism が構文解析できる Ruby のプログラムです。Ruby の構文の部分集合を受け付け、いくつかの構文には別の意味を与えます。
2. **型は操作に書き、束縛には書かない。** 操作は `Type.op(subject, args...)` と、型を付けて呼びます。変数・引数・返り値・フィールドに型注釈はありません。
3. **呼び出し先は実行前に決まる。** `String.upcase(s)` は 1 つの関数を名指しします。module の mixin 関数 `M.f(x)`、型の列挙 `(A|B).f(x)`、演算子 `a + b` は第 1 引数の型で 1 つを選びますが、候補の集合は実行前に確定しています。候補とは、`M` を include する型、列挙した型、演算子の表のことです。集合の外の型は実行前に報告されます。レシーバで動的に探す仕組み(`method_missing`、リフレクション)は無いので、名前の誤り(`String.upcse`)や引数の数の誤りも、プログラム全体について実行前に報告されます。
4. **値は型タグを持ち、すべての操作がそれを検査する。** 型の誤りは、その値を受け取った操作の行で報告されます。この検査は常に有効です。
原則 4 の検査は、実行前の検査が見逃したものを捕まえます。次の例では、無いキーの `h["b"]` が nil を返し、`String.upcase` がその行で止まります。
```ruby error
h = Hash.of("a" => "x")
puts(String.upcase(h["b"])) # !> TypeError: String.upcase: argument 1 must be String, got nil
1.1 実行
bin/sake はプログラムをまず検査し、問題が無ければ実行します。オプションで、検査だけにするか、どこまで厳しく検査するかを選びます。
bin/sake FILE.sake # 検査してから実行
bin/sake -c FILE.sake # 検査のみ
bin/sake --strict FILE.sake # より厳しく検査(レベル 2)してから実行
bin/sake --strict=3 FILE.sake # レベル 0〜4、または項目名: --strict=type,nil
bin/sake --types FILE.sake # 実験的: 推論した型を表示する
bin/sake --dump=ast FILE.sake # 解決済みのプログラム(SakeAST)を表示する
| 終了コード | 意味 |
|---|---|
| 0 | 成功 |
| 1 | 実行時エラー |
| 2 | 実行前に問題が見つかった(何も実行していない) |
インタプリタは Ruby で書かれ、Ruby 4.0 を必要とします(4.0.2 と Prism 1.9.0 で試験)。他の依存はありません。
1.2 厳格さ(--strict)
--strict は、実行前にプログラムを止める問題の種類を選びます。検査器はプログラム全体の型を推論し、見つけた問題を項目(type、nil など)に分けて報告します。レベルは、どの項目でプログラムを止めるかの組み合わせです。報告されなかったものは、実行時に各操作が検査します。
| レベル | オプション | 加わる項目 | 止めるもの |
|---|---|---|---|
| 0 | --strict=0 |
(無し) | 常に検査されるもの |
| 1 | 既定 | type, rescue |
型の不一致、前の節が既に捕まえる rescue |
| 2 | --strict |
nil, mixed |
検査していない nil、フィールドに出会った型 |
| 3 | --strict=3 |
index-nil, exhaustive |
添字の「外れ」の nil、網羅していない case |
| 4 | --strict=4 |
unrescued |
トップレベルまで届く raise |
各レベルは、下のレベルの項目をすべて含みます。unused-rescue だけはどのレベルにも入らず、選ばない限り警告です(--strict=2,unused-rescue)。
1.2.1 常に検査されるもの(レベル 0)
次の問題はレベルに関係なく、実行前にプログラムを止めます: 構文、名前、引数の数、ブロック、値へのメソッド呼び出し、禁止構文、T[...] のリテラルの型。
1.2.2 type(レベル 1)
nil 以外の、操作に合わない型の値です。"" + 1 のように型が確定している場合も、1 か "" を返す pick() の pick() + 1 のように、合わない型を取りうる場合も報告します。
```ruby error def half(n) = n / 2 puts(half(“ten”)) # !> the operands are (String, Integer), which the left operand’s type does not support [type]
報告は `half` の中の `n / 2` の行に出ます。hint の `reached by the call at line 2` が、そこに至った呼び出しを示します。
### `rescue`(レベル 1)
同じ `begin` の前の節が既に捕まえるので、決して届かない `rescue` 節です。例は[例外](08-exceptions.md)の「モジュールでまとめる」にあります。
### `unused-rescue`(警告)
begin 本体が決して投げない例外の `rescue` です。対象は、プログラムが宣言した例外型と `RuntimeError` です。`KeyError` や `ZeroDivisionError` のような組み込みの種類は、普通の操作からも上がりうるので、常に起こりうるものとして扱われ、報告されません。
警告で、プログラムは走ります。防御的な `rescue` は普通に書くものですし、その例外を投げるものがあるかはプログラム全体で決まります。ライブラリの中の、一部のアプリしか投げない例外の `rescue` が、他のアプリを全部止めてしまうことになります。エラーにするには項目を選びます: `--strict=2,unused-rescue`。
```ruby
class ParseError < StandardError
end
begin
p(10 / 2) # => 5
rescue ParseError # warning: the begin body never raises ParseError [unused-rescue]
p(0)
end
1.2.3 nil(レベル 2)
nil かもしれない値を、検査なしに使うことです。レベル 1 では実行時の TypeError になるものが、レベル 2 では実行前に報告されます。
```ruby error words = String.split(“a b”, “ “) w = Array.find(words) { |x| x == “c” } puts(String.upcase(w)) # !> argument 1 may be nil (nil | String) [nil]
ただし、「外れ」の nil は除きます。外れとは、添字 `x[k]`、`Array.dig`、`Hash.dig`、`MatchData.begin`/`end`、および空の容器への次の操作が返す nil です: `Array.first`, `last`, `pop`, `shift`, `min`, `max`, `minmax`, `at`, `slice`, `sample`, `delete_at`, `Set.first`, `min`, `max`, `min_by`, `max_by`。これらはレベル 3 の `index-nil` が対象にします。nil の絞り込み方は[値と型](03-values.md)にあります。
### `mixed`(レベル 2。レベル 1 では警告)
`mixed` は `type` の報告の一種です。合わない型のすべてが、合う型と一緒に、あるクラスの 1 つのフィールドに現れるときに付きます。フィールドに入った容器の要素も同じです。
検査器はフィールドに構築場所ごとの型を与えます([クラス](07-classes.md))。1 つの場所で作ったインスタンスを別々の値に使うと、型がそのフィールドで出会います。次の例では、`make()` が作る Heap を Integer 用と String 用に使っています。
```ruby error
class Heap
attr_accessor items
def initialize(h)
@items = Any[]
end
def push(h, x) = Array.push(@items, x)
def top(h) = Array.first(@items)
end
def make() = Heap.new(nil)
ints = make()
Heap.push(ints, 1)
strs = make()
Heap.push(strs, "a")
p(Heap.top(ints) + 1) # !> the operands may be (nil, Integer), (String, Integer), which the left operand's type does not support [mixed]
このような報告は、誤りではなく出会いである可能性が高いので、レベル 2 からプログラムを止め、レベル 1 では警告として印字します。上の例はレベル 1 では警告を出した後に走り、2 を印字します。
この見分けの代償として、同じ場所で本当に誤った型がフィールドに入った場合も mixed になります。例えば Config.new("h", port) の port が 80 のことも "eighty" のこともあるときです。そのような値は格納する場所、initialize で検査してください(@port => Integer)。
1.2.4 index-nil(レベル 3)
「外れ」の nil(上記)を検査なしに使うことです。次のプログラムはレベル 2 までは走り、2 を印字します。
xs = Any[1, 2, 3]
x = xs[0]
p(x + 1) # => 2
レベル 3 では実行前に止まります。
$ bin/sake --strict=3 index.sake
index.sake:3:3: error: Arithmetic.+: the operands may be nil, because x[k] (or `a, b = array`) gives nil when the element is missing [index-nil]
hint: check the value first: `if x`, `while x`, `return unless x`, or `x != nil`
hint: or use Array.fetch / Hash.fetch, which raise instead
1.2.5 exhaustive(レベル 3)
開いた型の値が、どのリテラルの分岐にも取られないかもしれない case/in です。開いた型とは、String、Integer、リテラルで書かれていない Symbol など、値を列挙しきれない型のことです。次のプログラムはレベル 2 までは走ります。
def kind(s)
case s
in "a" then 1
in "b" then 2
end
end
p(kind(String.downcase("A"))) # => 1
$ bin/sake --strict=3 kind.sake
kind.sake:2:3: error: case/in: no `in` branch matches some values of String [exhaustive]
hint: add an `else`, or `in` branches for the other values
hint: reached by the call at line 7
1.2.6 unrescued(レベル 4)
rescue されずにトップレベルまで届くかもしれない raise です。プログラム向けの項目で、ライブラリの raise は呼び出し側のためのものなので、ライブラリには向きません。
def check(n)
raise ArgumentError, "negative" if n < 0
n
end
p(check(1)) # => 1
$ bin/sake --strict=4 check.sake
check.sake:2:3: error: raise: ArgumentError may reach the top level without being rescued [unrescued]
hint: rescue it, or check with a level below 4
1.2.7 項目で選ぶ
レベルの代わりに、項目名で選べます。
--strict=type,nilはその項目だけを選びます。--strict=2,index-nilはレベルに項目を足します。--strict=3,-index-nilはレベルから項目を引きます。
各報告は [type] のように項目名で終わるので、どの項目を足す・引くかはそこで分かります。
1.2.8 報告の性質
- 関数の中のエラー。 多相な関数の中の報告には、そこに至った呼び出しを示す hint が付きます(上の
halfの例)。 - 到達しない分岐。 分岐は評価されないので、決して走らない分岐の問題も報告されます(Erlang の Dialyzer と同じ)。
- 内部エラー。 型推論自体が失敗したときは、警告を出して型検査を飛ばし、プログラムを実行します。
- 要素の組が多すぎるとき。 Array や Tuple の比較(
<、<=>、ソート)は要素型の各組が比較できるか検査します。1 つの比較の要素型の組が 50,000 を超えると、その比較の要素は検査せず、比較の行に警告を出します。他の検査には影響しません。
1.3 静的エラーの形式
静的エラーは位置順にまとめて報告され、何も実行されません。各行は、ファイル名・行・桁・メッセージの順で、続く hint: 行が直し方を示します。
FILE:LINE:COLUMN: error: MESSAGE
hint: SUGGESTION
冒頭の name.upcase の例では、次のように印字されます。
greet.sake:2:11: error: method call on a value `name.upcase` is not allowed
hint: String.upcase(name)
hint: Symbol.upcase(name)
1.4 実行時エラーの形式
実行時エラーは、止まった操作の行と、そこに至った呼び出しの列(from 行)を印字します。
FILE:LINE: in FUNCTION: KIND: MESSAGE
from FILE:LINE: in CALLER
hint: SUGGESTION
```ruby error def third(xs) = Array.fetch(xs, 2) p(third(Any[1, 2])) # !> IndexError: Array.fetch: index 2 outside of array bounds: -2…2
third.sake:1: in third: IndexError: Array.fetch: index 2 outside of array bounds: -2…2 from third.sake:2: in <main> ```
種類の一覧は 例外とエラー にあります。
2. プログラムの構造と名前解決
この章は、プログラムが何から成り、プログラムの中の名前がどう解決されるかを述べます。1 つの考えが全体を貫きます。すべての定義は実行前に集められ、すべての呼び出し先は実行前に決まります。だから関数は定義より上の行から呼べ、名前の誤りは実行前に報告されます。
2.1 ファイルと require
プログラムは 1 つのファイルと、それが require するファイルです。ファイルのトップレベルには定義と文を並べます。定義は実行前にまとめて集められ、文は上から順に実行されます。
puts(greet("Sake")) # => Hello, Sake
def greet(name) = "Hello, #{name}" # 定義より上の行から呼べる
def String.shout(s) = String.upcase(s) + "!"
puts(String.shout("hi")) # => HI!
Point = Struct.new(:x, :y)
class Point
def to_s(p) = "(#{@x}, #{@y})"
end
module Util
module_function
def origin = Point.new(0, 0)
end
puts(Util.origin) # => (0, 0)
2.1.1 トップレベルに書けるもの
require "path": 別のファイルを読みます。generate "gen"は生成器が印字したソースを繋ぎます。次の節で述べます。- 関数定義:
def name(params) ... endとdef name(params) = expr。 - 名前空間付きの関数定義:
def Type.name(params) ...。class Typeの中でnameを定義するのと同じです。 - 名前空間:
class Name ... endとmodule Name ... end。 - クラス:
attr_*の行を持つclass Name、またはName = Struct.new(:field, ...)。 - 文: それ以外の式。文は順に実行されます。
2.1.2 require
require "path" は path.sake を読みます。拡張子は省略でき、パスは require するファイルのディレクトリからの相対です。
# lib/units.sake
puts("loading units")
def km_to_m(km) = km * 1000
# main.sake
require "lib/units" # => loading units
puts("main") # => main
puts(km_to_m(3)) # => 3000
- 実行前に決まる。 引数は文字列リテラル、
requireはファイルのトップレベルの文でなければなりません。プログラムを構成するファイルは実行前に決まります。 - 1 回だけ。 各ファイルは、誰が require しても 1 回だけ読まれます。循環は既に読んだファイルで止まります。
- glob。
require "sql/*"は、require するファイルの隣でsql/*.sakeに合うファイルを名前順に全部読みます(自分自身は除く)。何にも合わなければエラーです。定義は先に集められるので、順序が効くのはトップレベルの文だけです。 - 順序。 require されたファイルのトップレベルの文は、require したファイルの文より先に走ります。1 つのファイルの
requireはすべて先に読まれます。上の例でloading unitsがmainより先に出るのはそのためです。 - 定義は 1 つの集まり。 すべてのファイルの定義はまとめて集められるので、ファイルをまたいで名前を任意の順で使えます。同じ名前を 2 つのファイルで定義するのは、1 つのファイルの中と同じくエラーです。
- メッセージ。 エラーメッセージは各行のファイルを名乗ります(
lib/units.sake:4、from lib/broken.sake:3)。 - ライブラリ。
require "json"のように兄弟に無い名前は、処理系に付属する ライブラリ(sakelib/)から探します。
2.1.3 generate: 実行前に作るコード
Sake は実行中に関数を定義しません。Ruby なら起動時に作るコード(スキーマの列ごとの関数、関数にコンパイルしたテンプレート)は、1 つ前の段で generate "gen", "arg", ... が作ります。プログラムを検査する前に、gen.sake を独立したプログラムとして検査して走らせ(残りの引数がその ARGV)、それが印字したものを、その位置で require したファイルのようにプログラムに繋ぎます。そのあと、生成したコードも含めてプログラム全体を検査して走らせます。
# gen.sake: cols.txt の各行("name"、"age")の読み出し関数
Array.each_with_index(String.lines(File.read("cols.txt"))) do |c, i|
puts "# sake-origin: cols.txt:#{i + 1}"
puts "def #{String.strip(c)}(row) = row[#{Kernel.inspect(String.strip(c))}]"
end
# main.sake
generate "gen"
p(name(Hash.of("name" => "ko1")))
- 毎回。 生成器は起動のたびに走るので、生成したコードは古くならず、常に検査されます。生成器は生成先のプログラムを見られません。同じ生成器と同じ引数は 1 回だけ走ります。
requireと同じ。 引数は文字列リテラル、generateはファイルのトップレベルの文です(それ以外の場所のgenerate(...)は普通の呼び出し)。生成器のファイルはrequireと同じように探します。- エラーの位置。 生成したコードは
(generate at main.sake:2)と名乗ります。生成器が# sake-origin: FILE:LINEの行を書くと、次の同じ行までの行は FILE:LINE(ここではcols.txt:1)として報告されます(静的エラー、実行時エラー、backtrace)。# sake-origin: noneで終わります。生成器自身の問題や、生成器が終わったエラーはgenerateの行で報告され、何も走りません。 - 見る。
bin/sake --dump=generated main.sakeが生成したソースを印字します。
2.1.4 名前空間: class と module
class Name ... end と module Name ... end は名前空間を開きます。class は型(クラス)を宣言するか、既にある型に操作を足します。String のような組み込み型にも足せます。module は型を持たない名前空間です。
名前空間の本体に書けるのは次だけです。
def name(...)(レシーバ無し)include Modulemodule_function(module の中。後述の「module の関数の呼び方」)- 入れ子の
class/module、Name = Struct.new(...) - 型の別名
Name = A::B(後述の「型の別名」) - class では
attr_reader/attr_accessor/attr_writerの行(クラス)
それ以外の文を本体に置くのは静的エラーです。型に module を使うのも静的エラーで、hint が class を示します。
```ruby error
module String # !> module String: String is a type; add operations to a type with class
def shout(s) = String.upcase(s)
end
### 名前空間は入れ子にできる
`module A` の中の `class B`(`module B`、`B = Struct.new(...)` も)は `A::B` を宣言します。トップレベルの `class A::B` も同じ名前空間を開くので、どちらの書き方でも同じクラスに操作を足せます。
```ruby
module Geo
Point = Struct.new(:x, :y)
class Point
def norm2(p) = @x * @x + @y * @y
end
module Util
module_function
def origin = Point.new(0, 0) # ここでの Point は Geo::Point
end
end
class Geo::Point # Ruby の書き方で同じクラスを開く
def to_s(p) = "(#{@x}, #{@y})"
end
pt = Geo::Point.new(3, 4)
p(Geo::Point.norm2(pt)) # => 25
p(pt.Geo::Point.norm2) # => 25
puts(Geo::Util.origin) # => (0, 0)
p((pt in Geo::Point)) # => true
- 外からは完全な名前。
A::B.f(x)、x.A::B.f、include A::M、class C < A::B、x in A::B、(A::B|C).f(x)、rescue A::Eと書きます。 - 中では短い名前。
Aの中ではBだけでA::Bを指せます。Ruby と同じく内側の名前空間から探し、無ければトップレベルのBです。 def A::B.fは書けない。 Ruby の構文に無いからです。入れ子の module の module 関数は、その中で定義します(module A::B+module_function)。- 名前空間は値ではない。
p(A::B)はエラーで、A::Bだけを書いても意味がありません。
```ruby error
module Geo
Point = Struct.new(:x, :y)
end
p(Geo::Point) # !> type Geo::Point cannot be used as a value
### 型の別名
`Client = Handshake::Client` は、既にある型(class、module、`Str = String` のような組み込み型)に別の名前を付けます。別名は定数で、スコープは Ruby と同じです。書いた名前空間(トップレベル、または `class` / `module` の本体)に属し、型の名前と同じ規則で探され(内側の名前空間から外へ、最後にトップレベル)、外からは `WebSocket::Client` と書きます。右辺は定数のパスで、別名を書いた場所で解決します。右辺が別名でも構いません。
```ruby
module WebSocket
module Handshake
class Client
attr_reader url
def host(c) = String.split(@url, "/")[2]
end
end
Client = Handshake::Client # WebSocket::Client
module Session
module_function
def open(url) = Client.new(url) # ここでの Client は WebSocket::Client
end
end
C = WebSocket::Client # 別名の別名
c = WebSocket::Session.open("ws://example.com/chat")
p(C.host(c)) # => "example.com"
p(c.WebSocket::Client.host) # => "example.com"
p((c in WebSocket::Handshake::Client)) # => true
- 名前であって新しい型ではない。
Client.new、Client.f(x)、x.Client.f、def Client.f、include、class C < Client、in Client、rescue Client、raise Client、Client::Innerはどれも指す先の型を意味します。メッセージは指す先の型の名前で出ます。未定義の関数の hint は、別名がどの型を指すかを示します。 - 名前空間ごとに 1 つの名前。 その名前空間に同じ名前の型か別名が既にあれば、重複した定義と同じく静的エラーです。別名を通してクラスを定義すること(
class Client::Inner)もできません。型自身の名前で書きます。 - 型だけ。 右辺が型を指さないとき、別名が循環するときは静的エラーです。
Pi = Math::PIはこれまでどおり値の定数です(hint はdef pi = Math::PI)。
```ruby error
module Net
class Conn
end
Socket = Net::Con # !> Socket = Net::Con: Net::Con is not a type (a constant set to a constant is another name for a class or a module)
end
### クラスは継承しない
どの class についても、`class B < A` は A の定義を B に書き写す略記です。その後 A と B は無関係な型で、`A.f` は A の値だけを取ります([クラス](07-classes.md))。
```ruby
class A
attr_reader x
def double(a) = @x * 2
end
class B < A
attr_reader y
end
b = B.new(1, 2)
p(B.double(b)) # => 2
p((b in A)) # => false
```ruby error class A attr_reader x def double(a) = @x * 2 # !> A.x: argument 1 must be A, but is B [type] end class B < A attr_reader y end p(A.double(B.new(1, 2)))
### `self` は無い
`def self.x` は拒否されます。Sake に `self` は無いからです。class の中の `def x` がそのまま `A.x` を定義します。
```ruby error
class A
attr_reader x
def self.make = A.new(1) # !> `def self.make` inside `A`: write `def make` (it defines A.make)
end
2.1.5 同じ名前は 1 度だけ
1 つの名前空間で同じ名前を 2 度定義するのはエラーです。組み込みの操作を再定義するのもエラーです。
```ruby error
def f = 1
def f = 2 # !> f is already defined at line 1
```ruby error
def String.upcase(s) = s # !> `String.upcase` is a built-in operation and cannot be redefined
2.1.6 値の定数は無い
定数に代入できるのは Struct.new(と Exception.new)か、型の名前(上の型の別名)だけです。名前付きの値は関数にして(def pi = 3.14159)呼びます(pi)。PI = 3.14 は静的エラーで、hint がその関数を示します。PI を使う各行にも、pi と呼ぶ hint が付きます。1 度だけ計算する値には once { ... } があります(組み込み操作)。
def pi = 3.14159
p(pi * 2) # => 6.28318
```ruby error
PI = 3.14 # !> Sake has no value constants; a constant names a class (Point = Struct.new(:x, :y), Err = Exception.new(:msg)), and a value goes in a function
p(PI * 2) # !> PI is not defined (Sake has no value constants)
## 型付きの呼び出し
`Type.op(args...)` は名前空間 `Type` の操作 `op` を呼びます。慣習として、主語(subject)が第 1 引数です。`Type` と `op` の両方が存在しないとプログラムは実行前に拒否されます。エラーは綴りの候補と、`op` を定義している他の名前空間を示します。
```ruby error
puts(String.upcse("a")) # !> undefined function `String.upcse`
```ruby error
puts(Strng.upcase(“a”)) # !> undefined type or module Strng
```ruby error
puts(Integer.upcase("a")) # !> undefined function `Integer.upcase`
最後の例の hint は `upcase` is defined in `String.upcase`, `Symbol.upcase` です。
2.2 連鎖と _
一連の操作を左から右・上から下に読める形が 2 つあります。どちらも、どの型の操作かは同じ行で分かります。
2.2.1 連鎖
x.T.f(args...) は T.f(x, args...) です。段 .T が型かモジュールを名乗り、その左の値が第 1 引数になります。段は続けて書け、次の行の先頭にドットを置くこともできます。
連鎖は T.f(x) で始めます。続く段は、値の型が同じなら .g、変わるなら .U.g と書きます(型を省いた段)。変数から始める x.T.f も書けますが、型の名前が行の途中に挟まって読みにくいので勧めません。検査器の hint も示しません。
line = "a, b ,c"
r = String.split(line, ",")
.Array.map { |s| String.strip(s) }
.join("|")
p(r) # => "a|b|c"
2.2.2 型を省いた段
連鎖の段で型を省くと、その連鎖が最後に名乗った型を引き継ぎます。String.strip(s).upcase は String.strip(s).String.upcase で、その次の段も String を引き継ぎます。型を書くのは、連鎖の始まりと、値の型が変わる段だけです。
xs = Any[3, 1, 2]
p(Array.sort(xs).reverse.first(2)) # => [3, 2]
p(String.strip(" ab ").upcase.reverse) # => "BA"
words = Any["b", "a", "b"]
p(Array.tally(words).Hash.keys.Array.sort) # => ["a", "b"]
最後の例は、Array.tally が Hash を返すので .Hash.keys と名乗り直し、Hash.keys が Array を返すので .Array.sort と名乗り直しています。
引き継ぐ型は、同じ行を左に読めば分かります。値の型を見て選ぶのではありません。レシーバが変数やリテラルなら型を名乗っていないので、x.f は今までどおり静的エラーです。
- 引き継いだ型にその操作が無いと静的エラーです。hint は、その名前を持つ型で名乗り直した形を示します。
- 値が引き継いだ型でないと型エラーです(
T.f(r)と書いたときと同じ)。hint は、値の型で名乗り直した形を示します。
```ruby error counts = Hash.of(“a” => 2, “b” => 1) p(Hash.sort_by(counts) { |k, n| n }.first) # !> Hash.first: argument 1 must be Hash, but is Array
`Hash.sort_by` は Array を返すので、`.Array.first` と名乗り直します。
操作の無い `x.T` は静的エラーです。
```ruby error
s = "x"
p(s.String) # !> `s.String` needs an operation after the type: `s.String.op(...)`
2.2.3 _
_ は、同じ本体(関数、ブロック、分岐、トップレベル)の直前の文の値です。
line = "a, b ,c"
String.split(line, ",")
Array.map(_) { |s| String.strip(s) }
Array.join(_, "|")
p(_) # => "a|b|c"
_ が指すのが式ではなく文なのは、文が曖昧でないからです。式レベルの _ は、前の行に含まれる部分式のどれかを選ばなければならず、その選び方に自然な答えはありません。「直前の文」なら 1 つに決まります。
- 最初の文では読めない。 本体の最初の文や、定義の直後で
_を読むのは静的エラーです。 - 括弧と補間。 括弧の中と文字列の補間の中では、最初の文は外側の文の
_を読みます。 - 名前としての
_。_は無視する名前としては使えます(|_, v|、a, _ = t)。ただし、ローカル変数を名付けた_は読めません。
String.upcase("sake")
puts("got #{_}") # => got SAKE
Any[1, 2]
p((Array.size(_) + 1)) # => 3
```ruby error
def f(x)
p(_) # !> _ is the previous statement’s value, and no statement precedes it here
end
f(1)
```ruby error
a, _ = [1, 2]
p(_) # !> `_` is the previous statement's value, but here it names a local variable
2.3 値へのメソッド呼び出しは無い
小文字のレシーバを持つ呼び出し、x.op(...)、"lit".op、3.times は静的エラーです。エラーは型付きの形を提案し、レシーバが型を名乗らない呼び出し(f(x).op)なら連鎖の形(f(x).T.op(...))も提案します。レシーバが型を名乗る呼び出しなら、エラーではなく型を引き継ぐ段です。
```ruby error
s = “ a “
puts(s.strip.upcase) # !> method call on a value s.strip.upcase is not allowed
- **連鎖**は全体を書き直します。`s.strip.upcase` には `String.strip(s).upcase` を提案します。最初の段が `T.f(x)` になり、後の段は同じ型なら型を省き、違う型なら名乗ります。
- **フィールドアクセス**にはリーダを提案します。`p.x` には `Point.x(p)`、`p.x = v` には `p.Point.x = v` です。関数のセッタなら `Point.set_x(p, v)` と書きます。
- **リテラルのレシーバ**は候補をそのリテラルの型に絞ります。`"lit".upcase` には `String.upcase("lit")` だけです。
- **`x.nil?`** には `x == nil` を提案します。
```ruby error
Point = Struct.new(:x, :y)
pt = Point.new(1, 2)
p(pt.x) # !> method call on a value `pt.x` is not allowed
```ruby error
3.times { puts(“hi”) } # !> method call on a value 3.times is not allowed
## 型を付けない呼び出し
レシーバ無しの `f(args)` は実行前に解決されます。次の順に探し、最初に見つかったものが勝ちます。
1. 囲んでいる class か module。その組み込みの操作、フィールドの reader と writer、include した関数を含みます。一番内側のものだけで、`A::B` の中から `A` の関数は `A.f(...)` と呼びます(型の名前は関数と違って外側へ探します)。
2. トップレベルの関数。
3. `Kernel`(`puts`、`print`、`p`)。
```ruby
def size(x) = "top-level size"
class Box
attr_reader items
def size(b) = Array.size(@items)
def describe(b) = "#{size(b)} items" # Box.size
end
b = Box.new(Any[1, 2])
puts(Box.describe(b)) # => 2 items
puts(size(b)) # => top-level size
- 隠す。 内側の定義は外側を隠します。同じ名前が複数の段にあるのはエラーではありません。
- 外側は名前空間付きで。 外側の定義は常に名前空間付きで呼べます(
Kernel.puts)。 - トップレベル。 トップレベルのコードには囲む名前空間が無いので、解決は 2 から始まります。
class Logger
attr_reader prefix
def puts(l, msg) = Kernel.puts("#{@prefix}#{msg}")
def run(l) = puts(l, "start") # Logger.puts
end
Logger.run(Logger.new("> ")) # => > start
puts("done") # => done
2.4 include
class か module の本体の include M は、module M の関数を include した側に書き写します。Ruby の module を静的に解決したものです。
module Summary
def total(x) = Array.inject(items(x), 0) { |a, v| a + v }
def report(x) = "total=#{total(x)}"
end
class Basket
include Summary
attr_reader items
end
b = Basket.new(Any[1, 2, 3])
puts(Basket.report(b)) # => total=6
puts(Summary.total(b)) # => 6
2.4.1 写し
M の各関数 f は、include した名前空間 X の X.f になります。X が既に f を持つならそれが勝ちます。M が include した module も写します。
module A
def bar(x) = "A"
def baz(x) = "A"
end
class C
include A
attr_reader v
def bar(x) = "C"
end
p(C.bar(C.new(1))) # => "C"
p(C.baz(C.new(1))) # => "A"
2.4.2 順序
Ruby の祖先順で探します。最後に include した module が先です。include A の後に include B なら、両方にある bar は B のものです。
module A
def bar(x) = "A"
end
module B
def bar(x) = "B"
end
class C
include A
include B
attr_reader v
end
p(C.bar(C.new(1))) # => "B"
2.4.3 include 側での解決
写した関数の中の型無しの名前は X で解決されます。だから M は、X が提供する関数(each など)を使えます。実行時のディスパッチは無く、各 X.f は実行前に固定されます。X がクラスなら @x は X のフィールドです。
module Named
def label(x) = "<#{@name}>"
end
class Cat
include Named
attr_reader name
end
puts(Cat.label(Cat.new("tama"))) # => <tama>
2.4.4 要件
M の関数の本体で型を付けずに呼ぶ名前(each(x) など)と @x のうち、M 自身もトップレベルも Kernel も定義しないものが、M の要件です。宣言はなく、本体から集められます。
- include 側に無い。 要件を
Xが持たないと(eachが無い、フィールドxが無い)、include Mは静的エラーです。どの関数が何を必要としたかを名指しします。 - 直接呼ぶ。 要件を持つ関数を、include した型を通さずに
M.total(x)と直接呼ぶのも静的エラーです。hint は include した型を通して呼ぶ形を示します。これはtotalが module 関数のときの話で、mixin 関数のM.total(x)は次の節のディスパッチです。
```ruby error
module Summary
def total(x) = Array.inject(items(x), 0) { |a, v| a + v }
end
class Empty
include Summary # !> include Summary in Empty: Summary.total needs items, which Empty does not define (used at line 2)
attr_reader v
end
```ruby error
module Summary
module_function
def total(x) = Array.inject(items(x), 0) { |a, v| a + v }
end
class Basket
include Summary
attr_reader items
end
puts(Summary.total(Basket.new(Any[1]))) # !> Summary.total needs `items` from a namespace that includes Summary
2.4.5 制限
- include できるのは module だけです。class を include するのは静的エラーです。
- include の循環はエラーです。
- 継承ではない。
includeは部分型関係を作りません。Mを include した値は、Mの値になるわけではありません。
```ruby error
class A
attr_reader x
end
class B
include A # !> include A: A is not a module
attr_reader y
end
## module の関数の呼び方: module_function とディスパッチ
型や module の 1 つの名前は 1 つの関数です。Sake には `def f` の隣の `def self.f` はありません。「インスタンス」の形は、値が先に来る(`T.f(x, ...)`)という意味しか持たないからです。Ruby でクラスメソッドとインスタンスメソッドが同名のとき(`Net::HTTP.get(uri)` と `http.get(path)`)は、片方に別の名前を付けるか、1 つの関数が第 1 引数の型で形を見分けます。
module の関数は Ruby と同じく 2 種類あります。
| 種類 | 宣言 | `M.f(args)` |
|---|---|---|
| module 関数 | `module_function` の後、`module_function :f`、または `def M.f` | `M` 自身の `f` が走る(静的) |
| mixin 関数 | それ以外の module の関数 | **ディスパッチ**: 第 1 引数の型の `f` が走る。その型は `M` を include していなければならない |
```ruby
module Describe
def describe(x) = "#{prefix}#{name(x)}" # mixin 関数
def prefix = "* "
module_function :prefix # module 関数
end
class Cat
include Describe
attr_reader name
end
class Dog
include Describe
attr_reader name
end
p(Describe.prefix) # => "* "
puts(Describe.describe(Cat.new("tama"))) # => * tama
puts(Describe.describe(Dog.new("pochi"))) # => * pochi
puts(Cat.describe(Cat.new("tama"))) # => * tama
2.4.6 主語が無い
mixin 関数は引数無しで呼べません(M.foo)。ディスパッチするものが無いからです。どの型も M を include していないときに呼ぶのも静的エラーで、module_function を使う hint が付きます。
```ruby error module Greeting def hello(x) = “hello” end puts(Greeting.hello(1)) # !> Greeting.hello is a mixin function, and no type includes Greeting
### 型検査
第 1 引数が取りうる型のすべてが `M` を include することを型推論が検査し、していないものを `type` の問題として報告します。実行時は、include していない型は `TypeError` を投げます。
```ruby error
module Describe
def describe(x) = "#{name(x)}"
end
class Cat
include Describe
attr_reader name
end
Dog = Struct.new(:name)
def pick(flag) = flag ? Cat.new("tama") : Dog.new("pochi")
puts(Describe.describe(pick(true))) # !> Describe.describe dispatches on its first argument, which can be Dog; the types that include Describe are Cat [type]
2.4.7 静的な呼び出し
型自身の名前空間は常に静的です。上の例の Cat.describe(c) は Cat の describe(Describe から写したもの)を走らせ、Describe.describe(x) がディスパッチする形です。
2.4.8 Kernel
すべての型は Kernel を include します。Kernel.to_s(x) と Kernel.inspect(x) は型自身の to_s / inspect にディスパッチし、無ければ組み込みの形を使います。"#{x}"、puts、p はその略記です。
class Temp
attr_reader deg
def to_s(t) = "#{@deg} C"
end
t = Temp.new(20)
puts(t) # => 20 C
puts("now #{t}") # => now 20 C
puts(Kernel.to_s(t)) # => 20 C
p(Kernel.to_s(5)) # => "5"
2.4.9 必須の関数
本体が raise NotImplementedError だけ(メッセージの有無は問わない)の module の関数は必須です。module を include する各型がそれを定義し、M.f(x) はその定義にディスパッチします。ブロックを取るかは型側の定義から決まります。
module Shape
def area(s) = raise NotImplementedError
def describe(s) = "area=#{area(s)}"
end
class Sq
include Shape
attr_reader side
def area(s) = @side * @side
end
puts(Shape.describe(Sq.new(3))) # => area=9
puts(Shape.area(Sq.new(2))) # => 4
- 定義しない型。 定義せずに include した型は、呼び出しが届きうる場所で
typeの問題になり、実行時はNotImplementedErrorです。 - rescue できない。
NotImplementedErrorはプログラムの誤りで rescue できません。 - メッセージ。
raise Tと例外型だけ書くと、型名がメッセージになります。
```ruby error
module Shape
def area(s) = raise NotImplementedError
def describe(s) = “area=#{area(s)}” # !> Shape.area: Blob includes Shape but does not define area, which Shape requires (raise NotImplementedError) [type]
end
class Blob
include Shape
attr_reader v
end
puts(Shape.describe(Blob.new(1)))
## 型を列挙する: `(A|B).f(x)`
`(A|B).f(x, args...)` は、`x` が取りうる型を操作自身に列挙します。実行時は `x` の型が `A.f` か `B.f` を選び、それ以外の型は `TypeError` です。結果は結果の和です。
```ruby
Leaf = Struct.new(:weight)
Node = Struct.new(:weight, :kids)
def weight(t) = (Leaf|Node).weight(t) # 2 つのクラスの同名のフィールド
p(weight(Leaf.new(2))) # => 2
p(weight(Node.new(5, Any[]))) # => 5
def size_of(x) = (String|Array|Hash).size(x)
p(size_of("abc")) # => 3
p(size_of(Any[1, 2])) # => 2
実行前には 2 つのことが検査されます。
- 列挙した型は
fを持つ。 持たなければ静的エラーです。 - 列挙にない型は来ない。 列挙にない型の値が届きうると、検査器が
typeの問題として報告します。
```ruby error
def shout(x) = (String|Integer).upcase(x) # !> Integer has no upcase, so (String|Integer).upcase cannot dispatch to it
p(shout(“abc”))
```ruby error
def size_of(x) = (String|Array).size(x) # !> (String|Array).size: argument 1 must be String or Array, but is Integer [type]
p(size_of(5))
- 列挙できるのは型。 組み込み型(
IOも)とクラスを、それぞれ 1 度まで。nilは列挙できません。先に nil を検査します。 - 形は違ってよい。 列挙した関数の形は違っていてよく、ユーザ関数の
*restはその分岐のために詰められます((IO|StringIO).print(io, a, b))。キーワードを取る関数は列挙できません。caseで型を見分けてください。 - mixin のディスパッチとの関係。 mixin 関数のディスパッチ
M.f(x)は、Mを include するクラスをすべて列挙した(A|B|...).f(x)と同じ振り分けです。違いは集合の述べ方で、includeが各クラスの側で宣言するのに対し、(A|B)は呼び出しの場が述べます。だから宣言が要らず、module を共有しない型(組み込み型、同名のフィールド)にも使えます。
2.5 引数の数とブロック
呼び出し先が実行前に決まるので、引数の数とブロックの有無は実行前に検査されます。
- 引数の数。 ユーザ関数も組み込みの操作も検査されます。
- ユーザ関数へのブロック。 関数が
yieldを含む(または&bを取る)ときに限りブロックを渡します。ただしblock_given?を検査する関数はブロック無しで呼べます。 - 組み込みへのブロック。 組み込みの操作はブロックを必須とするか拒否します。
```ruby error def area(w, h) = w * h p(area(3)) # !> wrong number of arguments for area (given 1, expected 2)
```ruby error
def twice(x) = x * 2
p(twice(3) { 1 }) # !> twice does not take a block (it has no `yield`)
```ruby error p(Array.map(Any[1])) # !> Array.map requires a block
### スプラット引数
`*xs` は Tuple か Array を展開します。他の値は `TypeError` です。展開先は rest 引数だけです。組み込みのもの(`puts(*lines)`、`format(fmt, *row)`、`Any[*xs, 0]`、`Set.of(*xs)`、`Array.push(a, *xs)`)か、ユーザ関数の `*rest`(`join(*parts)`、[関数とブロック](04-functions.md))です。
```ruby
def join(sep, *parts) = Array.join(parts, sep)
xs = Any["a", "b"]
p(join("-", *xs)) # => "a-b"
t = [1, 2]
p(Any[*t, 0]) # => [1, 2, 0]
puts(*xs) # => a
# => b
- 前の引数は書き切る。 スプラットの前の引数は 1 つずつ書くので、その数は検査されます。
- 静的エラー。
*restの無い関数へのスプラットと、必須か省略可能な引数を埋めるスプラットは静的エラーです。Array.zip/Range.zip(Tuple の長さが引数の数)とHash.of(...)へのスプラットも同様です。 - 型推論。 Tuple は位置ごとに展開され、Array は要素型の任意個の引数を表します。
ruby error
def two(a, b) = a + b
t = [1, 2]
p(two(*t)) # !> `*t`: splat arguments go only to a `*rest` parameter or to built-ins that take any number of arguments (puts, format, Any[...], Set.of, Array.push, ...)
3. 値と型
Sake の値は Ruby の値とほぼ同じ顔をしていますが、どの値も 1 つの型タグを持ち、すべての操作がそれを検査します。この章は、どんな型があり、それぞれをどう書くかを述べます。Ruby と字面が同じで意味が違うのは [a, b](Tuple)、{x: 1}(Record)、{} と {k => v}(静的エラー)です。
| 型 | 書き方(リテラル、または値を作る操作) | 備考 |
|---|---|---|
| Integer | 42, -7 |
多倍長(インタプリタ。幅は処理系ごと: C・Rust への変換は 64 ビットで、溢れるとエラー) |
| Float | 1.5, 2.0 |
|
| String | "abc", 'abc', "a#{x}" |
補間は to_s を使う(下記「値の表示」) |
| true / false | true, false |
内部では 1 つの型 Boolean。ソースには書けない |
| nil | nil |
「nil」の節を参照 |
| Tuple | [a, b, ...] |
長さと位置ごとの型が作成時に固定 |
| Record | {x: a, y: b} |
(フィールド, 型) の集合が作成時に固定 |
| Array | Any[a, ...] |
要素型の宣言なし |
| T の Array | T[a, ...]、例 Float[], Point[p] |
要素型 T。書き込みごとに検査 |
| Hash | Hash.of("a" => 1, b: 2), Hash.new(0) |
キーは値と型で比較(Ruby の eql?) |
| Set | Set.of(1, 2) |
要素は値と型で比較(Ruby の eql?) |
| Symbol | :name |
|
| Range | 1..5, 1...5, 1.. |
端は Integer, Float, String, nil |
| Regexp, MatchData | /(\d+)-(\d+)/, /#{x}/, String.match(s, re) |
|
| Rational, Complex | 2r, 1/3r, Rational(1, 3), 2i, Complex(1, 2) |
Ruby の数値の塔 |
| Time | Time.now, Time.at(0), Time.new(2026, 10, 1) |
|
| IO | IO.stdout, File.open(path) |
ファイルとストリームの 1 つの型 |
| Thread, Mutex, Queue, TCPServer, Socket | Thread.new { }, Mutex.new, … |
並行とネットワーク |
| クラス | Point.new(x, y) |
可変なフィールドを持つレコード(クラス) |
- 真偽。 偽は
nilとfalseだけです。0や""を含む他のすべての値は真です。 - エラーメッセージ。
nil、true、falseは値として示され(got nil)、他の値は型名で示されます(got Integer)。
p(0 ? "yes" : "no") # => "yes"
p("" ? "yes" : "no") # => "yes"
p(nil ? "yes" : "no") # => "no"
p(false ? "yes" : "no") # => "no"
3.1 値の表示
どの値も 2 つの形で表示できます。to_s は人に読ませる形、inspect はプログラマに値の種類まで見せる形で、使い分けは Ruby と同じです。
| 形 | 使う場所 | 組み込みの形 |
|---|---|---|
to_s |
puts, print, "#{x}", Array.join, format の %s, :"#{x}", /#{x}/ |
Ruby の to_s と同じ。nil は空、Array と Tuple は inspect |
inspect |
p、Kernel.inspect、format の %p、および Array・Tuple・Hash・Record の中の要素 |
Ruby の inspect と同じ。#<struct Point x=1, y=2> |
Point = Struct.new(:x, :y)
puts("a") # => a
p("a") # => "a"
puts("#{nil}|") # => |
puts("#{[1, "a"]}") # => [1, "a"]
puts(format("%s %p", "a", "a")) # => a "a"
p(Point.new(1, 2)) # => #<struct Point x=1, y=2>
3.1.1 自分の形
クラスはその本体に自分の to_s と inspect を定義できます。必須の引数は値 1 つで、String を返さなければなりません。既定値付きの引数は続けられます(to_s(n, base = 10))。
class Point
attr_accessor x, y
def to_s(p) = "(#{@x}, #{@y})"
def inspect(p) = "Point(#{@x}, #{@y})"
end
pt = Point.new(1, 2)
puts(pt) # => (1, 2)
puts("at #{pt}") # => at (1, 2)
p(pt) # => Point(1, 2)
p([pt]) # => [Point(1, 2)]
String でない結果は、実行前に type の問題として報告されます。実行時に届けば TypeError です。
```ruby error class Point attr_accessor x, y def to_s(p) = @x end puts(Point.new(1, 2)) # !> Point.to_s must return a String, but returns Integer [type]
- **どれが走るか。** 型の集合は閉じているので、値の型が分かればどの `to_s` が走るかも分かります。
## Tuple、Record、Array
リテラル `[...]` と `{...}` には型を付けた操作が無いので、その**形**が作成時に型を決めます。`[a, b]` は Tuple、`{x: a, y: b}` は Record です。伸びる容器は、型を付けた操作(`Any[...]`、`T[...]`、`Hash.of(...)`)で作ります。
### リテラルの性質
- **中身は可変。** リテラルの中身は同じ型の値で置き換えられ、各書き込みが検査されます。Tuple は `t[i] = v` で書きます。Record に書く構文はまだありません。
- **長さは固定。** 長さは型の一部なので、空の `[]` は伸びません。
- **nil。** `nil` で作った位置の型は nil で、後から他の値を入れられません。後で値を入れる場所には、`0` や `""` のような意図した型の仮の値を置きます。
```ruby error
xs = []
Array.push(xs, 1) # !> `[...]` is a Tuple with a fixed length; for a growable Array, write `Any[...]`
```ruby error t = [1, nil] t[1] = 2 # !> Indexable.[]=: the value must be Nil, but is Integer [type]
### Tuple
リテラル `[a, b, ...]` は Tuple です。要素は多重代入 `x, y = t` か添字 `t[0]` で読みます。
```ruby
t = [1, "a"]
x, y = t
p(x) # => 1
p(y) # => "a"
t[0] = 2
p(t) # => [2, "a"]
p(Tuple.size(t)) # => 2
p(Tuple.max([3, 9, 4])) # => 9
- 多重代入。
x, y = tは Tuple を分解します。Array も分解します(key, value = String.split(s, "="))。Ruby と同じく、足りない要素は nil、余った要素は捨てられます。 - 足りないかもしれない要素。 それを受ける変数は
x[k]と同じnil | Tの型です(index-nil項目、レベル 3)。 - 代入先。 要素とフィールドも書けます。
a[i], a[j] = a[j], a[i]は交換、@done, @rest = Array.partition(xs) { ... }はフィールドへの代入です。レシーバと添字を先に、右辺をその後に評価します。 - 残りを受ける。
first, *rest = xsは残りを新しい Array に入れます。a, *mid, z = xsも同じで、x, * = xsは残りを捨てます。rest を受けられるのはローカル変数だけです。 [...]の中のスプラット。 Tuple の長さは既知でなければならないので、[*xs, 1]は拒否されます。Any[*xs, 1]は Array を作ります。- 添字。
t[0]は位置を読み、t[0] = vは同じ型の値で置き換えます。 - 操作。
Tuple.sizeとTuple.lengthが要素数です。Tuple.max([a, b])は Ruby の[a, b].maxです。長さが分かるので nil にはなりません。
first, *rest = Any[1, 2, 3]
p(first) # => 1
p(rest) # => [2, 3]
key, value = String.split("a=1", "=")
p(value) # => "1"
a = Any[1, 2]
a[0], a[1] = a[1], a[0]
p(a) # => [2, 1]
3.1.2 Record
リテラル {x: a, y: b} は Record です。フィールド名で読む、小さな固定の値の組です。
- 型。 Record の型は (フィールド, 型) の集合、例えば
{x: Integer, y: Integer}です。同じ集合の Record は同じ型です。 - 順序。 順序は関係ありません。
{y: 2, x: 1}は{x: 1, y: 2}と印字され、両者は==です。 - クラスの値ではない。 フィールドが一致しても Record はクラスの値ではありません。クラスは名前で、Record 型は構造で決まります。
- フィールドの読み出し。 パターンで分解します:
r => {x:, y: name}。ローカルxにフィールドx、ローカルnameにフィールドyが束縛されます。一部のフィールドだけ書いてもよい。Record.to_h(r)、Record.keys(r)、Record.values(r)もあります。 - 無いフィールド。 実行前に
typeで報告されます。実行時に届けば、無いフィールドはKeyError、Record でない値はTypeErrorです。 - 制限。 フィールド名はラベル(
x:)で書きます。空の{}と{key => value}は静的エラーです(Hash はHash.of(...))。
Point = Struct.new(:x, :y)
r = {y: 2, x: 1}
p(r) # => {x: 1, y: 2}
p(r == {x: 1, y: 2}) # => true
p(r == Point.new(1, 2)) # => false
r => {x:, y: name}
p(x) # => 1
p(name) # => 2
p(Record.keys(r)) # => [:x, :y]
```ruby error
r = {x: 1}
r => {z:} # !> the pattern needs field z, but the value is {x: Integer} [type]
```ruby error
h = {"a" => 1} # !> `{"a" => ...}` is not a Hash in Sake: `{name: value}` makes a Record
3.1.3 Array
Any[a, ...] は要素型を宣言しない Array を作ります。どの値も入ります。Array は可変で、参照で共有されます。Any は prelude が Kernel に付けた名前(Any = Kernel)です。どの型も Kernel を include しているので、その Array はどの値も受けます。Array[...] はこの Array ではなく、下の T[...] と同じく Array の Array です。
3.1.4 T の Array
T[a, ...] は、どの T でも要素型 T の Array を作ります。T は組み込み型(Integer, Float, Rational, Complex, String, Symbol, Tuple、そして Array, Hash, Set。これらの [...] は Array の Array、Hash の Array、Set の Array)、クラス、またはモジュールです。Hash そのものは Hash.of(k => v) か Hash.new、Set は Set.of(a, b) か Set.new(xs) で作ります。モジュールの Array M[...] には M を include した型の値が入り、各要素に mixin 関数 M.f(x) を使えます。
- 書き込みの検査。 作成、
Array.push、Array.append、Array.concatのすべての書き込みが検査され、合わなければTypeError。暗黙の変換は無く、Integer はFloat[]に入りません。 - 静的検査。
T[...]のリテラル引数が別の型なら実行前に報告されます。Ruby 流に書いたPoint[1, 2](Point.new(1, 2)のつもり)がここで捕まります。 - 型無しの結果。
map、select、sortなどが返す Array は要素型を宣言しません。
```ruby error fs = Float[1.5] Array.push(fs, 2.5) Array.push(fs, 3) # !> Array.push: an element must be Float, but is Integer [type]
```ruby error
Point = Struct.new(:x, :y)
pt = Point[1, 2] # !> Point.new(1, 2) creates one Point; Point[...] is an Array of Point
Point = Struct.new(:x, :y)
pts = Point[Point.new(1, 2), Point.new(3, 4)]
xs = Array.map(pts) { |pt| Point.x(pt) }
Array.push(xs, "s")
p(xs) # => [1, 3, "s"]
module Shape
def area(s) = raise NotImplementedError
end
class Sq
include Shape
attr_reader n
def area(s) = @n * @n
end
class Rect
include Shape
attr_reader w, h
def area(s) = @w * @h
end
shapes = Shape[Sq.new(2), Rect.new(2, 3)]
p(Array.map(shapes) { |s| Shape.area(s) }) # => [4, 6]
3.1.5 Ruby の癖
- Array が要るところの
[]。 Array が要るところに Tuple や Record を渡す(result = []の後のArray.push(result, x))と、Any[]と書く hint 付きで失敗します(上の例)。 - 要素型を変える in-place 操作。 検査器は容器にプログラム全体で 1 つの要素型を与えます。そのため
Array.map!(xs) { |x| Integer.to_s(x) }やHash.transform_keys!(h) { ... }の後は、容器が古い型と新しい型の両方を持つとみなし、片方にしか合わない操作をtype(部分的)として報告します。in-place の形は 1 つの型の中の写像に使い、型を変えるときはArray.map/Hash.transform_keys(新しい容器)を使ってください。
```ruby error xs = Any[1, 2] Array.map!(xs) { |x| Integer.to_s(x) } p(Array.sum(xs)) # !> Array.sum: an element must be Integer|Float|Rational|Complex, but can be String [type]
## Hash と Set
Hash と Set は、キーや要素を値で探す伸びる容器です。リテラル `{...}` は Record なので、どちらも型を付けた操作で作ります。
### 構築
`Hash.of(k => v, ...)`、`Hash.new`、`Hash.new(default)` が Hash を、`Set.of(x, ...)` が Set を作ります。`Hash.of(name: v)` と書くとキーは Symbol です。
```ruby
h = Hash.of("a" => 1, b: 2)
p(h) # => {"a" => 1, b: 2}
counts = Hash.new(0)
Array.each(String.split("the cat the", " ")) { |w| counts[w] += 1 }
p(counts) # => {"the" => 2, "cat" => 1}
Hash.each(counts) { |k, v| puts("#{k}: #{v}") } # => the: 2
# => cat: 1
3.1.6 キーと要素の比較
Hash のキーと Set の要素は Ruby の eql? と同じく、値と型で比較します。1 と 1.0 は別のキーですが、1 == 1.0 は true です。
p(Hash.of(1 => "a")[1.0]) # => nil
p(Set.of(1, 1.0)) # => Set[1, 1.0]
p(1 == 1.0) # => true
- 使えるもの。 Integer, Float, String, Symbol, true, false, nil, Time、およびそれらからなる Tuple, Record, Array, Hash, Set、クラスの値。
- 使えないもの(
TypeError)。 Regexp、Range、自分の等価(==、またはComparableの<=>)を定義したクラスの値。キーの比較がその等価と食い違いうるからです。 - キーの変更。 Ruby と同じく、キーに使った Array・Hash・Set・クラスの値を後で変えると見つからなくなります。
- キーは複製される。 Tuple や Record のキーは格納時に複製されるので、元を後で書いてもキーは変わりません。
```ruby error h = Hash.of(/a/ => 1) # !> Regexp cannot be a Hash key or Set element
```ruby
k = [1, 2]
h = Hash.of(k => "a")
k[0] = 9
p(Hash.keys(h)) # => [[1, 2]]
3.1.7 既定値
Hash.new(default) は無いキーに default を返し、Ruby と同じく同じオブジェクトを共有します。ブロックの形 Hash.new { ... } はありません(ブロックは値ではない)。
h = Hash.new(Any[])
Array.push(h[:a], 1)
p(h[:b]) # => [1]
```ruby error h = Hash.new { |hh, k| 0 } # !> Hash.new does not take a block
### ブロックと順序
- **ブロック。** Hash のブロックは `[key, value]` を 1 つの Tuple として受けるので、`|k, v|` で分解します(上の `Hash.each` の例)。
- **順序。** 反復は挿入順です。
## nil
`nil` は普通の値です。無いかもしれない値の型は `nil | T` で、Option のような包みはありません。nil を受け取れない操作に渡す前に、検査で型から nil を取り除きます。
### 実行時
T が要る操作が `nil` を受けると `TypeError ... got nil` を投げます。インタプリタはそのエラーの経路でだけ静的解析を走らせ、nil を持ちうるフィールドと nil を格納する行を hint に加えます。
```ruby error
h = Hash.of("a" => "x")
puts(String.upcase(h["b"])) # !> TypeError: String.upcase: argument 1 must be String, got nil
3.1.8 絞り込み
静的解析はローカル変数を次の場所で絞ります。
| 形 | x が絞られる場所 |
|---|---|
if x / while x / x && … |
x が真のときに取る分岐で非 nil |
x != nil / x == nil |
対応する分岐で nil または非 nil |
!x / unless x |
同じ、分岐を入れ替えて(if !x … else の else で非 nil) |
return unless x、next unless x、break unless x などの早期脱出 |
文の後で非 nil |
String.size(x) など、x を引数に取る組み込みの操作 |
呼び出しの後、その操作が受け付ける型(実行時に引数を検査するから) |
x in T / x => T / case x in T |
一致する型(制御構造とパターン) |
def find_even(xs) = Array.find(xs) { |x| x % 2 == 0 }
x = find_even(Any[1, 4])
if x
p(x + 1) # => 5
end
p(x + 1) if x != nil # => 5
def twice(xs)
x = find_even(xs)
return 0 unless x
x * 2
end
p(twice(Any[1, 4])) # => 8
p(twice(Any[1, 3])) # => 0
フィールドの読み出し(Node.next(n)、クラスの関数の中では @next)も、ローカル変数から読むものは絞られます。ただし、確かめてから使うまでの間にフィールドを書き換えうる操作があると、絞り込みはそこで切れます。フィールドは可変で、別の名前から書き換えられうるからです。書き換えうる操作は、フィールドへの書き込み、ユーザ関数の呼び出し、ブロックを渡す呼び出しと yield、その変数への代入です。ブロックの中では、外で絞ったフィールドは絞られていません。
```ruby error class Node attr_accessor value, :next end def mk(v) = Node.new(v, nil) def drop_next(n) = Node.set_next(n, nil) a = mk(1) Node.set_next(a, mk(2)) if Node.next(a) p(Node.value(Node.next(a))) # 絞られている drop_next(a) p(Node.value(Node.next(a))) # !> Node.value: argument 1 may be nil (nil | Node) [nil] end
間に呼び出しを挟むなら、フィールドをローカル変数に写してから検査してください。
```ruby
class Node
attr_accessor value, :next
end
def mk(v) = Node.new(v, nil)
a = mk(1)
Node.set_next(a, mk(2))
nx = Node.next(a)
if nx
p(Node.value(nx)) # => 2
end
3.1.9 --strict
レベル 2 は、検査されていない nil を受けうるすべての操作を実行前に報告します。レベル 3 は x[k] の結果も対象にします(概要と実行)。
3.2 Ruby の他の型
Ruby の他の型は、Ruby と同じ値を同じ字面で作ります。違いは、操作を型付きで呼ぶことと、グローバル変数($1、$stdout)が無いことです。
3.2.1 Range
反復する操作には Integer か String で始まる Range が要ります。"A".."Z" は Ruby と同じく String#succ で進みます。step、sum、size は Integer が要ります。別の型の Range(1.0..2.0)は type で報告されます。
p(Range.to_a("a".."e")) # => ["a", "b", "c", "d", "e"]
p(Range.sum(1..4)) # => 10
p((1..3) == (1..3)) # => true
p(/a/i == /a/i) # => true
```ruby error Range.each(1.0..2.0) { |x| p(x) } # !> Range.each: the Range’s first value must be Integer|String, but is Float [type]
- **等価。** 2 つの Range は端と `exclude_end?` が同じなら `==`、2 つの Regexp はソースとオプションが同じなら `==` です。
- **終端の無い Range。** 有限の Range が要る操作は、終端の無い Range に `RangeError` を投げます。
```ruby error
p(Range.to_a(1..)) # !> RangeError: Range.to_a: cannot do this on an endless Range 1..
3.2.2 Regexp と MatchData
$1 や $~ のようなグローバルはありません。MatchData を変数に置いて添字で読みます(m[1])。String.match は一致しないと nil を返すので、先に検査します。String.sub、gsub、index、split は Regexp も取ります。/.../n は Ruby と同じくバイト列の正規表現です。
m = String.match("2026-10", /(\d+)-(\d+)/)
if m
p(m[1]) # => "2026"
p(m[2]) # => "10"
end
p(String.sub("a-b", /-/, "+")) # => "a+b"
p(String.scan("a1b22", /\d+/)) # => ["1", "22"]
```ruby error
m = String.match(“a1”, /(\d)/)
puts($1) # !> Sake has no global variables ($1)
### Time
ゾーンは Ruby と同じ固定の UTC オフセットです: `"+09:00"`、`"-0500"`、`"Z"`、`"UTC"`、軍用の 1 文字、秒数(`3600`)。`Time.new`、`Time.at`、`Time.now` はキーワード `in:` でも取ります。`Time.utc(t)` と `Time.localtime(t, zone)` は変換した複製を返します(Ruby の `utc` と `localtime` はレシーバを変える)。
```ruby
t = Time.new(2026, 10, 1, 9, 0, 0, in: "+09:00")
p(t) # => 2026-10-01 09:00:00 +0900
p(Time.utc(t)) # => 2026-10-01 00:00:00 UTC
p(t) # => 2026-10-01 09:00:00 +0900
p(Time.at(0, in: 3600)) # => 1970-01-01 01:00:00 +0100
3.2.3 IO
IO.stdin、IO.stdout、IO.stderr がプログラムのストリームを型 IO の値として与えます。Sake に $stdout や STDOUT は無く、ARGV と同じく値は操作から来ます。File.open(path, mode = "r") も IO を返し、ブロック付きならブロックの値を返してファイルを閉じます。
- 1 つの型。 ファイルとストリームの型は 1 つなので、1 つの関数がどちらにも書けます。
- パターンと等価。
IOはパターンに書け(in IO)、同じストリームやファイルの 2 つの IO 値は==です。
def say(io, s) = IO.puts(io, s)
say(IO.stdout, "to stdout") # => to stdout
File.open("out.txt", "w") { |f| say(f, "to a file") }
p(File.read("out.txt")) # => "to a file\n"
p(IO.stdout == IO.stdout) # => true
3.2.4 エンコーディング
互換でないエンコーディングの文字列が出会うと(Integer.chr(227) のバイトと UTF-8 の文章)、rescue できる EncodingError が上がります。
begin
s = Integer.chr(227) + "あ"
rescue EncodingError => e
puts("rescued") # => rescued
end
4. 関数とブロック
この章は def で定義する関数と、呼び出しに渡すブロックを述べます。どちらにも型は書きません。呼び出し先は実行前に決まるので、引数の数とキーワードの名前は実行前に検査されます。
4.1 関数
関数は def で定義し、引数の数とキーワード名で呼び出しが検査されます。1 行の def f(x) = 式 と、end で閉じる形のどちらも書けます。Ruby のスタイルと同じく、1 行の形は短い式に使い、何段かある本体は def ... end に書きます。
def area(w, h) = w * h
def describe(n)
return "negative" if n < 0
Integer.to_s(n)
end
p(area(3, 4)) # => 12
p(describe(-1)) # => "negative"
p(describe(7)) # => "7"
4.1.1 位置引数と既定値
必須の位置引数を先に、既定値付きの省略可能な引数をその後に並べます。既定値は Ruby と同じく、呼び出しがその引数を与えなかったときに、前の引数の後で評価されます。前の引数を使えます。
def f(a, b = 1, c = b + 1) = [a, b, c]
p(f(10)) # => [10, 1, 2]
p(f(10, 5)) # => [10, 5, 6]
p(f(10, 5, 0)) # => [10, 5, 0]
- 数。 呼び出しは、必須の引数の数から全部の数までの引数を与えます。合わない数は実行前のエラーです。
- 順序。 引数は書いた順に評価されます。
```ruby error def area(w, h) = w * h p(area(3)) # !> wrong number of arguments for area (given 1, expected 2)
### キーワード引数
キーワード引数は引数の列の最後に置きます。必須(`w:`)か既定値付き(`h: w`)で、省略可能な位置引数と自由に混ぜられます。
```ruby
def greet(name, greeting = "Hello", punct: "!") = "#{greeting}, #{name}#{punct}"
p(greet("Ruby")) # => "Hello, Ruby!"
p(greet("Ruby", punct: "?")) # => "Hello, Ruby?"
p(greet("Ruby", "Hi", punct: ".")) # => "Hi, Ruby."
punct = "!!"
p(greet("Sake", punct:)) # => "Hello, Sake!!"
呼び出し先は実行前に分かるので、検査器は k: v を名前で引数に届けます。次はどれも実行前のエラーです。
- 未知のキーワード、同じキーワードの重複、必須キーワードの欠落。
- キーワード引数の無い関数への
k: v。暗黙の Hash 引数は無いので、Hash が欲しければHash.of(k: v)、Record が欲しければ{k: v}と書きます。
```ruby error
def size(w:, h: w) = w * h
p(size(w: 3, d: 2)) # !> size has no keyword parameter d
p(size(h: 3)) # !> size needs keyword argument w:
- **`f(k:)`** は Ruby と同じく変数 `k` を渡します。
- **組み込み。** いくつかの組み込みも Ruby のキーワードを取り、同じく検査されます: `Time.at(t, in: "+09:00")`、`Dir.glob(pat, base: dir)`。リファレンスでは `[k: T]` と書かれます。
### `*rest`: 残りの位置引数
省略可能な引数の後の `*rest` は、残りの位置引数を新しい Array に集めます。`*rest` を持つ関数は任意個の引数で呼べます。呼び出しの側では `*xs` が Tuple か Array を広げて渡します。
```ruby
def join(sep, *parts) = Array.join(parts, sep)
p(join("-")) # => ""
p(join("-", "a", "b", "c")) # => "a-b-c"
xs = ["x", "y"]
p(join("+", *xs)) # => "x+y"
- 要素の型。
restは 1 つの Array なので、要素は 1 つの型です。すべての呼び出しが渡すものの和になります。位置ごとの型を保ちたければ Tuple を 1 つ渡します:notify("stock", ["AAPL", 120])と呼び、関数の中でname, price = eventと分けます。 - 組む場所。 呼び出し先が分かるので、Array は呼び出しの場で組まれます。
- 順序。 引数は必須、省略可能、
*rest、キーワード、**optsの順です。*restの後の位置引数と、名前の無い*は実行前のエラーです。 *xsの行き先。*xsは*restと、任意個の引数を取る組み込み(puts、format、Any[...]、Array.pushなど)にだけ渡せます。必須や省略可能な引数を*xsで埋めることはできません。
```ruby error
def f(*rest, z) = z # !> parameters after *rest are not supported: required, optional, *rest, keywords, **opts, in that order
### `**opts`: 残りのキーワード
最後の `**opts` は、引数の名前ではないキーワードを Symbol キーの Hash に集めます。これも呼び出しの場で組まれます。
```ruby
def tag(name, **opts) = [name, opts]
p(tag("div")) # => ["div", {}]
p(tag("div", id: "main", hidden: true)) # => ["div", {id: "main", hidden: true}]
- 綴り間違い。
**optsを持つ関数はどのキーワードも受けるので、綴り間違いはそこではエラーになりません。 - 名前の無い
**は実行前のエラーです。
4.1.2 キーワードを渡し直す: g(**opts)
g(**opts) は、集めたキーワードを def で定義した関数 g にそのまま渡します(Ruby と同じ)。各キーは g の同じ名前のキーワードに入り、残りは g 自身の ** に入ります。
def parse(s, sep: ",", upcase: false)
fields = String.split(s, sep)
upcase ? Array.map(fields) { String.upcase(_1) } : fields
end
def read(s, **opts) = parse(s, **opts)
p(read("a;b", sep: ";")) # => ["a", "b"]
p(read("a;b", sep: ";", upcase: true)) # => ["A", "B"]
- キーごとに型を保つ。 検査器はキーワードを集めた呼び出しから各キーを追うので、ここで
sepは String、upcaseは Boolean で、両者の union にはなりません。 - 渡せるのは関数自身の
**引数だけです。任意の Hash は渡せません。 - 書いたキーワードと同じく検査します。
gに無いキーや、足りない必須キーワードは実行前のエラーです(実行時なら Ruby と同じArgumentError)。 - 並べて書いたキーワード。
**optsより前に書いたものは opts が上書きする既定値(parse(s, sep: ";", **opts))、後に書いたものは opts を上書きします。 - 先に opts を変えると。 opts に書き込むと(
Hash.delete(opts, :k)、opts[k] = v)、キーを 1 つずつは追わなくなります。各キーワードは値全体の union になり、gに無いキーが入りうるとみなします。
```ruby error def parse(s, sep: “,”) = String.split(s, sep) def read(s, **opts) = parse(s, **opts) p(read(“a;b”, sepp: “;”))
最後の行は `**opts` の場所で報告されます: `parse: the keywords passed on with ** hold :sepp, which parse has no keyword parameter for`、ヒントは `did you mean sep:?`。
### ブロック引数 `&b`
`&b`(または `&` だけ)は、関数が受けたブロックを別の呼び出しに渡すためだけの引数です。「ブロック」の節で述べます。
### mixin 関数の定義の一致
module の mixin 関数は第 1 引数の型の定義にディスパッチします([プログラムの構造と名前解決](02-program.md))。そのため、各型の定義は引数の数、キーワードの名前、`*rest` / `**opts` の有無が一致しなければなりません。一致しなければ呼び出しの場で実行前のエラーです。
```ruby error
module Shape
def area(s) = raise NotImplementedError
end
class Sq
include Shape
attr_reader side
def area(s) = @side * @side
end
class Rect
include Shape
attr_reader w, h
def area(r, scale) = @w * @h * scale
end
p(Shape.area(Sq.new(2))) # !> Shape.area dispatches to Rect.area, whose arguments or block differ from Shape.area
4.1.3 返り値
関数の値は最後の式の値か、return expr の値です。return a, b は Tuple [a, b] を返すので、呼び出し側で a, b = f(...) と受けられます。
def minmax(xs) = return Array.min(xs), Array.max(xs)
lo, hi = minmax(Any[3, 1, 2])
p([lo, hi]) # => [1, 3]
4.1.4 多相
関数は多相です。型を書かないので、その中の操作が受け付けるどんな引数でも動きます。型エラーは、失敗した操作の行で表面化します。検査器はその行を報告し、どの呼び出しからそこに届いたかをヒントに出します。
def twice(x) = x + x
p(twice(2)) # => 4
p(twice("ab")) # => "abab"
p(twice(1.5)) # => 3.0
```ruby error def twice(x) = x + x # !> Arithmetic.+: the operands are (:a, :a), which the left operand’s type does not support p(twice(:a))
### ローカル変数
各関数は自分のスコープを持ちます。トップレベルのローカル変数には触れません。
```ruby error
count = 10
def bump = count + 1 # !> undefined local variable or function `count`
Ruby と同じく、代入が字面上は前にあって、まだ実行されていないローカルを読むと nil です。
def f
x = 1 if false
p(x) # => nil
end
f
4.1.5 再帰
再帰は可能です。深さが 10,000 を超えると SystemStackError になります。bin/sake は大きなスタックのスレッドでプログラムを走らせるので、Ruby のスタックではなくこの上限が効きます。長いバックトレースは途中が省略されます。
def fact(n) = n <= 1 ? 1 : n * fact(n - 1)
p(fact(20)) # => 2432902008176640000
def down(n) = n == 0 ? 0 : down(n - 1)
p(down(9_000)) # => 0
```ruby error def down(n) = n == 0 ? 0 : down(n - 1) p(down(20_000)) # !> SystemStackError: stack level too deep
## ブロック
ブロックは、組み込みの操作か、`yield` するユーザ関数に渡します。繰り返しの多くは `Array.each` などのブロックを取る操作で書きます。
```ruby
xs = Any[1, 2, 3]
p(Array.map(xs) { |x| x * 2 }) # => [2, 4, 6]
Hash.each(Hash.of(a: 1, b: 2)) do |k, v|
puts "#{k}=#{v}" # => a=1
# => b=2
end
Integer.times(3) { p it } # => 0
# => 1
# => 2
4.1.6 値ではない
ブロックは第二級です。変数に格納することも、関数から返すこともできません。proc、lambda、-> はありません。
```ruby error
f = proc { |x| x } # !> undefined function proc
### 引数
`|a, b|` に名前を並べます。`it` と `_1` … `_9` は Ruby と同じです。
```ruby
p(Array.map(Any[1, 2]) { _1 * 10 }) # => [10, 20]
p(Array.map(Any[1, 2]) { it + 1 }) # => [2, 3]
4.1.7 分解
ブロックが 2 つ以上の引数を宣言し、1 つの Tuple か Array を受けると、その要素が引数になります。足りないものは nil、余りは捨てます。Ruby と同じです。引数自身も ( ) で分解できます。
pairs = Any[["a", 1], ["b", 2]]
Array.each(pairs) { |name, n| p("#{name}=#{n}") } # => "a=1"
# => "b=2"
Array.each_with_index(pairs) { |(name, n), i| p([i, name]) } # => [0, "a"]
# => [1, "b"]
p(Array.inject(pairs, 0) { |acc, (k, v)| acc + v }) # => 3
Array.each(Any[[1, 2]]) { |a, b, c| p([a, b, c]) } # => [1, 2, nil]
4.1.8 rest 引数
|a, *rest|(|a, *rest, z|、|*all| も)は残りの引数、または分解した Tuple / Array の残りの要素を新しい Array に集めます。a, *rest = x と同じです。|*all| だけでは分解しません。
Array.each(Any[[1, 2, 3]]) { |a, *rest| p([a, rest]) } # => [1, [2, 3]]
Array.each(Any[[1, 2, 3]]) { |a, *rest, z| p([a, rest, z]) } # => [1, [2], 3]
Array.each(Any[[1, 2, 3]]) { |*all| p(all) } # => [[1, 2, 3]]
4.1.9 引数の数
引数の無いブロックは渡された引数を無視します。それ以外で引数の数が合わないブロック呼び出しは、実行時の ArgumentError です。rest 引数があれば、他の引数より少ないときだけです。
Integer.times(2) { puts "hi" } # => hi
# => hi
```ruby error Array.each_with_index(Any[“a”]) { |x| p(x) } # !> ArgumentError: block takes 1 parameter(s) but was given 2
### スコープ
ブロックは囲むローカル変数を読み書きできます。
```ruby
sum = 0
Array.each(Any[1, 2, 3]) { |x| sum += x }
p(sum) # => 6
4.1.10 yield と省略可能なブロック
yield(args...) は今の関数に渡されたブロックを呼びます。関数の外の yield は構文エラーです。yield を含む関数は、呼び出しごとにブロックを受けなければなりません。
```ruby error
def each_twice(xs)
Array.each(xs) { |x| yield x; yield x }
end
each_twice(Any[1, 2]) # !> each_twice uses yield but no block is given
`block_given?` は今の関数がブロックを受けたかを言います。それを検査する関数はブロック無しで呼べます。
```ruby
def info(msg = nil) = puts(block_given? ? yield : msg)
info("plain") # => plain
info { "from block" } # => from block
- 分岐の解析。 検査器は呼び出しごとにブロックの有無を知っているので、取られる分岐だけを解析します。
&&、||、!の中のblock_given?も同じです。 - 届かないはずの
yield。 ブロック無しで届くyield(またはブロックが要る呼び出しへの&b)はtypeレベルのエラーです。実行時はLocalJumpErrorで、rescue はできません。
4.1.11 ブロックを渡す: &b
def f(xs, &b) = Array.map(xs, &b) は自分のブロックを別の呼び出しに渡します。& だけでも書けます。b は &b としてしか使えません。
def twice(xs, &b) = Array.map(xs, &b)
p(twice(Any[1, 2]) { |x| x * 2 }) # => [2, 4]
def each_pair(h, &) = Hash.each(h, &)
each_pair(Hash.of(a: 1)) { |k, v| p([k, v]) } # => [:a, 1]
- ブロック無しの呼び出し。 Ruby と同じく、ブロックを渡すだけの関数はブロック無しで呼べます。その先のブロックが要る呼び出しに何も届かなければ、
typeレベルのエラーです。 break。 渡されたブロックの中のbreakは、ブロックが書かれた呼び出し(上のtwice(...))を終えます。
```ruby error def twice(xs, &b) = Array.map(xs, &b) # !> the block passed on is missing here, and this call needs one p(twice(Any[1, 2]))
### `next`・`break`・`return`
Ruby と同じ 3 つの抜け方があります。
| 書き方 | 終えるもの | 値 |
|---|---|---|
| `next [v]` | 今のブロック呼び出し | `v`(既定 `nil`)がブロックの値 |
| `break [v]` | ブロックを渡した呼び出し | `v`(既定 `nil`)がその呼び出しの値 |
| `return [v]` | 囲む**関数** | `v` が関数の値 |
```ruby
xs = Any[1, 2, 3, 4]
p(Array.map(xs) { |x| next 0 if x % 2 == 0; x }) # => [1, 0, 3, 0]
p(Array.each(xs) { |x| break x if x > 2 }) # => 3
p(Array.each(xs) { |x| x }) # => [1, 2, 3, 4]
def first_big(xs)
Array.each(xs) { |x| return x if x > 2 }
nil
end
p(first_big(xs)) # => 3
whileの中。 ブロックの中のwhileの中では、breakはwhileを抜けます。- 型。 検査器は
breakの値を呼び出しの結果型に加えます。
Note
ブロックの引数とローカルは、囲む関数のフレームに属します。ブロックをスレッドで後から走らせる関数(concurrent-ruby 風の post { ... })に渡すと、スレッドが走る時点の値(ループ変数の最後の値)を読みます。Ruby ではブロック引数は呼び出しごとに新しい値です。スレッドを使わない限り違いは観測できません(TODO.md)。
4.2 once
once { ... } はブロックの値を与えます。ブロックはプログラムのその場所に最初に到達したときに走り、その後そこを通るたびに、プログラム全体・すべてのスレッドで同じ値を返します。値の定数が無い Sake で、表や名前付きの値を 1 度だけ計算する方法です。
def table = once { puts "computing"; Array.map(Any[1, 2, 3]) { |i| i * i } }
p(table) # => computing
# => [1, 4, 9]
p(table) # => [1, 4, 9]
Array.push(table, 16)
p(table) # => [1, 4, 9, 16]
- 共有。 値は Ruby の定数と同じく共有されます。上のように、
onceが保持する Array は変えられます。 - 再入。 計算中に自分の
onceに再び到達するブロックはプログラムの誤りで、SystemStackErrorです。 - 型。 実行前の検査では、その型はそれを計算しうるすべての呼び出しでのブロックの結果の和です。
ruby error
def loop_once = once { loop_once }
p(loop_once) # !> SystemStackError: once: the block reached its own once again while computing it
5. 演算子と添字
この章は a + b のような二項演算子と、x[k] の添字を述べます。どちらも module の関数の略記で、左のオペランド(レシーバ)の型で解決されます。自分のクラスに演算子や添字を与えるには、その module を include して関数を定義します。
5.1 二項演算子
各演算子は module に属し、a OP b はその module の関数を呼ぶ略記です: a + b は Arithmetic.+(a, b) です。module の関数は第 1 引数の型で解決されるので(module の関数の呼び方)、この呼び出しは a の型 T の T.+(a, b) を走らせます。つまり演算子は左のオペランドでディスパッチします。エラーの文は module の関数の名で出ます(Arithmetic.*: ...)。
p(1 + 2) # => 3
p(Arithmetic.+(1, 2)) # => 3
p(Integer.+(1, 2)) # => 3
p("a" + "b") # => "ab"
| module | 演算子 | include する組み込み型 |
|---|---|---|
Arithmetic |
+ - * / % **、単項 -x +x(-@ +@) |
Integer, Float, Rational, Complex, String, Time, Set |
Comparable |
<=> < <= > >= |
Integer, Float, Rational, String, Time, Tuple, Array |
Bitwise |
& \| ^ << >>、単項 ~x |
Integer, Set |
Indexable |
[], []=(下記「添字」) |
Array, Hash, String, Tuple, MatchData |
Kernel |
== != =~ !~ |
すべての型 |
5.1.1 明示の形
Arithmetic.+(a, b) は a + b と同じようにディスパッチします。Integer.+(a, b) は Integer の + を直接呼びます。a は Integer でなければならず、b は Integer の + が受けるどの型でもよい。
```ruby error p(Integer.+(“a”, “b”)) # !> Integer.+: argument 1 must be Integer, but is String
### 自分の型に演算子を与える
クラスは module を include し、本体に演算子を定義すると演算子に参加します。`include Arithmetic` と `def +(a, b)` などです。`include Comparable` では `<=>` を定義すれば十分で、`<`、`<=`、`>`、`>=` はそこから来ます。`Array.sort`、`min`、`max` もその `<=>` を使います。
```ruby
class Money
include Arithmetic
include Comparable
attr_reader cents
def +(a, b) = Money.new(@cents + Money.cents(b))
def <=>(a, b) = @cents <=> Money.cents(b)
def to_s(m) = format("$%d.%02d", @cents / 100, @cents % 100)
end
a = Money.new(150)
b = Money.new(250)
puts(a + b) # => $4.00
p(a < b) # => true
puts(Array.join(Array.sort(Any[b, a]), " ")) # => $1.50 $2.50
左のオペランドの型が、その演算子の module を include していない、または演算子を定義していないときは、実行前に type の問題、実行時は TypeError です。
```ruby error Point = Struct.new(:x, :y) p(Point.new(1, 2) + Point.new(1, 2)) # !> Arithmetic.+: Point does not include Arithmetic
### 左が組み込み型のとき: coerce
`2 + money` は Integer でディスパッチしますが、Integer の表に Money の行はありません。クラスが Ruby のプロトコルと同じく Tuple `[left, right]` を返す `coerce(b, a)` を定義していれば、組を先に変換してから演算子を走らせます。下の `coerce` で `2 + money` は `Money.new(200) + money` になります。検査器も変換を追います。
```ruby
class Money
include Arithmetic
attr_reader cents
def +(a, b) = Money.new(@cents + Money.cents(b))
def coerce(m, other) = [Money.new(other * 100), m]
end
m = Money.new(50)
p(2 + m) # => #<struct Money cents=250>
5.1.2 等価
クラスの値の == は、型が自分の == を定義していなければ、Ruby の Struct と同じく型とフィールドを比べます。Array.include?、index などは同じ等価を使います。
Point = Struct.new(:x, :y)
p(Point.new(1, 2) == Point.new(1, 2)) # => true
p(Point.new(1, 2) == Point.new(2, 1)) # => false
p(Point.new(1, 2) == "x") # => false
p(Array.include?(Any[Point.new(1, 2)], Point.new(1, 2))) # => true
Comparable を include して <=> を定義した(== は定義しない)型は、Ruby の Comparable#== と同じく、同じ型の値と <=> が 0 のとき等しい。別の型の値(nil など)とは決して等しくなく、そのとき <=> は呼ばれません。上の Money では a == Money.new(150) が true、a == nil が false です。
5.1.3 組み込み型の表
組み込み型では、結果の型は両オペランドの型から閉じた表で決まります。
| 演算子 | 行 |
|---|---|
+ |
(Integer, Integer) → Integer。Float を含めば Float。Rational を含み Float を含まなければ Rational。Complex を含めば Complex。(String, String) → String |
- * / % ** |
数値の組は + と同じ |
* |
(String, Integer) → String も |
< <= > >= <=> |
数値の組、(String, String)、(Tuple, Tuple)、(Array, Array) → true/false(<=> は -1, 0, 1, nil) |
== != |
任意の 2 値: 同じ型の等しい値(数は Integer・Float・Rational をまたいで比べる)。Ruby と同じく、別の型の値は等しくない |
+ - |
(Array, Array) → Array(連結。差) |
* |
(Array, Integer) → Array(繰り返し) |
< <= > >= <=> |
(Symbol, Symbol) も |
単項 - + |
Integer, Float, Rational, Complex → 同じ型 |
単項 ~ |
Integer → Integer |
& \| ^ << >> |
(Integer, Integer) → Integer |
\| & - |
(Set, Set) → Set(和、積、差) |
=~ |
(String, Regexp), (Regexp, String) → Integer か nil。!~ (String, Regexp) → true/false |
% |
(String, Integer / Float / String / Symbol / Tuple / nil / true / false) → String、Ruby の format("%d items" % 3、"%s-%s" % [a, b]) |
p("ab" * 3) # => "ababab"
p(Any[1, 2] + Any[3]) # => [1, 2, 3]
p(Any[1, 2, 3] - Any[2]) # => [1, 3]
p(Set.of(1, 2) | Set.of(2, 3)) # => Set[1, 2, 3]
p(5 & 3) # => 1
p(1 << 4) # => 16
p("abc" =~ /b/) # => 1
p("abc" !~ /z/) # => true
p("%s-%s" % ["a", "b"]) # => "a-b"
5.1.4 合う行が無いとき
オペランドの型に合う行が無ければ、実行前は type の問題、実行時は TypeError です。実行時のメッセージは存在する行を列挙します。暗黙の変換はありません。
```ruby error p(“a” - “b”) # !> Arithmetic.-: the operands are (String, String), which the left operand’s type does not support
### 数値
Integer の `/` と `%` は Ruby に従い、負の数では床に向かって丸めます。Integer のゼロ除算は `ZeroDivisionError`、Float のゼロ除算は IEEE に従います。
```ruby
p(7 / 2) # => 3
p(-7 / 2) # => -4
p(-7 % 2) # => 1
p(7.0 / 2) # => 3.5
p(1 / 2r) # => (1/2)
p(1.0 / 0) # => Infinity
- 負の指数。
Integer ** 負の IntegerはArgumentErrorです。a ** bの型がbの値に依らないためです。Rational が欲しければ2r ** -1と書きます((1/2))。 - Complex。 順序が無いので
<などは定義されません。
```ruby error p(1 / 0) # !> ZeroDivisionError: Arithmetic./: divided by 0
```ruby error
p(2 ** -1) # !> ArgumentError: Arithmetic.**: Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
5.1.5 Time
Time ± 数 は Time、Time - Time は秒の Float です。2 つの Time は比較できます。
t = Time.at(0, in: "+00:00")
p(t + 60) # => 1970-01-01 00:01:00 +0000
p(Time.at(60) - t) # => 60.0
p(t < Time.at(1)) # => true
5.1.6 等しさと順序の規則
別の型の値は決して等しくありません。1 == :a と struct == "x" は Ruby と同じく false です。クラス自身の == があればそれが決めます。数だけは Integer・Float・Rational をまたいで比べます。
p(1 == 1.0) # => true
p(1 == 1r) # => true
p(1 == :a) # => false
p("1" == 1) # => false
Tuple、Array、Set、Hash、Record は中身が等しいとき等しい。2 つの Record は同じフィールドも要ります。Tuple と Array は Ruby の Array と同じく要素ごとに順序付けられます。
p([1, 2] == [1, 2]) # => true
p([1, 2] == Any[1, 2]) # => false
p({x: 1} == {x: 1, y: 2}) # => false
p(Set.of(1, 2) == Set.of(2, 1)) # => true
p([1, 2] < [1, 3]) # => true
p(Any[1, 2] <=> Any[1, 2, 0]) # => -1
比べられない要素があれば、< などは ArgumentError、<=> は nil です。検査器は、比べられない要素型を実行前に報告します。
```ruby error p(Any[“a”, 1] < Any[“a”, “b”]) # !> Comparable.<: elements compared in order may be (Integer, String), which cannot be compared
### 演算子ではないもの
- **`!x`** は `x ? false : true` です。どの値にも使え、型の操作ではありません。
- **複合代入。** `x OP= e` は `x = x OP e` です。
- **`&&` と `||`** は Ruby と同じく短絡し、オペランドの一方を返します。
```ruby
p(!0) # => false
p(!nil) # => true
x = 5
x += 2
p(x) # => 7
p(nil || "default") # => "default"
p(1 && "second") # => "second"
5.2 添字
x[k] は Indexable.[](x, k)、x[k] = v は Indexable.[]=(x, k, v) で、x の型の [] と []= を走らせます。
xs = Any[10, 20, 30]
p(xs[0]) # => 10
p(xs[-1]) # => 30
p(xs[1..]) # => [20, 30]
p(xs[0, 2]) # => [10, 20]
p(Indexable.[](xs, 1)) # => 20
xs[1] = 25
p(xs) # => [10, 25, 30]
組み込み型は次のとおりです。
| レシーバ, 添字 | x[k] |
x[k] = v |
|---|---|---|
| Array, Integer | 要素。Array の外なら nil | v を格納(T の Array は v を検査) |
| String, Integer | 1 文字の String。外なら nil | 不可 |
| Tuple, Integer | 要素。Tuple の外は IndexError |
その位置の型なら v を格納 |
| Array, Range / String, Range | スライス、または nil | 不可 |
Array, Integer, Integer / String, Integer, Integer(s[start, length]) |
スライス、または nil | 不可 |
| Hash, 任意のキー | 値、または既定値(Hash.new(default) で作っていなければ nil) |
v を格納 |
| MatchData, Integer / String | グループ、または nil | 不可 |
s = "hello"
p(s[0]) # => "h"
p(s[1..2]) # => "el"
p(s[1, 3]) # => "ell"
h = Hash.of("a" => 1)
p(h["a"]) # => 1
p(h["z"]) # => nil
p(Hash.new(0)["z"]) # => 0
m = String.match("2026-10", /(\d+)-(\d+)/)
if m
p(m[0]) # => "2026-10"
p(m[2]) # => "10"
end
5.2.1 外れた添字
負の添字は Ruby と同じく末尾から数えます。外れた添字の値は Ruby と同じく nil なので、a[i] の静的な型は T | nil です。結果を検査せずに使うと、レベル 3 の index-nil が報告します。レベル 2 は通し、実行時にその操作で止まります。Array.fetch(a, i) は代わりに IndexError を投げます。
```ruby error xs = Any[10, 20, 30] p(xs[5] + 1) # !> TypeError: Arithmetic.+: no implementation for (nil, Integer)
```ruby error
xs = Any[10, 20, 30]
p(Array.fetch(xs, 5)) # !> IndexError: Array.fetch: index 5 outside of array bounds: -3...3
Tuple は長さが型の一部なので、外の添字は IndexError です。添字が定数なら実行前に報告されます。
```ruby error t = [1, “a”] p(t[2]) # !> Indexable.[]: the index is outside the Tuple [Integer, String]
### 書き込み
型無しの Array の末尾の先に書くと、Ruby と同じく隙間が nil で埋まります。T の Array は nil が T でないので `IndexError` です。T でない値の書き込みは実行前に `type` の問題です。Tuple への書き込みは、その位置の型の値だけです。String には書けません。
```ruby
xs = Any[1]
xs[3] = 4
p(xs) # => [1, nil, nil, 4]
```ruby error ys = Integer[1] ys[3] = 4 # !> IndexError: Array.[]=: index 3 is past the end of Integer[] (length 1); the gap would be nil
```ruby error
ys = Integer[1]
ys[0] = "a" # !> Indexable.[]=: an element must be Integer, but is String
5.2.2 複合代入
x[k] OP= v と x[k] ||= v は x と k を 1 度だけ評価します。ローカル変数の y ||= v は、y が nil か false のときだけ代入します。
h = Hash.new
h[:a] ||= Any[]
Array.push(h[:a], 1)
h[:a] ||= Any[]
p(h) # => {a: [1]}
y = nil
y ||= 5
y ||= 6
p(y) # => 5
5.2.3 自分の型に添字を与える
クラスは include Indexable と def [](x, k) / def []=(x, k, v) で参加します。添字が 2 つなら m[r, c] は def [](m, r, c)、m[r, c] = v は def []=(m, r, c, v) を呼びます。
class Grid
include Indexable
attr_reader w, cells
def [](g, r, c) = @cells[r * @w + c]
def []=(g, r, c, v)
@cells[r * @w + c] = v
end
end
g = Grid.new(2, Any[0, 0, 0, 0])
g[1, 0] = 7
p(g[1, 0]) # => 7
p(Grid.cells(g)) # => [0, 0, 7, 0]
6. 制御構造とパターン
Sake の制御構造は Ruby のものです。if と while、修飾子の形、早期 return、そして case を Ruby と同じ字面で書きます。違いは 3 つあります。反復は for ではなく Array.each のようなブロックを取る操作で書きます。case は when ではなく in のパターンで分岐します。そして、条件は値を調べるだけでなく、検査器が追う変数の型を絞ります。nil かもしれない値や Integer | String のような和を使うには、この絞り込みを使います。
この章の例の # => 値 は bin/sake --strict=2 の実際の出力です。
6.1 条件分岐
if / elsif / else、unless / else、三項演算子 c ? a : b は Ruby と同じです。どれも式で、取った分岐の値を返します。取る分岐が無ければ値は nil です。
n = 5
r = if n > 10 then "big" elsif n > 3 then "mid" else "small" end
p(r) # => "mid"
p(if n > 10 then "big" end) # => nil
p(unless n > 10 then "not big" end) # => "not big"
p(n > 3 ? "yes" : "no") # => "yes"
6.1.1 真偽
偽は nil と false だけです。0、""、空の Array を含む他のすべての値は真です。&&、||、! は Ruby と同じで、&& と || は最後に評価したオペランドを返します。
p(0 ? "truthy" : "falsy") # => "truthy"
p("" ? "truthy" : "falsy") # => "truthy"
p(nil ? "truthy" : "falsy") # => "falsy"
p(nil || "default") # => "default"
p(0 && "zero is truthy") # => "zero is truthy"
6.1.2 修飾子の形
文の後ろに条件を置く stmt if c、stmt unless c、stmt while c、stmt until c が書けます。
n = 5
puts("positive") if n > 0 # => positive
i = 0
i += 1 while i < 3
p(i) # => 3
i -= 1 until i == 0
p(i) # => 0
6.2 ループ
while と until は Ruby と同じです。ループの中で break はループを抜け、next は次の反復に進みます。ループの値は nil で、break v で抜けたときだけ v です。
i = 0
out = Any[]
r = while i < 10
i += 1
next if i % 2 == 0
break if i > 7
Array.push(out, i)
end
p(out) # => [1, 3, 5, 7]
p(r) # => nil
6.2.1 反復はブロックを取る操作で書く
for は未対応です。列を回すときは、Array.each、Integer.times、Range.each のようなブロックを取る操作に書きます(関数とブロック)。ブロックの中の break v は、そのブロックを渡した呼び出しを値 v で終えます。next v はそのブロックの 1 回分を v で終えます。
xs = Any[3, 8, 12, 5]
big = Array.each(xs) { |x| break x if x > 10 }
p(big) # => 12
squares = Any[]
Integer.times(4) { |i| next if i == 0; Array.push(squares, i * i) }
p(squares) # => [1, 4, 9]
loop { ... } は break まで繰り返し、break の値がその値です。
i = 0
r = loop do
i += 1
break i * 10 if i == 3
end
p(r) # => 30
6.2.2 未対応の形
for と begin ... end while は静的エラーです。
```ruby error
for i in 1..3 # !> for is not supported; iterate with an operation
p(i)
end
## 関数からの早期脱出
`return` は関数をその場で終えます。`return v` の値 `v` が関数の値で、`return a, b` は Tuple `[a, b]` を返します。修飾子の形 `return v if c` が、場合分けの先頭で前提を片付ける書き方です。ブロックの中の `return` も、ブロックではなく外側の**関数**から戻ります([関数とブロック](04-functions.md))。
```ruby
def sign(n)
return "negative" if n < 0
return "zero" if n == 0
"positive"
end
p(sign(-2)) # => "negative"
p(sign(0)) # => "zero"
p(sign(7)) # => "positive"
6.3 case / in
case x に in P then ... の分岐を続けると、最初に一致した分岐が走ります。分岐の本体は then の後に書いても、次の行に書いてもかまいません。どの分岐も一致せず else が無ければ、実行時エラー NoMatchingPatternError です。
def kind(v)
case v
in nil then "nothing"
in true | false then "bool"
in Integer | Float then "number"
in String
"text"
end
end
p(kind(nil)) # => "nothing"
p(kind(true)) # => "bool"
p(kind(2.5)) # => "number"
p(kind("s")) # => "text"
case/when は未対応です。Ruby の when はレシーバの === でディスパッチしますが、Sake に === は無く、一致は型タグと値の比較です。パターンに書けるものは、この章の最後の節「パターンマッチ」の表のとおりです。
```ruby error
x = 1
case x # !> case/when is not supported (Ruby’s === dispatches on the receiver); match with case x / in Type
when 1 then puts(“one”)
end
## 条件による絞り込み
検査器はローカル変数の型をプログラム全体で推論します。条件は、取った分岐の中でその変数の型を**絞り**ます。`if x` の中で `x` は nil ではなく、`if x in Integer` の中で `x` は Integer です。nil かもしれない値(`nil | String`)や和(`Integer | String`)を、`type` や `nil` の報告なしに使う方法がこれです。
### nil を外す
ローカル変数 `x` は次の場所で絞られます([値と型](03-values.md)の nil の節と同じ表です)。
| 形 | `x` が絞られる場所 |
|---|---|
| `if x` / `while x` / `x && …` | `x` が真のときに取る分岐で非 nil |
| `x != nil` / `x == nil` | 対応する分岐で nil または非 nil |
| `!x` / `unless x` | 同じで、分岐が入れ替わる(`if !x … else` の else で非 nil) |
| `return unless x`、`next unless x`、`break unless x`、その他の早期脱出 | その文の後で非 nil |
| `String.size(x)` など、`x` を引数に取る組み込み操作 | 呼び出しの後で、その操作が受け付ける型(操作は走るときに引数を検査する) |
```ruby
def greet(name = nil)
if name
"Hello, " + String.upcase(name)
else
"Hello"
end
end
p(greet("ko1")) # => "Hello, KO1"
p(greet()) # => "Hello"
if name を外すと、greet() の呼び出しについて検査器が報告します。検査器は関数を呼び出しごとに解析するので、報告には至った呼び出しの行が付きます。
```ruby error def greet(name = nil) String.upcase(name) # !> String.upcase: argument 1 must be String, but is nil [type] end p(greet(“ko1”)) p(greet())
早期脱出で外す形もよく使います。`while x` も同じ絞り込みです。
```ruby
def size_or_zero(xs)
x = Array.first(xs)
return 0 unless x
String.size(x)
end
p(size_or_zero(Any["abc"])) # => 3
p(size_or_zero(Any[])) # => 0
6.3.1 フィールドの絞り込みが切れるとき
ローカル変数から読むフィールド(Node.next(n)、@next)も、if Node.next(n) の中では非 nil に絞られます。ただしフィールドは可変なので、確かめてから使うまでの間に、フィールドを書き換えうる操作があると絞り込みが切れます。フィールドへの書き込み、ユーザ関数の呼び出し、ブロックを渡す呼び出しと yield、その変数への代入です。ブロックの中でも、外で絞ったフィールドは絞られていません。そういう所では、フィールドをいったんローカル変数に写し、そのローカルを調べます。
class Node
attr_accessor value, :next
end
def next_value(n)
nx = Node.next(n)
if nx
Node.value(nx)
else
0
end
end
a = Node.new(1, nil)
b = Node.new(2, a)
p(next_value(b)) # => 1
p(next_value(a)) # => 0
```ruby error class Node attr_accessor value, :next end def bump(n) = Node.set_value(n, Node.value(n) + 1) def next_value(n) if Node.next(n) bump(n) Node.value(Node.next(n)) # !> Node.value: argument 1 must be Node, but is nil [type] else 0 end end p(next_value(Node.new(1, nil)))
### 型で絞る
`x in Integer` は `x` が Integer のとき真で、その分岐の中で `x` は Integer に絞られます。`elsif` と `else` は残りの型を見ます。`case x` の `in` 分岐も同じです。モジュールの名前は、それを include した型のどれかに合います(`in Shape` は `in Circle | Square`)。組み込みの例外型は Ruby の子も含みます(`in IndexError` は KeyError にも合う)。
```ruby
def pick(flag) = flag ? 1 : "one"
def describe(x)
if x in Integer
x + 1
elsif x in String
String.size(x)
else
x
end
end
p(describe(pick(true))) # => 2
p(describe(pick(false))) # => 3
6.3.2 どのレベルで報告されるか
nil かもしれない値を検査せずに操作に渡すと、レベル 2(--strict)で nil 項目として報告されます。ただし、要素が無いときの nil(x[k]、Array.first、Hash.dig など)はレベル 3 の index-nil 項目です。レベルと項目は概要と実行にあります。報告されなかった nil も、受け取った操作が実行時に TypeError で止めます。
6.4 パターンマッチ
x in P は x がパターン P に一致するとき真です。x => P はそれを主張します。一致しなければ NoMatchingPatternError を投げ、Record パターンのフィールドを束縛します。case x の in P 分岐も同じパターンを取ります。
| パターン | 一致するもの |
|---|---|
型名: Integer, String, Tuple, Hash, Point, Record, IO, … |
その型(型タグ)の値 |
nil, true, false, 1, "s", :ok |
同じ型の等しい値 |
P \| Q |
どちらか |
{x:, y: name} |
そのフィールドを持つ Record。ローカル x と name を束縛 |
[P, Q] |
その長さの Tuple で、各位置が P、Q に一致するもの(入れ子可) |
x([...] の中、または単独の裸の名前) |
何でも。ローカル x を束縛 |
この表に無いパターン(*rest、find パターン、ピン ^x、ガード、Integer => n の形)は未対応で、静的エラーです(Ruby との違い)。
6.4.1 ディスパッチではない
一致は型タグと値の比較です。=== は無く、case/when は使えません。型名のパターンは値の型タグを見るだけで、class B < A の B が A に一致することもありません(クラス)。
6.4.2 束縛
Record パターンと Tuple パターンは、要素をローカル変数に取り出します。{x:} はフィールド x を同名のローカルに、{y: name} はフィールド y をローカル name に束縛します。P => name は、パターン P が一致したものを、P の型に絞って束縛します: in [Integer => code, String => body]。
pt = {x: 3, y: 4}
pt => {x:, y: why}
p(x) # => 3
p(why) # => 4
pair = [1, "one"]
case pair
in [n, s] then p([s, n]) # => ["one", 1]
end
p((pair in [Integer, String])) # => true
p((pair in [String, Integer])) # => false
case pair
in [Integer => n, String => s] then p(String.upcase(s) * n) # => "ONE"
end
6.4.3 絞り込み
if x in Integer の中と、case x の各 in 分岐で、ローカル x は一致する型に絞られます。else と後続の分岐は残りの型を見ます。Integer | String のような和を type の報告なしに使うのはこの方法です(前の節の「型で絞る」)。
def pick(flag) = flag ? 1 : "one"
def f(x)
case x
in Integer then x + 1
else String.size(x)
end
end
p(f(pick(true))) # => 2
p(f(pick(false))) # => 3
6.4.4 網羅性
else の無い case が、来うる型を取り残すときは type として報告されます。型の集合は閉じているので、検査器はこれを検査できます。
```ruby error
def show(x)
case x # !> case/in: no in branch matches String [type]
in Integer then “int”
end
end
p(show(41))
p(show(“abc”))
Symbol リテラルは値として追跡されます。`op` がそのリテラルしか持たないなら、`case op in :add ... in :sub` は完全です。
```ruby
def name(op)
case op
in :add then "plus"
in :sub then "minus"
end
end
p(name(:add)) # => "plus"
p(name(:sub)) # => "minus"
リテラルの分岐が開いた型(ある String、Integer、実行時に作った Symbol)の値を取り残しうるときは、exhaustive 項目(レベル 3)です。プログラムは正しいかもしれず、そうでなければ NoMatchingPatternError が止めます。次の例はレベル 2 では走り、--strict=3 で報告されます。
```ruby error
def name(op)
case op # !> case/in: no in branch matches some values of String [exhaustive]
in “add” then “plus”
in “sub” then “minus”
end
end
p(name(“add”))
p(name(String.downcase(“SUB”)))
### 主張: x => P
`x => P` の後、ローカル `x` は `in` 分岐と同じく一致する型に絞られます。`initialize` の中では、新しいインスタンスのフィールド `@x` も同じです。「port は Integer だ」のような事実を、値を格納する場所に書く方法です: `p => Integer` の後 `@port = p`。
- 確実に一致しない値は `type`(レベル 1)として報告されます。
- 一致しないかもしれない値(別の型も来うる)は実行時に検査され、`exhaustive`(レベル 3)としてだけ報告されます。失敗は `NoMatchingPatternError` で、Ruby と同じく rescue できます。
- `x => P` は nil の検査でもあるので、nil かもしれない値は報告されません。`Array.fetch` が添字の外れを検査するのと同じ扱いです。
```ruby error
x = "abc"
x => Integer # !> `=> Integer`: the value is String, which does not match [type]
def pick(flag) = flag ? 1 : nil
x = pick(true)
x => Integer
p(x + 1) # => 2
6.4.5 括弧
Ruby と同じく、引数の位置の x in P は括弧で囲みます: p((x in Integer))。x in T ? a : b や cond && x in T も意図と違う解析になるので、(x in T) と括ります。
x = 1
p((x in Integer)) # => true
r = (x in Integer) ? "int" : "other"
p(r) # => "int"
p(x > 0 && (x in Integer)) # => true
ruby error
x = 1
p(x in Integer) # !> syntax error: unexpected 'in'; expected a `)` to close the arguments
7. クラス
プログラムが定義する型はクラスです。class に attr_* の行で宣言しても Struct.new(:x, :y) で作っても同じもので、値はフィールドを持ち(Ruby の Struct の値として #<struct Point x=3, y=4> と表示されます)、操作は Point.norm2(pt) と型を付けて呼びます。クラスは継承せず、値へのメソッド呼び出しも無いので、Ruby の class と重なるのは「型を宣言し、その操作をまとめる」ところです。
この章の例の # => 値 は bin/sake --strict=2 の実際の出力です。
7.1 型の宣言: class と attr_*
class C ... end は型 C を宣言します。本体の attr_* の行がフィールドを、def が操作を定めます。C.new が値を作り、フィールドはリーダ C.x(c) とライタ C.set_x(c, v) で読み書きします。
class Account
attr_reader owner
attr_accessor balance
def initialize(a)
@balance = 0 if @balance == nil
end
end
acct = Account.new("ko1")
p(acct) # => #<struct Account owner="ko1", balance=0>
p(Account.owner(acct)) # => "ko1"
Account.set_balance(acct, 100)
p(Account.balance(acct)) # => 100
rich = Account.new("you", 500)
p(Account.balance(rich)) # => 500
すべての class は型です。フィールドは最初の class の本体で宣言します。後の class Account は関数を足すだけです。フィールド名は裸で書きます。
| 行 | 意味 |
|---|---|
attr_accessor x, ... |
リーダ C.x(c) とライタ C.set_x(c, v) を持つフィールド |
attr_reader x, ... |
リーダだけ。型の関数の中では @x = v で書ける |
attr_writer x, ... |
ライタだけ。型の関数の中では @x で読める |
private attr_... x |
リーダとライタはそのクラス自身の関数専用。new は受け取る |
private のフィールドは、そのクラスの関数からならどの値のものでも読み書きできます(@x、または T.x(other))。外からの T.x(c) は静的エラーです。
class Counter
attr_reader label
private attr_accessor n
def bump(c) = @n += 1
def same?(c, other) = n(c) == n(other)
end
c = Counter.new("hits", 0)
Counter.bump(c)
p(Counter.bump(c)) # => 2
p(Counter.same?(c, Counter.new("x", 2))) # => true
```ruby error
class Counter
attr_reader label
private attr_accessor n
end
c = Counter.new(“hits”, 0)
p(Counter.n(c)) # !> field n of Counter is private (private attr_*)
### フィールドの順序と new
フィールドは書いた順に並び、行をまたいでも同じです。これが `C.new` の引数の順です。`C.new` はアクセスの種類に関わらずフィールドを位置で取り、名前でも取れます。`Logger.new("app", level: :warn)` は最初のフィールドを位置で、`level` をキーワードで与えます。
- フィールドが複数ある型では名前を勧めます。位置の `new` はフィールド順と照合されません。
- 未知の名前と、1 つのフィールドの二重指定は静的エラーです。
- `initialize` が無ければ、全フィールドを与えます。
- `initialize` があれば、後ろのフィールドを省略できます。後のキーワードのために飛ばすこともできます。省略したフィールドは initialize が始まるとき nil で、initialize が設定します。
```ruby
class Logger
attr_reader name, level
def initialize(l)
@level = :info if @level == nil
end
end
a = Logger.new("app")
p(Logger.level(a)) # => :info
b = Logger.new("app", level: :warn)
p(Logger.level(b)) # => :warn
c = Logger.new(level: :debug, name: "db")
p(c) # => #<struct Logger name="db", level=:debug>
```ruby error
class Logger
attr_reader name, level
def initialize(l)
@level = :info if @level == nil
end
end
Logger.new(“app”, lvl: :warn) # !> Logger has no field lvl
### initialize
class の `def initialize(c)` は、`C.new` がフィールドを格納した**後**に、新しいインスタンスを受けて走ります。Ruby と同じく、最初の値(`@items = Any[]`)、検査(`@port => Integer`)、変換(`@celsius = Float(@celsius)`)を書く場所です。`@level = :info if @level == nil` は `new` が与えた値を保ちます。
- 引数はその 1 つ(新しいインスタンス)だけです。2 つ以上は静的エラーです。
- `C.initialize` を直接呼ぶのは静的エラーです。`C.new` が呼びます。
- 検査器は initialize を `C.new` の呼び出しごとに解析します。そこで `@x` はその呼び出しが与えた値を読むので、誤った引数はその呼び出しについて報告されます。フィールドは initialize が残したものを持ちます。
```ruby
class Temp
attr_reader celsius, label
def initialize(t)
@celsius = Float(@celsius)
@label = "C" if @label == nil
end
end
t = Temp.new(21)
p(Temp.celsius(t)) # => 21.0
p(Temp.label(t)) # => "C"
p(Temp.new(1, "K")) # => #<struct Temp celsius=1.0, label="K">
```ruby error
class Box
attr_reader value
def initialize(b)
@value => Integer # !> => Integer: the value is String, which does not match [type]
end
end
p(Box.new(1))
p(Box.new(“s”))
> [!WARNING]
> Ruby の `def initialize(x) = @x = x` は Sake では静的エラーです。`x` は新しいインスタンスであって値ではなく、自分を自分のフィールドに入れることになります。`C.new(...)` が既にフィールドを格納しているので、initialize は検査と変換だけを書きます。
```ruby error
class Box
attr_reader value
def initialize(x) = @value = x # !> `@value = x` stores the new Box in its own field: `x` is the instance, not a value
end
7.1.1 既定値は無い
attr_reader items = Any[] は静的エラーです。フィールドの最初の値は initialize で設定します。新しいインスタンスごとに走る唯一の場所がそこで、他のフィールドも読めます(@len = String.bytesize(@src))。検査器は initialize を通してフィールドを追うので、initialize が常に設定するフィールドはその後 nil ではありません。
```ruby error class Bag attr_reader items = Any[] # !> a field has no default value; set it in initialize, which runs after Bag.new end
```ruby
class Bag
attr_reader items
def initialize(b)
@items = Any[] if @items == nil
end
def add(b, x) = Array.push(@items, x)
end
b = Bag.new
Bag.add(b, 1)
p(Bag.items(b)) # => [1]
7.1.2 予約語と同じ名前のフィールド
予約語と同じ名前のフィールドは Symbol で宣言します: attr_accessor :next。裸では解析できません。リーダは Node.next(n)、関数の中の @next はいつもどおりです。
class Node
attr_accessor value, :next
def last_value(n)
nx = @next
nx ? last_value(nx) : @value
end
end
list = Node.new(1, Node.new(2, nil))
p(Node.next(Node.next(list))) # => nil
p(Node.last_value(list)) # => 2
7.1.3 構築ごとに 1 つの型
検査器にとって、各 C.new はそれ自身の型です。別の場所で作ったインスタンスはそれぞれのフィールド型を保つので、Integer の Heap と String の Heap は混ざりません。1 つの C.new が別の引数型で呼ばれた関数の中にあるとき(expect(42) と expect("x"))も、呼び出しごとに型が分かれます。汎用の包み(Result.map の Ok.new(yield(@value)))は呼び出し元ごとに 1 つの型です。
- メッセージは、型に複数の場所があるときだけ場所付きで名乗ります(
Heap@L7)。 - 値から値を作る関数(Value を取る関数の中の
Value.new(a + b))は、場所ごとに 1 つの型にとどまります。
class Heap
attr_reader items
def push(h, x) = Array.push(@items, x)
def top(h) = Array.first(@items)
end
ints = Heap.new(Any[])
Heap.push(ints, 3)
words = Heap.new(Any[])
Heap.push(words, "job")
p(Heap.top(ints) + 1) # => 4
p(String.upcase(Heap.top(words))) # => "JOB"
7.1.4 class B < A
class B < A は A の定義を B に書き写す略記で、継承ではありません。
- A のフィールドが先、次に B のフィールドが並びます。
- A の関数は B の関数でもあります。その中の型無しの名前と
@xは B のものです。 - A の
includeは B のものです。 - B 自身の定義が A のものに勝ちます。
- その後 A と B は無関係です。B は A ではなく、
A.f(b)は型エラーで、b in Aは偽です。 <はプログラムのどの class でも取り、その場に書いたStruct.new(...)も取ります(class P < Struct.new(:x))。module はincludeします。
class Shape
attr_reader name
def describe(s) = "a shape called " + @name
end
class Circle < Shape
attr_reader r
def area(c) = 3.0 * @r * @r
end
class Square < Shape
attr_reader side
def describe(s) = "a square called " + @name
end
c = Circle.new("c1", 2.0)
p(c) # => #<struct Circle name="c1", r=2.0>
p(Circle.describe(c)) # => "a shape called c1"
p(Circle.area(c)) # => 12.0
p(Square.describe(Square.new("s1", 3.0))) # => "a square called s1"
```ruby error class Shape attr_reader name def describe(s) = @name # !> Shape.name: argument 1 must be Shape, but is Circle [type] end class Circle < Shape attr_reader r end p(Shape.describe(Circle.new(“c”, 2.0)))
### 例外型
`class E < Exception`(または `< StandardError`)は例外型を宣言します。`message` が最初のフィールドで、その後に `attr_*` の行のフィールドが続きます。`message` より後のフィールドは常に省略でき、`E.new("msg")` はメッセージだけ与えます。`raise E, "msg"` の形は、`message` 以外のフィールドを持たない例外型に限ります([例外とエラー](08-exceptions.md))。
```ruby
class ParseError < StandardError
attr_reader line
end
begin
raise ParseError.new("bad token", 7)
rescue ParseError => e
p(ParseError.message(e)) # => "bad token"
p(ParseError.line(e)) # => 7
end
begin
raise ParseError.new("no line")
rescue ParseError => e
p(ParseError.line(e)) # => nil
end
7.1.5 略記: Struct.new と Exception.new
Struct.new(:x, :y) は attr_accessor x, y を持つ class C の略記です。Ruby の形 class C < Struct.new(:x, :y) も書け、そのフィールドが先、本体の attr_* 行と関数が後に来ます。Exception.new(:line) は attr_accessor line を持つ class C < Exception の略記です。
class Point < Struct.new(:x, :y)
attr_reader label
def to_s(pt) = @label + "(" + Integer.to_s(@x) + ", " + Integer.to_s(@y) + ")"
end
p(Point.to_s(Point.new(1, 2, "P"))) # => "P(1, 2)"
Oops = Exception.new(:line)
begin
raise Oops.new("oops", 3)
rescue Oops => e
p(Oops.line(e)) # => 3
end
7.1.6 宣言の静的エラー
次はどれも静的エラーで、検査器が hint で直し方を示します。
attr_reader :x: 予約語でないフィールドを Symbol で書いた(裸で書く)。- 最初の
class Cより後のclass Cでフィールドを宣言した。 - 既定値
attr_reader x = v。 - 読み出し専用(
attr_reader)のフィールドへの、クラスの外からの書き込み。 - 古い形
class C < {reader: [...]}。hint がattr_*の行を示します。
7.2 Struct.new の操作
Point = Struct.new(:x, :y)
名前空間 Point に次の操作が定義されます。
| 操作 | 意味 |
|---|---|
Point.new(x, y) |
作成。位置引数、フィールドごとに 1 つ |
Point.x(p), p.Point.x |
フィールド x を読む(リーダはフィールドの名前) |
Point.set_x(p, v), p.Point.x = v |
フィールド x をその場で書く。v を返す |
p.Point.x += v, \|\|=, &&= |
p.Point.x = p.Point.x + v。p は 1 度だけ評価 |
Point[p1, ...] |
Point の Array |
p == nil, p != nil |
nil との比較 |
Point = Struct.new(:x, :y)
pt = Point.new(3, 4)
p(pt) # => #<struct Point x=3, y=4>
p(Point.x(pt)) # => 3
p(Point.set_x(pt, 10)) # => 10
pt.Point.y += 1
p(pt) # => #<struct Point x=10, y=5>
pts = Point[Point.new(1, 2), Point.new(3, 4)]
p(pts) # => [#<struct Point x=1, y=2>, #<struct Point x=3, y=4>]
p(pt == nil) # => false
7.2.1 可変性
値は可変で、参照で共有されます。ある変数を通した変更は、同じ値を持つ他のすべての変数から見えます。
Point = Struct.new(:x, :y)
pt = Point.new(3, 4)
q = pt
Point.set_y(q, 0)
p(pt) # => #<struct Point x=3, y=0>
7.2.2 操作を足す
class Point ... end か def Point.f で自分の操作を足します。その中ではリーダとライタを型無しで呼べます(x(p)、set_x(p, v))。フィールドと同名の関数(def x(p) = ...)は、Ruby で attr_accessor の後にメソッドを定義したのと同じくリーダを置き換え、ライタと同名の関数(def set_x(p, v))はライタを置き換えます(p.Point.x = v と += もそれを呼びます)。@x は常にフィールドを読み書きします。
Point = Struct.new(:x, :y)
def Point.shift(pt, dx)
@x += dx
pt
end
class Point
def norm2(pt) = @x * @x + @y * @y
def x(pt) = 100
end
pt = Point.new(3, 4)
p(Point.shift(pt, 1)) # => #<struct Point x=4, y=4>
p(Point.norm2(pt)) # => 32
p(Point.x(pt)) # => 100
7.2.3 名前でフィールドを扱う
Point.field_names はフィールドの名前を Symbol で並べた Array です(Ruby の Struct.members)。Point.read_field(p, name) と Point.write_field(p, name, v) は、Symbol が名指すフィールドを @x と同じく読み書きします(Struct での Ruby の p[name] と p[name] = v)。Hash への変換や表の行からの読み込みのように、全フィールドをたどるコードを 1 度だけ書けます。
Point = Struct.new(:x, :y)
pt = Point.new(3, 4)
p(Point.field_names) # => [:x, :y]
p(Array.map(Point.field_names) { |f| Point.read_field(pt, f) }) # => [3, 4]
Point.write_field(pt, :y, 10)
p(Point.read_field(pt, :y) + 1) # => 11
- リテラルの名前はそのフィールドです。
Point.read_field(pt, :y)はyの型を持ち、:zは静的なエラーです(「Point has no field :z」)。リテラルに絞られた Symbol(case name in :x | :y)はそのフィールドだけに届きます。 - それ以外の Symbol は、全フィールドの型の和を読み、検査器から見ると値を全フィールドに書きます。フィールドごとの型を保つには、名前の
caseの中で書きます:in :id then Post.write_field(post, name, Integer(v))。 - どのフィールドも名指さない Symbol は
KeyErrorです。 - クラスの関数の外では、名前が届きうるフィールドに公開のリーダ(
read_field)かライタ(write_field)が要ります。中では private も含めた全フィールドです。基底クラスの関数(class B < A)は、A にidが無くてもread_field(b, :id)で B のフィールドを読めます。
7.2.4 フィールドの略記 @x
クラスの関数(class Point の中、または def Point.f)の中で、@x は関数の第 1 引数のフィールド x です。第 1 引数は慣習として主語です。
| 書いたもの | 意味 |
|---|---|
@x |
Point.x(p) |
@x = v |
Point.set_x(p, v) |
@x OP= v |
Point.set_x(p, Point.x(p) OP v) |
@x \|\|= v |
Point.x(p) \|\| Point.set_x(p, v) |
pは第 1 引数の今の値で、同名の引数を持つブロックの中でも変わりません。- 通常の実行時検査がかかります。第 1 引数は Point でなければなりません。
@xは次の場合に静的エラーです: クラスの関数の外、引数の無い関数、フィールドが存在しないとき。
```ruby error
Point = Struct.new(:x, :y)
def far(pt) = @x > 100 # !> @x means a field of the first argument, so it is only available in a function of a class
### 印字
`p` は クラスのインスタンスを `#<struct Point x=1, y=2>` と印字します。`puts` も同じです。
### Struct.new の制限
`Struct.new` は定数に代入しなければなりません。定数はトップレベルか、class / module の本体の直下に置きます(`module Geo` の中の `Point = Struct.new(:x, :y)` は `Geo::Point`)。Symbol だけを取り、ブロックは取りません。関数は `class Point` の中で定義します。
`Data.define` は無く、`Struct.new` を使う hint 付きで拒否されます。Ruby の `Data` は不変ですが、Sake の名前付き型は可変で、それは Ruby の `Struct` が提供するものです。
```ruby error
Point = Data.define(:x, :y) # !> Sake's named types are mutable, so they are made with Struct.new, not Data.define
7.2.5 複製: Kernel.dup
Kernel.dup(x) は浅い複製です。新しい容器と クラスのインスタンスを作り、要素は同じものを共有します。型が dup を定義していればそれが呼ばれます。
Point = Struct.new(:x, :y)
a = Point.new(1, Any[2])
b = Kernel.dup(a)
Point.set_x(b, 9)
Array.push(Point.y(b), 3)
p(a) # => #<struct Point x=1, y=[2, 3]>
p(b) # => #<struct Point x=9, y=[2, 3]>
8. 例外とエラー
Sake の例外は Ruby と同じ字面で書きます: raise で投げ、begin/rescue/else/ensure で受け、retry でやり直します。違いは 2 つです。例外型は普通のクラスで、型をまとめるのは継承ではなくモジュールであること。そして検査器が、各関数からどの例外が漏れうるかを実行前に追うことです。この章の前半が例外の書き方、後半が静的エラーと実行時エラーの種類の一覧です。エラーの表示形式は概要と実行にあります。
8.1 例外
ParseError = Exception.new(:line) # フィールド: message, line
def parse(s)
raise ParseError.new("empty", 1) if s == ""
Integer(s)
end
begin
parse("")
rescue ParseError => e
p(ParseError.line(e)) # => 1
rescue KeyError, IndexError => e
p(Exception.message(e))
rescue => e # rescue できるすべての例外
raise # 再送出
else
p(:ok) # 何も投げられなかったとき
ensure
p(:ensure) # => :ensure
end
形は Ruby のままです。以下の節で、例外型の宣言、raise と rescue の各形、検査器が見るもの、捕まえられなかったときの振る舞いを順に述べます。
8.1.1 例外型の宣言
例外型はクラスです(クラス)。最初のフィールドが message で、その後に自分で宣言したフィールドが続きます。他のクラスと同じく Name.new("msg", ...)、Name.message(e)、Name.field(e)、関数の中の @field が使えます。
class ParseError < Exception
attr_reader line
end
e = ParseError.new("empty", 1)
p(ParseError.message(e)) # => "empty"
p(ParseError.line(e)) # => 1
p(e) # => #<ParseError: empty line=1>
- 2 つの書き方。
class Name < Exceptionにattr_reader fieldの行を書く形と、その略記Name = Exception.new(:field, ...)は同じものです。 - まとめるのはモジュール。
class B < Aは書き写しで、rescue Aは B を捕まえません。例外型をまとめるには、モジュールを include させてrescue Mと書きます(下)。組み込みの型は Ruby の親子関係のとおりで、rescue IndexErrorはKeyErrorを、rescue IOErrorはEOFErrorを、rescue RangeErrorはFloatDomainErrorを、rescue ArgumentErrorはMath::DomainErrorも捕まえます。class ConvError < RangeErrorのように組み込みの例外を親に書いたクラスも、rescue RangeErrorで捕まります。 - 組み込みの例外型。 組み込みの操作が投げる型で、フィールドは
messageだけです:RuntimeError、ArgumentError、TypeError、KeyError、IndexError、ZeroDivisionError、RangeError、IOError、EOFError、RegexpError、FloatDomainError、EncodingError、ThreadError、NoMatchingPatternError、Math::DomainError。それぞれの詳細は組み込みリファレンスの Exceptionsにあります。
8.1.2 raise の形
raise は構文で、操作ではありません。4 つの形があります。
| 書いたもの | 投げるもの |
|---|---|
raise "msg" |
RuntimeError |
raise T, "msg" |
T.new("msg")。T のフィールドは message だけ |
raise value |
その例外値 |
raise(rescue 節の中で) |
今受けている例外の再送出 |
begin
raise "boom"
rescue => e
p(e) # => #<RuntimeError: boom>
end
begin
raise KeyError, "no such key"
rescue KeyError => e
p(e) # => #<KeyError: no such key>
end
begin
raise IndexError.new("out")
rescue IndexError => e
p(Exception.message(e)) # => "out"
end
message 以外のフィールドを持つ型は raise T, "msg" では投げられません。値を作って raise T.new(...) と書きます。
```ruby error
E = Exception.new(:code)
raise E, “msg” # !> E has fields besides message; raise it with raise E.new(...)
### rescue
`rescue A, B => e` は列挙した型を捕まえます。書いた名前がモジュールなら、それを include した例外型を、組み込みの親(`IndexError` など)なら Ruby の子も捕まえます。
すべてを捕まえるには `rescue => e` と書きます。`rescue StandardError => e` と `rescue Exception => e` も同じ意味です。このとき `e` の型は和なので、型ごとに処理を分けるには `case e in A ...` で絞ります([制御構造とパターン](06-control.md))。
```ruby
def check(x)
raise ArgumentError, "negative" if x < 0
x
end
begin
check(-1)
rescue ArgumentError, ZeroDivisionError => e
v = case e
in ArgumentError then "argument"
in ZeroDivisionError then "zero"
end
p(v) # => "argument"
end
8.1.3 モジュールでまとめる
Ruby のライブラリは、例外を class Error < StandardError の子にして rescue MyLib::Error でまとめて受けます。Sake では親をモジュールにして、各例外型に include させます。rescue M => e は M を include したすべての例外型(M を include したモジュールを include する型も)を捕まえ、e の型はそれらの和です。モジュールの関数 M.f(e) は、型ごとの定義に振り分けられます。
module ParseFailure
def describe(e) = "parse: " + Exception.message(e)
end
class BadToken < StandardError
include ParseFailure
end
class Unclosed < StandardError
include ParseFailure
attr_reader line
end
def parse(s)
raise BadToken, "bad token" if s == "?"
raise Unclosed.new("unclosed", 3) if s == "("
s
end
begin
parse("(")
rescue ParseFailure => e
p(ParseFailure.describe(e)) # => "parse: unclosed"
case e
in Unclosed then p(Unclosed.line(e)) # => 3
in BadToken then p("token")
end
end
モジュールは投げられません(raise ParseFailure, "x" は静的エラー)。Ruby で親クラス自身も投げているなら、具体的な型を 1 つ足して include させます。レベル 1 は、前の rescue M がもう受けている型の rescue A で止めます。begin の本体がモジュールのどの型も投げない rescue M は unused-rescue の警告です。
```ruby error module ParseFailure end class BadToken < StandardError include ParseFailure end def parse(s) raise BadToken, “bad token” if s == “?” s end begin parse(“?”) rescue ParseFailure => e p(1) rescue BadToken => e # !> rescue BadToken: an earlier rescue (ParseFailure) already catches it [rescue] p(2) end
### メッセージを読む
`Exception.message(e)` は、どの型の例外値からもメッセージを読みます。`e` の型が和のときに使います。型が決まっているなら `ParseError.message(e)` でも同じです。
組み込みの操作が投げた例外のメッセージは Ruby の文です。rescue されずにプログラムを終えたときの報告には、操作名も付きます(`ZeroDivisionError: Arithmetic./: divided by 0`)。
```ruby
begin
p(1 / 0)
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
8.1.4 バックトレースと原因
Exception.backtrace(e) は、例外が投げられたときの呼び出しの列を、Ruby と同じ "FILE:LINE:in 'FUNCTION'" の String の Array で返します。内側が先です。まだ投げていない例外値(E.new で作っただけのもの)では nil です。raise e や引数のない raise で投げ直しても、最初に投げたときの列のままです。
Exception.cause(e) は、e が投げられたときに rescue 節で扱っていた例外を返します。無ければ nil です。どちらも nil になりうるので、--strict では使う前に確かめます。
Ruby と違い、組み込みの操作の中の行(Ruby の 'Integer#/' など)は列に入りません。
def parse(s)
Integer(s)
rescue ArgumentError
raise "not a number: #{s}"
end
begin
parse("x")
rescue => e
bt = Exception.backtrace(e)
p(Array.map(bt) { |l| String.sub(l, /\A.*?:/, "") }) if bt # => ["4:in 'parse'", "8:in '<main>'"]
c = Exception.cause(e)
p(c) if c # => #<ArgumentError: invalid value for Integer(): "x">
end
p(Exception.backtrace(RuntimeError.new("not raised"))) # => nil
8.1.5 プログラムの誤り
SystemStackError、LocalJumpError、NotImplementedError はプログラムの誤りで、rescue できません。rescue に書くのは静的エラーです。
```ruby error begin p(1) rescue SystemStackError => e # !> SystemStackError cannot be rescued: it is a program error, which the checks before running report p(e) end
`TypeError`(誤った型の値を受けた操作)と `NoMatchingPatternError` は Ruby と同じく rescue できます。ただし、それらが報告する型の誤りの多くは、実行前の検査が止めます([概要と実行](01-overview.md))。次の例は既定のレベルでは `type` の問題として実行前に止まります。`--strict=0` で走らせると `len(1)` は `TypeError` を投げ、他の例外と同じく rescue できて、メッセージは `"argument 1 must be String, got Integer"` です。
```ruby error
def len(x)
String.length(x) # !> String.length: argument 1 must be String, but is Integer [type]
end
begin
len(1)
rescue TypeError => e
p(Exception.message(e))
end
8.1.6 他の形
def f ... rescue ... end、修飾子の expr rescue fallback、retry は Ruby と同じです。ensure は begin ブロックを離れるときに 1 度走ります。return や exit で離れるときも走ります。
def safe_div(a, b)
a / b
rescue ZeroDivisionError
0
end
p(safe_div(6, 0)) # => 0
n = Integer("x") rescue -1
p(n) # => -1
tries = 0
begin
tries += 1
raise "again" if tries < 3
rescue RuntimeError
retry
end
p(tries) # => 3
8.1.7 例外の流れの検査
検査器は、明示的に raise した型のうちどれが各関数から出うるかを追います。2 つの項目がこれを使います(レベルは概要と実行)。
unused-rescue(レベル 1〜4 で警告。レベル 0 は型推論を走らせない)。 begin 本体が決して投げない型の rescue 節を報告します。ZeroDivisionErrorのような組み込みの種類は普通の操作から来うるので、常にありうるとみなし、報告しません。プログラムは走ります。防御的なrescueは普通に書くもので、投げるものがあるかはプログラム全体で決まるからです(一部のアプリしか投げない例外を受けるライブラリのrescue)。--strict=2,unused-rescueでエラーになります。関連するrescue項目(レベル 1)は、前の節が既に捕まえる節で止めます。unrescued(レベル 4)。 rescue されずにトップレベルまで届きうるraiseを報告します。raise: RuntimeError may reach the top level without being rescued [unrescued]のような報告です。
ParseError = Exception.new(:line)
begin
p(Integer("1")) # => 1
rescue ParseError => e # warning: the begin body never raises ParseError [unused-rescue]
p(e)
end
8.1.8 捕まえられなかった例外
rescue されなかった例外は、実行時エラーと同じ形でプログラムを終えます: FILE:LINE: in FUNCTION: ParseError: message。終了コードは 1 です。
```ruby error def check(x) raise ArgumentError, “x must be positive” if x <= 0 # !> ArgumentError: x must be positive x end check(0)
### デッドロック
すべてのスレッドが待ちになると、プログラムは Sake の `ThreadError` で終わります。何も push されない `Queue.pop`、自分の中で再び取る `Mutex.synchronize`、互いを待つ `Thread.join` がその例です([組み込み操作](09-builtins.md))。
```ruby error
q = Queue.new
Queue.pop(q) # !> ThreadError: deadlock: every thread is waiting
8.2 静的エラーの種類
静的エラーは位置順にまとめて報告され、何も実行されません。終了コードは 2 です。形式は概要と実行にあります。
| 種類 | 例 |
|---|---|
| 構文エラー | Prism が解析できないもの |
| 未定義の型、操作、関数 | String.upcse(s) |
| 引数の数の誤り | |
| ブロックの過不足 | 取らないところへのブロック、必要なところでの欠落 |
| 値へのメソッド呼び出し | name.upcase |
| 禁止構文 | 下記 |
| 未対応の構文 | 付録 Ruby との違いと未対応のもの |
| 二重定義 | |
T[...] のリテラルの型の不一致 |
Integer["a"] |
--strict で選んだ項目 |
既定では、型が合わない値 |
禁止構文は、send、public_send、__send__、method_missing、define_method、eval の仲間、instance_variable_get/set、const_get/set、binding、self、そしてクラスの関数の外の @x です。どれも呼び出し先を実行時まで決められなくするか、Sake に無い概念(インスタンスの状態)を指すものです。
8.3 実行時エラーの種類
実行時エラーは、その操作の行でプログラムを止めます。終了コードは 1 です。どれも例外なので rescue できます。例外は SystemStackError と、すべてのスレッドが待ちになって検出されるデッドロックです(Mutex.synchronize の再入の ThreadError は rescue できます)。
| 種類 | 投げるもの |
|---|---|
TypeError |
誤った型の値を受けた操作(下記) |
IndexError |
範囲の外の添字(下記) |
ArgumentError |
ブロックの引数の数。負のサイズ。Integer ** 負。sort で比べられない値 |
ZeroDivisionError |
Integer の / か % のゼロ除算 |
KeyError |
無いキーの Hash.fetch。無いフィールドを名乗る Record パターン |
RangeError |
有限の Range が要る操作に終端の無い Range |
RegexpError |
不正なパターンの Regexp.new |
IOError |
ファイル・ディレクトリ・ソケットの失敗(下記) |
EOFError |
組み込みは投げません(下記) |
EncodingError |
エンコーディングの合わない String の操作 |
FloatDomainError |
NaN や Infinity の Integer への変換 |
Math::DomainError |
Math.sqrt(-1) など |
SystemStackError |
10,000 より深い再帰 |
ThreadError |
デッドロック |
TypeError。 誤った型の値を受けた操作。左のオペランドの型が支えない演算子。型付き Array への別の型の書き込み。Tuple でも Array でもない値からの多重代入。IndexError。 Tuple の外の添字。Array の外のArray.fetch。T の Array の末尾の先への書き込み(xs[5] = 3。隙間が nil になるため)。IOError。File.read、Dir.mkdirなどの失敗。Ruby ではErrno::ENOENTなどで、メッセージは Ruby のものです。ソケットのエラー(拒否、リセット、未知のホスト)もIOErrorです。EOFError。 組み込みの操作は投げません。終端の読み出しは nil や""を返し、Ruby の EOFError はIOErrorに畳みます。型としては存在するのでrescueに書けます。
ruby error
h = Hash.of("a" => 1)
p(Hash.fetch(h, "b")) # !> KeyError: Hash.fetch: key not found: "b"
9. 組み込み操作
組み込みの操作は約 550 あり、名前は Ruby のコアライブラリと同じです。この章は名前空間ごとの案内です: その型が何のためにあるか、最初に手が伸びる 2〜3 の操作、短い例、そして詳細のある章へのリンクを置きます。各操作の正確な署名、引数、nil になる場合、投げる例外、例は、第 2 部「組み込みリファレンス」に型ごとに 1 章ずつあります(Array、String、Hash、Kernel、…)。
各節の表は「何をしたいか」で操作をまとめたもので、「結果」の列は結果の型です。「ブロック」と記したものはブロックが必須です。[x] は省略できる引数です。演算子(+、<、[])の規則は演算子と添字に、各型の値の性質は値と型にあります。
9.1 Kernel
Kernel は、プログラムのどこからでも名前だけで呼べる操作の集まりです。puts(x)、Integer(s)、loop { } のように書き、Kernel.puts(x) と書く必要はありません。最初に使うのは出力の p と puts、文字列から数への変換 Integer(s)、繰り返しの loop です。詳細は Kernel。
puts("total:", 3)
# => total:
# => 3
p(format("%05.1f", 3.14159)) # => "003.1"
p(Integer("42") + 1) # => 43
x = loop { break 7 }
p(x) # => 7
p(Math.PI > 3) # => true
| 操作 | 結果 | 備考 |
|---|---|---|
puts(*xs) |
nil | Ruby の puts と同じ。Array と Tuple は 1 要素 1 行 |
print(*xs) |
nil | 改行なし |
p(x), p(x, y), pp(x) |
x · [x, y] · x |
各引数を Ruby の inspect の形で 1 行ずつ。p() は nil |
format(fmt, *xs), sprintf |
String | Ruby の format。引数は Integer, Float, String, Symbol, nil, true, false |
Integer(x), Float(x), Rational(a, [b]), Complex(re, [im]) |
数 | Ruby の厳密な変換。不正な入力は ArgumentError |
rand, rand(n), srand(seed) |
Float、または n 未満の Integer/Float | |
sleep(secs) |
Integer | |
loop { } |
break の値 |
break までブロックを繰り返す |
dup(x) |
x の複製 | Ruby の obj.dup。dup を定義した型はそれを使う |
gets |
String か nil | 標準入力の 1 行 |
exit(status) |
Integer、または true で 0 / false で 1(既定 0)。ensure は走る。その後のコードには到達しない |
|
warn(x, ...) |
nil | エラー出力へ(puts と同じ形) |
system(cmd) |
true / false / nil | Ruby の system |
at_exit { } |
nil | プログラムの終わりに走るブロック |
ARGV |
Array of String | プログラムの引数。Ruby の定数のように書くが操作(Kernel.ARGV)。毎回同じ Array なので Array.shift(ARGV) が効く |
Kernel.PROGRAM_NAME |
String | 走っているファイルの名前。ARGV と違い、裸では書けない(大文字の名前は型) |
__FILE__、__LINE__、__dir__ |
String、Integer、String | このファイル(主ファイルは与えた名前、require したファイルはフルパス)、この行、このファイルのディレクトリのフルパス。Ruby と同じ: File.join(__dir__, "views") |
equal?(a, b) |
true/false | 同一性 |
block_given? |
true/false | 今の関数がブロックを受けたか |
- 定数は操作。
Math::PI、Math::E、Float::INFINITY、Float::NAN、Float::EPSILON、Float::MAX、Float::MINは、ARGVと同じく操作として読みます(Math.PI)。Sake に値の定数は無く、組み込みの定数もこれだけです(A::Bのような入れ子の名前は名前空間で、値ではありません)。
9.2 Integer / Float
Integer は上限の無い整数、Float は倍精度の浮動小数点数です。算術と比較は演算子で書き、Integer.to_s(n, 16) のような変換と、Integer.times(n) { } のような繰り返しを型を付けて呼びます。最初に使うのは / と %(Integer どうしは整数除算)、Float.round、Integer.times です。詳細は Integer、Float、Arithmetic。
p(7 / 2) # => 3
p(7.0 / 2) # => 3.5
p(Integer.divmod(7, 2)) # => [3, 1]
p(Integer.to_s(255, 16)) # => "ff"
p(Float.round(3.14159, 2)) # => 3.14
Integer.times(3) { |i| print(i) }
puts # => 012
| 操作 | 結果 |
|---|---|
+ - * / % ** |
Integer(Float なら Float) |
< <= > >= == != |
true/false |
Integer.to_s(n, base = 10), to_f, abs, succ, pred |
String, Float, Integer, Integer, Integer |
even?, odd?, zero? |
true/false |
times(n) { \|i\| } · upto(a, b) { } · downto(a, b) { } · step |
n · a · a(ブロック) |
& \| ^ << >> |
Integer |
divmod(a, b) |
[商, 余り] の Tuple(Float は [Integer, Float]) |
gcd, lcm, pow(a, b, [mod]), bit_length, sqrt(n), clamp(n, lo, hi) |
Integer |
digits · chr · between?(n, lo, hi) |
Array of Integer · String · true/false |
Float.to_i, floor, ceil, truncate · round(f) / round(f, digits) |
Integer · Integer / Float |
nan? · finite? · infinite? |
true/false · true/false · 1, -1, nil |
Float.to_r, rationalize |
Rational |
- どの実数も取る丸め。
Arithmetic.round(x)、floor、ceil、truncate(省略可能な桁数付き)、abs、to_f、to_i、zero?は、Ruby のx.roundと同じくどの実数(Integer, Float, Rational)も取り、結果の型は x に従います(桁なしの round は Integer)。Float.roundは Float だけを名乗ります。
9.3 String
String はエンコーディングを持つ文字の並びで、可変です。名前が ! で終わる操作は主語をその場で変え、他は新しい String を返します。最初に使うのは String.split、String.sub/gsub、String.include? と、連結の + です。詳細は String。
s = "Hello, World"
p(String.length(s)) # => 12
p(String.upcase(s)) # => "HELLO, WORLD"
p(String.split(s, ", ")) # => ["Hello", "World"]
p(String.sub(s, "World", "Sake")) # => "Hello, Sake"
p(String.include?(s, "World")) # => true
p(String.index(s, "x")) # => nil
p(s + "!") # => "Hello, World!"
| 目的 | 操作 | 結果 |
|---|---|---|
| 連結・繰り返し・書式 | +(a, b), *(s, n), % |
String |
| 比較 | == != < <= > >= |
true/false |
| 長さと数 | length, size, count(s, chars), to_i(s, base = 10) · to_f |
Integer · Float |
| 変形 | upcase, downcase, capitalize, swapcase, reverse, strip, lstrip, rstrip, chomp, chop, to_s |
String |
| 置換 | sub(s, pat, repl), gsub(...) |
String。sub!, gsub! はその場で |
| 詰め物と文字の置き換え | ljust(s, n, [pad]), rjust, center · tr(s, a, b), delete(s, chars), squeeze(s, [chars]), succ, next |
String |
| 問い合わせ | empty?, include?(s, t), start_with?, end_with?, match?(s, re), casecmp? |
true/false |
| 分割 | chars, lines, bytes, split(s, [sep, [limit]]), scan(s, re) |
Array |
| 位置 | index(s, t, [start]), rindex, byteindex |
Integer か nil |
| 正規表現の一致 | match(s, re, [pos]) |
MatchData か nil |
| 他の型へ | ord · hex · oct · to_sym, intern |
Integer · Symbol |
| バイト列と変換 | byteslice(s, i, [n]), b, force_encoding(s, enc), encode, encoding, valid_encoding?, unpack(s, fmt), unpack1 |
Ruby と同じ |
| 繰り返し | each_line(s) { }, each_char(s) { }, each_byte |
s(ブロック) |
- 置換の repl。
sub、gsubの repl は、String(\1可)、Hash(一致 => 置換)、またはブロック{ |m| ... }です。
9.4 Array
Array は長さが変わる並びです。Any[1, 2] で作ります(リテラル [1, 2] は Tuple です)。最初に使うのは Array.push、Array.each、Array.map と Array.select です。詳細は Array。
xs = Any[3, 1, 2]
Array.push(xs, 5)
p(xs) # => [3, 1, 2, 5]
p(Array.map(xs) { |x| x * 2 }) # => [6, 2, 4, 10]
p(Array.select(xs) { |x| x > 1 }) # => [3, 2, 5]
p(Array.sort(xs)) # => [1, 2, 3, 5]
p(Array.reduce(xs, 0) { |acc, x| acc + x }) # => 11
p(Array.first(xs)) # => 3
p(Array.join(xs, "-")) # => "3-1-2-5"
p(Array.tally(Any["a", "b", "a"])) # => {"a" => 2, "b" => 1}
| 目的 | 操作 | 結果 |
|---|---|---|
| 作る | Any[...] · Array.new(n), Array.new(n, v), Array.new(n) { \|i\| } |
新しい Array |
| 長さと問い合わせ | length, size · empty?, include?(a, x) |
Integer · true/false |
| 足す・消す(その場で) | push(a, *xs), append, unshift, concat(a, b), insert(a, i, *xs), delete_if, clear |
a |
| 1 要素を取る | first, last, min, max, pop, shift, at(a, i), sample, delete(a, x), delete_at(a, i) |
要素、または nil |
| 部分を取る | first(a, n), last(a, n), shift(a, n), take, drop, slice(a, i, n) |
新しい Array |
| 無ければ失敗 | fetch(a, i, [default]) |
要素、既定値、または IndexError |
| 文字列に | join(a, [sep]) |
String |
| 合計 | sum(a, [init]) |
和(下記) |
| 並べ替えなど | reverse, sort, sort_by, uniq, compact, flatten, rotate, shuffle, dup |
新しい Array |
| 繰り返し | each, each_with_index, each_slice(a, n), each_cons(a, n), cycle |
a(ブロック) |
| 写像と選別 | map, flat_map, select, filter, reject, partition, group_by, tally, to_h, zip, product |
新しい Array / Hash / Tuple |
| 判定と数え上げ | any?, all?, none?, count |
true/false · Integer(ブロックは省略可) |
| 畳み込み | reduce(a, init) { }, inject, each_with_object(a, memo) { } |
累積値 |
| 探す | find, detect, min_by, max_by, index(a, x), find_index, assoc, rassoc |
要素か nil · Integer か nil |
| 書き換え(その場で) | map!, select!, reject!, sort!, uniq!, compact!, flatten!, reverse!, slice!, fill, replace |
a |
| バイト列に | pack(a, fmt) |
String |
sum。 要素は数、またはArithmeticを include する型です。空ならinit(既定 0)です。- ブロック無しの繰り返し。
each_sliceなどをブロック無しで呼ぶと Array を返します。 - その場の書き換え。
map!などは要素型を変えない写像に使います。
9.5 Enum
Enum は Ruby の Enumerable を短い名前にした module で、プログラムを読む前に読まれる prelude(sakelib/prelude.sake)が定義します。Enum.map(x) { } などは第 1 引数の型でディスパッチし、Array・Hash・Set・Range は自分の操作で答えます(Enum.map(xs) は xs が Array なら Array.map(xs))。自分のクラスは include Enum と def each(c) = ... yield(v) ... を書けば、残りの関数(map、select、reject、find、reduce、sum、count、include?、any?/all?/none?/one?、min_by/max_by、sort_by、to_a、first、take/drop、group_by、partition、each_with_object、compact、filter_map、flat_map)が each の上に書かれているので全部使えます。詳細は Enum。
p(Enum.map(Any[3, 1, 2]) { |v| v * 10 }) # => [30, 10, 20]
p(Enum.map(Hash.of(a: 1, b: 2)) { |k, v| "#{k}=#{v}" }) # => ["a=1", "b=2"]
p(Enum.count(1..4) { |v| v > 2 }) # => 2
class Deck
include Enum
attr_reader cards
def each(d) = Array.each(@cards) { |c| yield(c) }
end
d = Deck.new(Any["A", "K", "Q"])
p(Deck.select(d) { |c| c < "K" }) # => ["A"]
p(Enum.first(d), Deck.count(d)) # => "A"
# => 3
Tuple は Enum を include しません。長さと位置ごとの型が決まっているので、添字と分解で扱います。
9.6 Tuple, Range
Tuple は長さと各位置の型が決まった並びで、リテラル [1, "a"] が作ります。Range は 2 つの端の組で、1..5、1...5、終端の無い 1.. があります。Tuple の操作は少なく、Tuple.to_a で Array にして扱うのが普通です。Range は Range.each で数を数え、Range.to_a と Range.include? をよく使います。詳細は Tuple、Range。
t = [1, "a"]
p(Tuple.size(t)) # => 2
p(Tuple.to_a(t)) # => [1, "a"]
p(Tuple.max([3, 7, 5])) # => 7
p(Range.to_a(1..5)) # => [1, 2, 3, 4, 5]
p(Range.sum(1..5)) # => 15
p(Range.include?(1...5, 5)) # => false
p(Range.map(1..3) { |i| i * i }) # => [1, 4, 9]
p(Range.first(1.., 3)) # => [1, 2, 3]
| 操作 | 結果 |
|---|---|
Tuple.size, length · to_a · max, min · minmax |
Integer · Array · 要素(nil にならない) · [min, max] |
Range.each, each_with_index, step(r, n) |
r(ブロック) |
to_a, map, select, filter, reject, zip |
Array |
reduce, inject · sum · size · count |
累積値 · Integer |
any?, all?, none? · find, detect · include?, cover?, member?, exclude_end? |
true/false · 要素か nil · true/false |
first, last, min, max, begin, end |
要素か nil(first(r, n)、last(r, n) は Array) |
9.7 Hash, Set
Hash はキーから値への対応で、Hash.of("a" => 1) か Hash.new(default) で作ります({a: 1} は Record です)。Set は重複の無い要素の集まりで、Set.of(1, 2) で作ります。最初に使うのは添字 h[k] と h[k] = v、Hash.each、Hash.fetch、そして Set.add と Set.include? です。詳細は Hash、Set。
h = Hash.of("a" => 1)
h["b"] = 2
p(h) # => {"a" => 1, "b" => 2}
p(h["c"]) # => nil
p(Hash.fetch(h, "a")) # => 1
p(Hash.map(h) { |k, v| [k, v * 10] }) # => [["a", 10], ["b", 20]]
counts = Hash.new(0)
counts["x"] += 1
p(counts) # => {"x" => 1}
s = Set.of(1, 2)
Set.add(s, 2)
p(s) # => Set[1, 2]
p(Set.include?(s, 2)) # => true
p(Set.union(s, Set.of(9))) # => Set[1, 2, 9]
| 目的 | 操作 | 結果 |
|---|---|---|
| 作る | Hash.of(k => v, ...) · Hash.new([default]) · Set.of(...) |
新しい Hash · Set |
| 長さと問い合わせ | length, size, empty?, key?, has_key?, include?, member?, value? |
Integer · true/false |
| 1 つを取る | fetch(h, k, [default]) · dig(h, k) · delete(h, k) · key(h, v) |
値 · 値か nil · 消した値か nil · キーか nil |
| 書き換え(その場で) | store(h, k, v), merge!, update, clear, transform_values!, transform_keys!, select!, reject!, compact! |
h |
| 新しい Hash | merge(a, b), invert, select, filter, reject, transform_values, transform_keys, compact, slice, except |
新しい Hash |
| Array に | keys · values · to_a · map · sort_by · group_by · partition |
Array |
| 繰り返し | each, each_pair, each_key, each_value |
h(ブロック) |
| 判定と探索 | any?, all?, none?, count, sum, find, detect, min_by, max_by |
ブロック付き |
| Set の書き換え(その場で) | Set.add(s, x), add?, delete, merge, subtract, clear |
s |
| Set から Array へ | to_a, each, map, select, filter, reject |
Array |
| 集合演算 | union, intersection, difference, \|, &, - · subset?, superset?, disjoint?, intersect? |
新しい Set · true/false |
9.8 Regexp, MatchData, Symbol, Time, Math
Regexp は正規表現で、リテラル /\d+/ が作ります。一致の結果が MatchData で、一致しなければ nil です。Symbol は名前そのものの値(:name)、Time は 1 つの時刻、Math は初等関数の集まりです。最初に使うのは Regexp.match と m[1]、String.scan、Time.now、Math.sqrt です。詳細は Regexp、MatchData、Symbol、Time、Math。
m = Regexp.match(/(\d+)-(\d+)/, "tel 03-1234")
if m
p(m[1]) # => "03"
p(MatchData.captures(m)) # => ["03", "1234"]
end
p(String.scan("a1 b22", /\d+/)) # => ["1", "22"]
p(Symbol.to_s(:name)) # => "name"
t = Time.at(0, in: "UTC")
p(Time.year(t)) # => 1970
p(Time.strftime(t, "%Y-%m-%d")) # => "1970-01-01"
p(Math.sqrt(16)) # => 4.0
| 操作 | 結果 |
|---|---|
Regexp.new(s, flags = "") · escape(s) · source(r) · match(r, s, [pos]) · match? |
Regexp · String · String · MatchData か nil · true/false(flags は imx の文字) |
MatchData.captures · named_captures · names · to_a · to_s, pre_match, post_match · begin(m, i), end(m, i) |
Array · Hash · Array · Array · String · Integer |
Symbol.to_s · length, size |
String · Integer |
Time.now · at(seconds, [in:]) · new(y, [m, d, h, min, s, zone]) · utc(t), getutc · getlocal(t, [zone]), localtime |
Time |
year, month, day, hour, min, sec, wday, yday, to_i, utc_offset · to_f · to_s, strftime(t, fmt) · zone · utc? |
Integer · Float · String · String か nil · true/false |
Math.sqrt, cbrt, sin, cos, tan, atan, exp, log, log2, log10, atan2(y, x), hypot(x, y) |
Float |
Symbol.to_procは無い。 ブロックは値ではないので、&:nameは書けません(関数とブロック)。
9.9 Rational, Complex
Rational は常に既約の有理数(1/3r)、Complex は複素数(1 + 2i)です。どちらも算術演算子がそのまま使え、Rational(1, 3) と Complex(1, 2) は Kernel の操作です。詳細は Rational、Complex。
r = Rational(1, 3)
p(r + 1/6r) # => (1/2)
p(Rational.numerator(r)) # => 1
p(Rational.to_f(r)) # => 0.3333333333333333
c = Complex(1, 2)
p(c * c) # => (-3+4i)
p(Complex.abs(Complex(3, 4))) # => 5.0
p(Integer.fdiv(7, 2)) # => 3.5
| 操作 | 結果 |
|---|---|
Rational(a, [b]) · Integer.to_r · Float.to_r · Float.rationalize |
Rational |
Rational.numerator, denominator, to_i, floor, ceil, round, truncate · to_f · abs |
Integer · Float · Rational |
Complex(re, [im]) · Complex.real, imaginary · abs, arg · conjugate · rectangular · polar |
Complex · 実数 · Float · Complex · [re, im] · [abs, arg] |
Integer.fdiv(a, b) |
Float |
9.10 File, Dir, IO, ENV, Process
File と Dir はパス(String)でファイルとディレクトリを扱う操作の集まりで、値はありません。IO は開いたファイルと標準ストリーム(IO.stdin、IO.stdout、IO.stderr)の型です。ENV は環境変数、Process はプロセスの情報と時計、Open3 は子プロセス、Zlib は圧縮です。最初に使うのは File.read、File.write、File.open(path) { |io| }、Dir.glob です。詳細は File、Dir、IO、ENV、Process、Open3、Zlib。
dir = Dir.mktmpdir
path = File.join(dir, "notes.txt")
p(File.write(path, "one\ntwo\n")) # => 8
p(File.readlines(path)) # => ["one\n", "two\n"]
File.open(path, "a") { |io| IO.puts(io, "three") }
p(Array.length(File.readlines(path))) # => 3
p(Dir.children(dir)) # => ["notes.txt"]
p(ENV.fetch("NO_SUCH_VAR", "default")) # => "default"
out, status = Open3.capture2("echo", "hi")
p(out) # => "hi\n"
File.delete(path)
Dir.rmdir(dir)
| 操作 | 結果 |
|---|---|
File.read(path) · readlines · write(path, s) · exist?, file?, directory?, symlink?, zero?, empty?, readable?, writable?, executable?, absolute_path? |
String · Array · Integer · true/false |
File.size, mtime, atime, ftype · rename, symlink, link, readlink, unlink, delete, chmod, utime |
Integer · Time · String · … |
File.basename(p, [suffix]), dirname(p, [levels]), extname, join(*parts), expand_path(p, [dir]), absolute_path, realpath · split(p) |
String · [dir, base] |
File.open(path, mode = "r", [perm]) · File.open(...) { \|io\| } |
IO · ブロックの値(閉じる) |
Dir.children, entries · glob(pat, [base:]) · each_child(path) { } · exist?, empty? · mkdir(path, [mode]), rmdir · pwd, home, tmpdir · mktmpdir([prefix, [dir]]) |
Array of String · … |
IO.puts(io, ...), print, write(io, s) · gets(io), read(io, [n]), readlines, getc · each_line(io) { } · eof?, closed?, tty? · flush, close · seek(io, off, [whence]), pos, rewind, truncate, size |
nil · String か nil · … |
ENV.fetch(name, [default]), get(name), set(name, v), key?, delete, to_h, keys |
String · … |
Process.clock_gettime(Process.CLOCK_MONOTONIC), pid |
Float · Integer |
Open3.capture2(cmd, ...), capture2e, capture3 |
[out, status] などの Tuple |
Zlib.inflate, deflate, gzip, gunzip |
String |
- 閉じた IO。 閉じた IO、またはその向きに開いていない IO の読み書きは
IOErrorです。 - stderr の順序。
IO.stderrへの書き込みは stdout を先に flush するので、行の順序が保たれます。
9.11 スレッドとソケット
Thread はブロックを並行に走らせ、Mutex が排他、Queue がスレッド間の受け渡しを担います。TCPServer と Socket は TCP の待ち受けと接続です。最初に使うのは Thread.new { } と Thread.value、Mutex.synchronize、Queue.push/pop です。詳細は Thread、Mutex、Queue、TCPServer、Socket。
count = 0
m = Mutex.new
ts = Array.map(Any[1, 2, 3]) { |w| Thread.new { Mutex.synchronize(m) { count += w }; w * 10 } }
p(Array.map(ts) { |t| Thread.value(t) }) # => [10, 20, 30]
p(count) # => 6
q = Queue.new
producer = Thread.new { Range.each(1..3) { |i| Queue.push(q, i) }; Queue.close(q) }
got = Any[]
loop do
x = Queue.pop(q)
break if x == nil
Array.push(got, x)
end
Thread.join(producer)
p(got) # => [1, 2, 3]
| 操作 | 結果 |
|---|---|
Thread.new { ... } · Thread.value(t) · join(t, [limit]) · alive?(t) · current · kill(t) · raise(t, msg) |
Thread · ブロックの値(待つ。スレッドのエラーはここで上がる)· t か nil · true/false |
Mutex.new · synchronize(m) { } · lock, unlock, try_lock, locked?, owned? |
Mutex · ブロックの値 |
Queue.new · push(q, x) · pop(q, [timeout]) · close, size, empty?, closed? |
Queue · q · 最古の要素(待つ。閉じて空なら nil) |
TCPServer.new(host, port) · accept(s) · port(s) · close(s) |
TCPServer(port 0 は空きを選ぶ)· Socket(待つ)· Integer · nil |
Socket.connect(host, port, [timeout]) · connect_ssl(host, port, [timeout]) · set_timeout(s, secs) |
Socket(connect_ssl は TLS、相手を検証) |
Socket.gets(s) · read(s, n) · write(s, str) · close_write(s) · close(s) |
String か nil · String か nil · Integer · nil · nil |
- スレッドの変数。 ブロックは周りの変数を共有します。スレッドでは、
Thread.newを囲むブロックの変数(引数とローカル)とスレッドブロック自身の変数は開始時に複製され、関数の変数は共有のままです。上の例のArray.map(xs) { |w| Thread.new { ... w ... } }は各スレッドに自分のwを与え、共有カウンタcountはMutex.synchronizeの中で更新する関数の変数です。 - スレッドブロックを抜ける。
Thread.newのブロックからのbreakとreturn(その中のブロックのreturnも)は静的エラーです。ブロックは呼び出しが戻った後で走るからです。next vはvで終えます。 - エラー。 スレッドの中のエラーは
Thread.valueかThread.joinで上がります。join されないスレッドは失敗しても黙って終わり、メインが終わればすべてのスレッドが止まります。 - 検査。 型推論はスレッドのブロックを
Thread.newで 1 度走らせます。その値がThread.valueの型、Queue の要素型は push されたものの和です(Queue.popは nil を加える)。交互実行は解析せず、各操作が実行時に引数を検査します。
9.12 型付き Array
Integer[...]、Float[...]、Rational[...]、Complex[...]、String[...]、Symbol[...]、Tuple[...]、および各クラス D の D[...] は、要素型がその型の Array を作ります(値と型)。書き込みは要素型と照合され、合わない値は静的には type の問題、実行時には TypeError です。
xs = Integer[1, 2]
Array.push(xs, 3)
p(xs) # => [1, 2, 3]
Point = Struct.new(:x, :y)
pts = Point[Point.new(0, 0)]
p(Array.length(pts)) # => 1
p(String[]) # => []
ruby error
xs = Integer[1, 2]
Array.push(xs, "a") # !> Array.push: an element must be Integer, but is String [type]
10. ライブラリ(sakelib)
処理系には、Ruby の標準添付ライブラリと主な gem を Sake で書き直したライブラリ群 sakelib/ が付属します。この章では、ライブラリの読み込み方、何が移植されているか、テスト枠組み minitest、そして各移植がどう検査されているかを述べます。個々のライブラリの API は sakelib/notes/NAME.md にあり、この章はその一覧です。
10.1 読み込みと名前
ライブラリは require "json" のように読みます。ライブラリの名前も、その中の操作の名前も Ruby のものに従うので、Ruby で知っている名前がそのまま使えます。違うのは呼び出しの形だけです。
- 探す場所。
require "name"は、まず require するファイルと同じディレクトリのname.sakeを探します。無ければ処理系に付属するsakelib/name.sakeを読みます。隣にあるname.sakeが require している自分自身か、ライブラリでないプログラムのときも sakelib のほうを読みます。どのディレクトリから実行してもrequire "json"は同じsakelib/json.sakeです。 /を含む名前。 Ruby のnet/http、open-uriはnet_http、open_uriと書きます。- モジュール関数は Ruby のまま。
JSON.parse(s)、Base64.encode64(s)は Ruby と同じ字面です。 - インスタンスメソッドは主語が先。 Ruby の
ss.scan(/\w+/)はStringScanner.scan(ss, /\w+/)です(プログラムの構造と名前解決)。 - 入れ子の名前は Ruby のまま。 名前空間は入れ子にできるので(プログラムの構造と名前解決)、
Net::HTTP、Concurrent::Future、JSON::ParserError、Benchmark::Tmsは Ruby と同じ字面です:Net::HTTP.get(http, path)、rescue JSON::ParserError。外側が組み込み型の名前だけは作れません(Enumerableが無いので、ActiveSupport のEnumerable::SoleItemExpectedErrorはトップレベルのSoleItemExpectedErrorです)。対応表は各ライブラリの notes にあります。
require "base64"
require "strscan"
e = Base64.encode64("hello")
p(e) # => "aGVsbG8=\n"
p(Base64.decode64(e)) # => "hello"
ss = StringScanner.new("foo bar")
p(StringScanner.scan(ss, /\w+/)) # => "foo"
p(StringScanner.scan(ss, /\w+/)) # => nil
p(ss.StringScanner.scan(/\s+/)) # => " "
各ライブラリの注記は sakelib/notes/NAME.md にあります。Ruby との違いとその理由、無いもの、そして Ruby の API と Sake の API の対応表です。移植しないライブラリとその理由は sakelib/notes/not-ported.md にあります。
10.2 標準添付ライブラリの移植
Ruby の標準添付ライブラリのうち、Sake で書けるものを移植しています。移植できないものの大半は、method_missing・define_method・特殊変数・第一級のブロックに依るもので、Sake がわざと持たない機能です。TLS・UDP・端末のように OS や C 実装が要るものは、ライブラリではなく処理系に組み込みます。
require "json"
doc = JSON.parse("{\"name\": \"ann\", \"tags\": [\"a\", \"b\"]}")
p(doc) # => {"name" => "ann", "tags" => ["a", "b"]}
if doc in Hash
p(doc["name"]) # => "ann"
puts(JSON.generate(doc)) # => {"name":"ann","tags":["a","b"]}
end
JSON.parse は Hash・Array・String・Integer・Float・true/false・nil のどれかを返すので、添字で読む前に if doc in Hash で絞り込みます(制御構造とパターン)。絞り込まずに doc["name"] と書くと、検査器は「添字できない型かもしれない」と報告します。
| 分野 | ライブラリ |
|---|---|
| データ形式 | json, csv, yaml, toml, ini, base64, digest, zlib, xml, erb, mustache |
| 文字列・解析 | strscan, optparse, getoptlong, shellwords, abbrev, prettyprint, pp, text, diff, terminal_table |
| 数 | bigdecimal, matrix, prime, securerandom, units |
| 時間 | time, date |
| 入出力・OS | fileutils, pathname, find, tempfile, stringio, logger, benchmark |
| ネットワーク | net_http, open_uri, uri, cgi, ipaddr, webrick |
| 並行 | monitor, mutex_m, timeout, observer, event_emitter |
| データ構造 | pqueue, lru_cache, trie, tsort, state_machine, semver |
10.2.1 部分的な移植と補足
- yaml は Psych の部分集合、xml は REXML の部分集合です。
- digest は MD5 と SHA を Sake で計算します(遅い)。zlib は CRC-32 と Adler-32 のチェックサムだけで、圧縮はありません。
- net_http は
Net::HTTP、Net::HTTPResponse、Net::HTTP::Getなど Ruby の名前のままです。https は組み込みのSocket.connect_sslを使います。 - timeout はブロックを別の Thread で走らせ、時間が来たら組み込みの
Thread.raiseで本当に止めます。 - pp は組み込みの
pp(1 行)とは別に、幅で折り返す PP です。
10.2.2 組み込みにあるので require しないもの
Ruby では require するが Sake では組み込みの操作になっているものがあります。require "tmpdir" は「読めるファイルが無い」という静的エラーになります。
| Ruby の require | Sake の組み込み |
|---|---|
tmpdir |
Dir.mktmpdir、Dir.tmpdir(Dir.tmpdir は tempfile が足す) |
open3 |
Open3.capture2、capture2e、capture3、Kernel.system |
set |
Set.of(...)、Set.add …(値と型) |
socket |
TCPServer、Socket(TCP のみ) |
10.2.3 gem に倣った小さなライブラリ
上の表のうち toml(toml-rb)、ini(inifile)、mustache、units(ruby-units)、text、diff(diff-lcs)、terminal_table、event_emitter(Node の EventEmitter)、pqueue、lru_cache(lru_redux)、trie、state_machine(AASM 風)、semver は Ruby の標準添付ではなく、よく使われる gem や他言語のライブラリに倣って書いたものです。どれに倣ったかは各 notes の冒頭にあります。
10.2.4 移植しないものの代わり
- singleton の代わりは
once { T.new(...) }です(関数とブロック)。 - forwardable / delegate の代わりは、委譲する操作を 1 つずつ書くことです。
- ostruct の代わりは Record
{a: 1}か Hash です。
10.3 gem の移植
よく使われる gem も、Sake で書ける範囲で移植しています。各移植は gem の名前を Sake で書ける限り保ちます(ActiveSupport::Inflector.pluralize(s)、Redis.get(r, k))。
| 分野 | ライブラリ |
|---|---|
| 端末・CLI | colorize, ruby_progressbar, highline, thor, awesome_print |
| ActiveSupport | active_support_inflector, active_support_core_ext, active_support_number_helper |
| テキスト | kramdown, liquid, i18n, faker |
| Web | rack, rackup, webrick, httparty |
| データ | redis, dotenv, money, rubyzip, chronic, jwt |
| 並行・テスト | concurrent_ruby, rspec, minitest |
10.3.1 補足
- active_support_core_ext は
Blank、StringExt、ArrayExt、HashExtの各 module と、クラスActiveSupport::Durationです。 - kramdown は GFM 方言、redis は RESP2 のクライアント、jwt は HS256/384/512 です。
- concurrent_ruby は
Concurrent::Future、Concurrent::Promise、Concurrent::Atom、Concurrent::Map、Concurrent::Semaphoreなどです。 - rspec は minitest と同じ形で、例を名乗って書きます:
RSpec.expect(ex, x).To.eq(y)。
10.3.2 落とした機能
落とした機能とその理由は sakelib/notes/not-ported.md の表にあります。主なものは次の 3 つです。
- 保持するブロック。
then(rescuer) {}やコールバックの登録。ブロックは第二級なので格納できません(関数とブロック)。 - 反射。
constantize、send、define_method。名前からの呼び出しはありません。 - クラスを値で渡す API。
expect(x).to be_a(T)。型は値ではないので、x in Tか型ごとの関数にします。
10.4 minitest
sakelib/minitest.sake は、Sake で書いたプログラムのための Ruby の Minitest 風のテスト枠組みです。Sake 自身のテストスイート(test/sake/*_test.sake)もこれで書かれています。
10.4.1 テストの書き方
Sake には反射が無いので、テストは名前で見つけるメソッドではなく、Minitest.test に渡すブロックです。各アサーションは、属するテスト(ブロックの引数 t)を第 1 引数で名乗ります。Sake のすべての操作が主語を名乗るのと同じです。
require "minitest"
suite = Minitest.suite("strings")
Minitest.test(suite, "upcase and split") do |t|
Minitest.assert_equal(t, "ABC", String.upcase("abc"))
Minitest.assert_equal(t, Any["a", "b"], String.split("a,b", ","))
Minitest.assert(t, String.empty?(""))
end
Minitest.run(suite) # 報告を印字。失敗があれば終了コード 1
# Running strings:
.
Finished in 0.026718s
1 runs, 3 assertions, 0 failures, 0 errors
- テストはその場で走ります。
Minitest.testはブロックをすぐ走らせて結果を記録します。テストは定義順に走ります。 Minitest.run(suite)は報告を印字し、失敗かエラーがあれば終了コード 1 で終わります。Minitest.report(suite)は印字だけして、全部通ったかを true / false で返します。
10.4.2 アサーション
Minitest の関数で、どれも第 1 引数がテスト t、最後に省略できるメッセージを取ります。
| 関数 | 検査 |
|---|---|
assert(t, cond), refute(t, cond) |
真 / 偽 |
assert_equal(t, expected, actual), refute_equal |
== |
assert_nil(t, x), refute_nil |
nil |
assert_in_delta(t, expected, actual, delta = 0.001) |
差が delta 以内 |
assert_includes(t, collection, x), refute_includes |
要素を含む |
assert_empty(t, collection), refute_empty |
空 |
assert_match(t, pattern, s) |
Regexp に一致 |
assert_raises(t) { ... } |
ブロックが例外を投げる。例外のメッセージを返す |
assert_output はありません。Sake のプログラムは自分の出力を捕まえられないので、値を印字して比べます。
10.4.3 失敗とエラー
失敗したアサーションは Minitest::Assertion を投げ、失敗(F)として記録されます。他の例外はエラー(E)として記録されます。Ruby の Minitest と同じです。
require "minitest"
suite = Minitest.suite("numbers")
Minitest.test(suite, "integer division floors") do |t|
Minitest.assert_equal(t, 3, 7 / 2)
end
Minitest.test(suite, "divides by zero") do |t|
Minitest.assert_equal(t, 0, 1 / 0)
end
Minitest.test(suite, "wrong expectation") do |t|
Minitest.assert_equal(t, 4, 2 + 3)
end
Minitest.run(suite) # 終了コード 1
# Running numbers:
.EF
Finished in 0.035481s
1) Error:
divides by zero
divided by 0
2) Failure:
wrong expectation
expected 4, got 5
3 runs, 2 assertions, 1 failures, 1 errors
10.5 テスト
ライブラリの各移植には Ruby の双子があります。同じプログラムを Sake のライブラリで書いた test/sakelib/NAME.sake と、Ruby のライブラリで書いた test/sakelib/NAME.rb で、両方が同じ出力を印字しなければなりません。移植の正しさの定義はこの「同じ出力」です。
# test/sakelib/shellwords.sake の形
require "shellwords"
p(Shellwords.split("a 'b c' d")) # => ["a", "b c", "d"]
p(Shellwords.escape("it's")) # => "it\\'s"
# 双子の shellwords.rb。Ruby で同じ 2 行を印字する
require "shellwords"
p(Shellwords.split("a 'b c' d"))
p(Shellwords.escape("it's"))
- 走らせ方。
ruby test/test_sakelib.rbが、各.sakeをbin/sake --strict(レベル 2)で、各.rbを Ruby でtest/sakelib/の中で走らせ、出力を比べます。 - gem が無いとき。 手元に無い gem の双子は、gem と同じ名前と出力を持つ素の Ruby の参照実装
test/sakelib/ref/NAME.rbで走らせます。 - notes。 双子が一致しない箇所、Ruby と違えた箇所は
sakelib/notes/NAME.mdに書きます。
組み込みリファレンス
組み込みの型とモジュールごとに 1 章ずつ、その名前空間のすべての操作を載せます。章の見出しは操作の名前で、別名は 1 つの節にまとめています(find, detect)。各節の最初の行は正確な署名です:
Array.first(x, [Integer]):xはその名前空間の型の値(主語)、[T]は省略できる引数、*Tは任意個の引数、[k: T]はキーワード、{ }は必須のブロック、[{ }]は省略できるブロック。Integer[*Any]のようなT[...]は、T の値の Array(型付き Array)を作る構築子です。
本文では、操作が何をして何を返すか、引数の意味、結果が nil になる場合とそれを検査器がどの厳格レベルで報告するか、投げる例外、検査器が静的に弾くもの、主語を書き換えるか新しい値を返すか、Ruby の同名メソッドとの違い、を順に述べます。例の # => 値 は実際の出力で、ruby tools/check_reference.rb がすべての例を --strict=2 で走らせて照合し、署名と操作の網羅も確かめています(ruby error と書いた例は、弾かれる・失敗することを確かめたもの)。
章の順は、Kernel と例外、数、文字列と正規表現、並びと写像、時刻、入出力と OS、スレッドとソケットです。演算子(+、<、[])の規則は演算子と添字に、各型の値の性質は値と型にあります。
11. Kernel
Kernel はプログラム全体から名前だけで呼べる操作の集まりです。puts(x)、Integer(s)、loop { } のように書き、Kernel.puts(x) と書く必要はありません(書いても動きます。名前の解決は、今の名前空間、トップレベルの関数、Kernel の順です。関数)。他の名前空間の操作と同じく、引数の型は静的に検査され、合わない型は type の問題です。
ここにあるのは入出力(puts、print、p、pp、format、gets、warn)、厳密な数値変換(Integer、Float、Rational、Complex)、値の表示(to_s、inspect)と複製(dup、equal?)、制御(loop、block_given?、once)、乱数と時間(rand、srand、sleep)、プロセス(exit、at_exit、system、ARGV、PROGRAM_NAME)です。raise は操作ではなく構文で、例外とエラーにあります。Math.PI や Float.INFINITY も操作として読みますが、それぞれの名前空間の章にあります(Sake に値の定数は無く、名前の付いた値は once で作ります)。
11.1 Kernel[]
Kernel[*Any]
何でも入る Array を作ります。どの型も Kernel を include しているので、モジュールの M[...](M を include した型の Array)の規則により、この Array はどんな値も受けます。prelude が Any = Kernel と名付けていて、Any[...] と書きます。要素の型は何でもよく、混ぜてもよく、以後どんな値でも追加できます(検査器は追加された値の型を要素型の和に加えます)。空の Array は Any[]。リテラル [1, 2] は Tuple なので、伸ばせる並びは Any[...] と書きます。型付き Array は Integer[1, 2] のように型の名前で作り、Array[...] は Array の Array です。
xs = Any[1, "a"]
Array.push(xs, :sym)
p(xs) # => [1, "a", :sym]
e = Any[]
Array.push(e, 1)
p(e) # => [1]
p([1, 2] == Any[1, 2]) # => false
```ruby error
xs = [1, 2]
Array.push(xs, 3) # !> [...] is a Tuple with a fixed length; for a growable Array, write Any[...]
## puts
`Kernel.puts(*Any)`
各引数を 1 行ずつ出力に書き、nil を返します(Ruby の `puts`)。String はそのまま(末尾に改行が無ければ足す)、nil は空行、他の値は `to_s` の形です。Array と Tuple は要素を 1 行ずつ、入れ子も平らにして書きます(空の Array は空行)。引数が無ければ空行です。クラスのインスタンスは型自身の `to_s(x)` があればそれで、無ければ `p` と同じ `#<struct ...>` の形で書きます([to_s](#to_s))。
```ruby
puts("a", 1, :sym)
# => a
# => 1
# => sym
puts(Any[1, [2, 3]])
# => 1
# => 2
# => 3
puts(Hash.of("k" => 1), 1..3)
# => {"k" => 1}
# => 1..3
11.2 print
Kernel.print(*Any)
各引数を to_s の形で改行を挟まずに書き、nil を返します(Ruby の print)。Array と Tuple は [1, 2] の形(puts のように 1 行ずつにはなりません)、nil は空文字列です。
print("a", 1, nil, "\n") # => a1
print(Any[1, 2], [3, 4], "\n") # => [1, 2][3, 4]
11.3 p
Kernel.p(*Any)
各引数を inspect の形で 1 行ずつ書きます(Ruby の p)。String は引用符付き、Symbol は :sym、クラスのインスタンスは #<struct Point x=1, y=2>、例外値は #<KeyError: msg> です。戻り値は Ruby と同じく、引数が 1 つならその値、複数なら引数の Tuple、無ければ nil(検査器もそう見ます)。型自身の inspect(x) を持つ クラスのインスタンスはそれで書きます。
x = p(1, "a")
# => 1
# => "a"
p(x) # => [1, "a"]
p(p()) # => nil
Point = Struct.new(:x, :y)
p(Point.new(1, 2)) # => #<struct Point x=1, y=2>
p(Hash.of("a" => 1), Set.of(1), 2r)
# => {"a" => 1}
# => Set[1]
# => (2/1)
11.4 pp
Kernel.pp(Any)
1 つの値を p と同じ形で書き、その値を返します。Ruby の pp は長い値を折り返しますが、Sake では p(x) と同じです。
a = pp(Any[1, {k: 2}]) # => [1, {k: 2}]
p(a) # => [1, {k: 2}]
11.5 format, sprintf
Kernel.format(String, *Any)
Kernel.sprintf(String, *Any)
Ruby の format と同じ書式で String を作ります: %d、%f、%e、%g、%x、%o、%b、%c、%s、%p、%% と、幅・精度・-・+・0 のフラグ。%s は値の to_s(クラス自身の to_s(x) も使われます)、%p は inspect です。%<name>d と %{name} は、Record({a: 1})か Symbol をキーに持つ Hash(Hash.of(a: 1))を唯一の引数として 1 つ渡します。演算子の形 fmt % x も同じです(String)。引数が足りない・多すぎる、数値の指示子に数にならない値を渡す、無いキーを名乗る(key<a> not found)、はすべて ArgumentError です。
p(format("%05.2f|%-4s|%p|%x", 3.14159, :ab, "q", 255)) # => "03.14|ab |\"q\"|ff"
p(sprintf("%+d %s %s", 3, nil, true)) # => "+3 true"
p(format("%<a>d-%<b>s", {a: 1, b: "x"})) # => "1-x"
p("%<a>05d" % {a: 42}) # => "00042"
p(format("%<n>d-%{s}", Hash.of(n: 1, s: "x"))) # => "1-x"
Point = Struct.new(:x, :y)
p(format("%s", Point.new(1, 2))) # => "#<struct Point x=1, y=2>"
```ruby error p(format(“%d %d”, 1)) # !> ArgumentError: Kernel.format: too few arguments
```ruby error
p(format("%<a>d", {b: 1})) # !> ArgumentError: Kernel.format: key<a> not found
11.6 gets
Kernel.gets()
標準入力から 1 行を読み、末尾の改行を含めた String を返します。入力の終わりでは nil です。戻り値は String | nil なので、検査せずに String.chomp(gets) と書くと --strict(レベル 2)が nil の問題として止めます。if (line = gets) のように先に調べます。Ruby の gets と違い引数(区切り)は取りません。
line = gets
p(line) # => "3\n"
if (rest = gets)
p(String.split(String.chomp(rest), " ")) # => ["1", "2"]
end
p(gets) # => nil
```ruby error p(String.chomp(gets)) # !> argument 1 may be nil
## warn
`Kernel.warn(*Any)`
各引数を `puts` と同じ形でエラー出力(stderr)に書き、nil を返します。引数が無ければ何も書きません。使い方の誤りを報告して `exit` する、という組み合わせが典型です。
```ruby error
warn("usage: prog FILE") # !> usage: prog FILE
exit(2)
11.7 Integer
Kernel.Integer(String|Integer|Float)
Ruby の厳密な Integer() です。String は 10 進の整数、または 0x・0b・0o・0 接頭辞付きの整数で、_ の区切りと前後の空白は許されます。それ以外("12abc"、""、"1e3")は ArgumentError。Float は切り捨て、Integer はそのままです。String.to_i は読めるところまでを黙って読みますが、こちらは全体が整数でなければ失敗します。NaN や Infinity の Float は FloatDomainError で(メッセージは値の名前 NaN・Infinity)、他の例外と同じく rescue できます。
p(Integer("42")) # => 42
p(Integer(" 0x1f ")) # => 31
p(Integer("1_000")) # => 1000
p(Integer(3.9)) # => 3
p(String.to_i("12abc")) # => 12
```ruby error p(Integer(“12abc”)) # !> ArgumentError: Kernel.Integer: invalid value for Integer(): “12abc”
```ruby error
p(Integer(Float.NAN)) # !> FloatDomainError: Kernel.Integer: NaN
11.8 Float
Kernel.Float(String|Integer|Float)
Ruby の厳密な Float() です。String は 10 進の実数("1e3"、"1_0.5"、前後の空白可)でなければ ArgumentError。Integer は Float に、Float はそのままです。
p(Float("1.5")) # => 1.5
p(Float("1e3")) # => 1000.0
p(Float(2)) # => 2.0
```ruby error p(Float(“abc”)) # !> ArgumentError: Kernel.Float: invalid value for Float(): “abc”
## Rational
`Kernel.Rational(Integer|Float|Rational|String, [Integer|Rational])`
Ruby の `Rational(a, b = 1)` です。`a` は Integer、Float、Rational、または `"1/3"`・`"0.75"`・`"3"` の形の String、`b` は Integer か Rational で、`a / b` を既約の Rational にします。Float は `Float.to_r` と同じく正確な値として読むので、`Rational(0.5)` は `(1/2)` ですが、`Rational(0.1)` は `0.1` が実際に表す長い 2 進の分数です(短い `(1/10)` が欲しければ `Float.rationalize`)。読めない String は `ArgumentError`、`b` が 0 なら `ZeroDivisionError`、NaN や Infinity の Float は `FloatDomainError`。
```ruby
p(Rational(3, 6)) # => (1/2)
p(Rational("0.75")) # => (3/4)
p(Rational("1/3")) # => (1/3)
p(Rational(1r/2, 1r/4)) # => (2/1)
p(Rational(0.5)) # => (1/2)
p(Rational(1.5, 1r/2)) # => (3/1)
p(Rational(0.1) == Float.to_r(0.1)) # => true
p(Float.rationalize(0.1)) # => (1/10)
```ruby error p(Rational(1, 0)) # !> ZeroDivisionError: Kernel.Rational: divided by 0
## Complex
`Kernel.Complex(Integer|Float|Rational, [Integer|Float|Rational])`
Ruby の `Complex(re, im = 0)` です。実部と虚部はそれぞれ実数で、String は受け付けません(`String.to_c` を使います)。
```ruby
p(Complex(1, 2)) # => (1+2i)
p(Complex(1.5)) # => (1.5+0i)
p(Complex(1r, 2.5)) # => ((1/1)+2.5i)
11.9 to_s
Kernel.to_s(Any)
値を puts・文字列補間 "#{x}"・format の %s が使う形の String にします。String はそのまま、nil は ""、Symbol は名前、数は Ruby の to_s、Array・Tuple・Hash・Set・Record・Range は inspect と同じ形です。クラスのインスタンスは、その型が to_s(x) を定義していればそれを呼び(include したモジュールのものでも)、無ければ #<struct T ...> です。Ruby の x.to_s に相当しますが、Sake では値へのメソッド呼び出しが無いので、どの型の値もこの 1 つの操作で文字列にします。
class Temp
attr_reader celsius
def to_s(t) = "#{@celsius}C"
end
t = Temp.new(21)
p(to_s(t)) # => "21C"
puts("now #{t}") # => now 21C
p(to_s(nil)) # => ""
p(to_s(Any[1, "a"])) # => "[1, \"a\"]"
p(to_s(:sym)) # => "sym"
11.10 inspect
Kernel.inspect(Any)
値を p が書く形の String にします(Ruby の x.inspect): String は引用符とエスケープ付き、Symbol は :sym、nil は "nil"、容器は要素を inspect した形です。クラスのインスタンスは、その型が inspect(x) を定義していればそれ(include したモジュールのものでも)、無ければ #<struct T f=v, ...>、例外値は #<E: message> です。p、pp、format の %p、容器の中の要素の表示がこれを使います。
module Pretty
def inspect(v) = "<pretty>"
end
class Box
include Pretty
attr_reader n
end
p(inspect("a\n")) # => "\"a\\n\""
p(inspect(nil)) # => "nil"
p(inspect(Box.new(1))) # => "<pretty>"
p(Any[Box.new(1)]) # => [<pretty>]
11.11 dup
Kernel.dup(Any)
値の浅い複製(Ruby の obj.dup): Array、Tuple、Hash、Set、Record、クラスのインスタンス、String は新しい容器になり、中の要素は同じ値のままです(要素の容器は共有されます)。Integer、Symbol、nil、true/false、Range はそのままの値が返ります。型は元と同じです。その型が dup(x) を定義している クラスのインスタンスには、それを呼びます(戻り値はその関数の型)。型の関数の中で要素を複製するときは Kernel.dup(@items) と書きます。裸の dup は型自身の dup に解決されるからです。
a = Any[Any[1], Any[2]]
b = dup(a)
Array.push(b, Any[3])
Array.push(b[0], 9)
p(a) # => [[1, 9], [2]]
p(b) # => [[1, 9], [2], [3]]
class Box
attr_accessor items
def dup(b) = Box.new(Kernel.dup(@items))
end
x = Box.new(Any[1])
y = dup(x)
Array.push(Box.items(y), 2)
p(Box.items(x)) # => [1]
p(Box.items(y)) # => [1, 2]
11.12 equal?
Kernel.equal?(Any, Any)
2 つの引数が同じ値(同一のオブジェクト)なら true(Ruby の a.equal?(b))。== は中身を比べますが、こちらは同一性です。dup の結果は元と == ですが equal? ではありません。同じ Integer、Symbol、nil、true/false は常に同一です。別々に書いた String リテラルは同一ではありません。
a = Any[1]
b = a
p(equal?(a, b)) # => true
p(a == dup(a)) # => true
p(equal?(a, dup(a))) # => false
p(equal?("a", "a")) # => false
p(equal?(:a, :a)) # => true
11.13 loop
Kernel.loop() { }
ブロックを break まで繰り返します(Ruby の loop)。戻り値は break v の v(裸の break は nil)で、検査器にはブロック内のすべての break の値の和が見えます。next は次の回へ進みます。break の無い loop は決して返りません(Ruby の StopIteration による終了は Sake にはありません)。
i = 0
v = loop do
i += 1
next if i < 3
break i * 10
end
p(v) # => 30
p(loop { break }) # => nil
11.14 block_given?
Kernel.block_given?()
今の関数がブロック付きで呼ばれたなら true。block_given? を調べる関数は、ブロック無しでも呼べます(調べない関数で yield すれば、ブロックは必須です。関数)。検査器は条件の中の block_given? をその呼び出しごとの値に畳むので、ブロック無しの呼び出しで yield に届く枝は走りません。トップレベルでは false です。
def each_or(x)
return yield(x) if block_given?
x
end
p(each_or(1)) # => 1
p(each_or(1) { |v| v + 10 }) # => 11
p(block_given?) # => false
11.15 once
Kernel.once() { }
ブロックの値を返します。ブロックはプログラム中のその場所が最初に実行されたとき 1 度だけ走り、値はプログラムの終わりまで保たれて、以後の実行(別の呼び出し、別のスレッド)はその同じ値を受け取ります。Sake には値の定数が無いので、表や名前付きの値を 1 度だけ計算する手段です: def table = once { ... }。保たれるのは値そのもので、Array なら後から変えられます(Ruby の定数と同じ)。計算中のブロックが自分の once にまた届くと SystemStackError でプログラムが止まります(rescue できません)。
def table = once { puts("computing"); Any[1, 2, 3] }
p(table)
# => computing
# => [1, 2, 3]
p(table) # => [1, 2, 3]
p(equal?(table, table)) # => true
```ruby error def rec(n) = once { rec(n) } rec(1) # !> SystemStackError: once: the block reached its own once again while computing it
## rand
`Kernel.rand([Integer|Float])`
Ruby の `rand` ですが、引数は厳密です。引数が無ければ 0.0 以上 1.0 未満の Float。Integer `n` なら 0 以上 `n` 未満の Integer。Float `x` なら 0.0 以上 `x` 未満の Float。上限は正でなければならず、0・0.0・負の数は `ArgumentError`(`invalid argument - 0`)です(Ruby は `|n|` を使い、0 なら Float を返します)。Range は受け付けません(静的に `type` の問題)。
```ruby
f = rand
p(f >= 0.0 && f < 1.0) # => true
i = rand(6)
p(i >= 0 && i < 6) # => true
g = rand(2.5)
p(g >= 0.0 && g < 2.5) # => true
```ruby error p(rand(0)) # !> ArgumentError: Kernel.rand: invalid argument - 0
## srand
`Kernel.srand([Integer])`
乱数の種を設定し、前の種(Integer)を返します(Ruby の `srand`)。引数が無ければ新しい種を無作為に選びます。同じ種からの `rand` の列は同じです。
```ruby
srand(7)
a = Array.map(Any[1, 2, 3]) { rand(1000) }
srand(7)
b = Array.map(Any[1, 2, 3]) { rand(1000) }
p(a == b) # => true
p(srand(1)) # => 7
11.16 sleep
Kernel.sleep([Integer|Float|Rational])
指定した秒数だけ止まり、眠った秒数(Integer に丸めたもの、Ruby の sleep)を返します。引数が無ければ永久に止まります(他のスレッドに起こされるまで)。
p(sleep(0)) # => 0
p(sleep(1r/100)) # => 0
11.17 exit
Kernel.exit([Integer|Boolean])
プログラムを終了状態 status で終えます。Integer はそのまま、true は 0、false は 1、省略は 0。rescue では捕まえられず(rescue => e も通り抜けます)、途中の ensure 節と at_exit のブロックは走ります。検査器は exit の後のコードを到達しないものとして扱います(型 []: 値を返しません)。
at_exit { puts("at_exit") }
begin
exit(true)
rescue => e
puts("not caught")
ensure
puts("ensure") # => ensure
end
puts("not reached") # => at_exit
```ruby error puts(“start”) # !> start exit(3) puts(“not reached”)
## at_exit
`Kernel.at_exit() { }`
ブロックを登録して nil を返します。登録したブロックはプログラムが終わるとき(普通に、`exit` で、または捕まえられなかった例外で)後に登録したものから順に走ります(Ruby の `at_exit`)。
```ruby
at_exit { puts("bye 1") }
at_exit { puts("bye 2") }
puts("main")
# => main
# => bye 2
# => bye 1
11.18 system
Kernel.system(String, *String)
コマンドを子プロセスとして走らせ、終わるまで待ちます(Ruby の system)。引数が 1 つならシェルを通し、複数なら最初がコマンド、残りがその引数です。戻り値は、終了状態 0 なら true、0 以外なら false、起動できなければ nil(Boolean | nil)。子プロセスの出力はそのまま出力に出ます。出力を String として受け取るには Open3.capture2・capture3 を使います。
p(system("echo", "hi"))
# => hi
# => true
p(system("false")) # => false
p(system("/no/such/command")) # => nil
11.19 ARGV
Kernel.ARGV()
プログラムのコマンドライン引数の Array(String の Array)。Ruby の定数のように ARGV と書きますが操作で、毎回同じ Array を返すので Array.shift(ARGV) や OptionParser の parse! で縮めると以後の ARGV にも反映されます。要素の String は凍結されていて、その場で変える操作(String.concat など)は TypeError です。
p(ARGV) # => []
Array.push(ARGV, "x")
p(ARGV) # => ["x"]
p(equal?(ARGV, ARGV)) # => true
11.20 PROGRAM_NAME
Kernel.PROGRAM_NAME()
実行中のプログラム(最初のファイル)のパス(Ruby の $0)。他の Kernel の操作と違い、裸の PROGRAM_NAME は型名として読まれて拒まれるので(type PROGRAM_NAME cannot be used as a value)、Kernel.PROGRAM_NAME と書きます。
p(String.end_with?(Kernel.PROGRAM_NAME, ".sake")) # => true
12. Exceptions
組み込みの例外型です。組み込みの操作が実行時に失敗したときに投げるもので、プログラム自身も raise E, "msg" で投げられます。各型はフィールド message だけを持つクラスとして宣言されていて(クラス)、他のクラスと同じ操作が使えます:
E.new(message)が例外値を作ります(messageは普通 String ですが、型は検査されません)。raise E, "msg"はraise E.new("msg")の略、raise Eはメッセージを型名にします。raise "msg"はRuntimeErrorです。raiseは構文で、例外とエラーにあります。E.message(e)がメッセージを読み、E.set_message(e, s)が書き換えます(e.E.messageも同じ)。古いE.get_messageは拒まれ、E.messageに直すよう案内されます。どの型の例外値でも読めるのがException.message(e)で、rescue => eのように型が和になっているときに使います。Exception.backtrace(e)は投げられたときの呼び出しの列("FILE:LINE:in 'FUNCTION'"の Array、内側が先)、Exception.cause(e)はそのとき rescue 節で扱っていた例外です。まだ投げていない値ではどちらも nil です(バックトレースと原因)。rescue E => eは列挙した型を捕まえます。組み込みの型は Ruby の親子関係のとおりで、rescue IndexErrorはKeyErrorも、rescue IOErrorはEOFErrorも、rescue RangeErrorはFloatDomainErrorも、rescue ArgumentErrorはMath::DomainErrorも捕まえます。それを親に書いたクラス(class E < RangeError)も捕まえます。ユーザのclass B < Aは書き写しなのでrescue Aは B を捕まえません。まとめるには、型が include したモジュールを書きます(rescue M)。複数はrescue A, B => e、すべてはrescue => e(rescue StandardError、rescue Exceptionも同じ)です。p(e)は#<E: message>、==はフィールドで比べます。- 組み込みの操作が投げた例外のメッセージは Ruby の文(
divided by 0)で、rescue されずにプログラムを終えたときの報告には操作名も付きます(ZeroDivisionError: Kernel.Rational: divided by 0)。 - 組み込みの操作の中で Ruby が投げた例外が、バックトレース付きの生の Ruby エラーとして漏れることはありません。同じ種類の Sake の例外になり、他と同じく rescue でき、メッセージは Ruby のもの(Ruby の
Sake::TupleはTuple)です。Ruby のEOFError・Errno::*・ClosedQueueErrorはIOError、FrozenErrorはTypeError、Math::DomainErrorはそのままMath::DomainError、Ruby 自身のRuntimeError(不正な文字列のString.undump)はArgumentErrorになります。Sake のRuntimeErrorはプログラム自身のraiseからだけ生じます。
Math::DomainError も組み込みの例外型で、rescue Math::DomainError => e と raise Math::DomainError, "msg" に書けますが、入れ子の名前なので Math::DomainError.new(...) と Math::DomainError.message(e) は書けません(Exception.message(e) で読みます)。Math.sqrt(-1)、Math.log(-1)、Integer.sqrt(-1)、Integer.digits(-1)、負の Float x の x ** 0.5(-8.0 ** 0.5 is not a real number (a negative base with a fractional exponent)。Ruby は Complex を返します)が投げます。SystemStackError(深すぎる再帰、once の再入)、LocalJumpError、NotImplementedError はプログラムの誤りで、rescue できず、rescue に書くのは静的エラーです。
型の誤り(TypeError)やパターンの不一致(NoMatchingPatternError)の多くは、実行前の検査が type の問題として止めるので、実行時に届くのは検査が見られなかった場所(Any を受ける操作、--strict=0 で走らせたプログラム)だけです。
begin
Integer("x")
rescue ArgumentError, TypeError => e
puts(Exception.message(e)) # => invalid value for Integer(): "x"
end
e = KeyError.new("missing")
p(e) # => #<KeyError: missing>
p(KeyError.message(e)) # => "missing"
KeyError.set_message(e, "other")
p(Exception.message(e)) # => "other"
p(e == KeyError.new("other")) # => true
12.1 RuntimeError
RuntimeError.new(message)
raise "msg"(型を書かない raise)が投げる型です。組み込みの操作が自分から投げることはなく、Thread.raise(t, msg) だけが相手のスレッドに投げ、Thread.value(t) や Thread.join(t) がそれを届けます(操作の中の Ruby の RuntimeError、たとえば String.undump のものは ArgumentError になります)。プログラムの「その他の失敗」に使います。したがって検査器は、本体が RuntimeError を投げえない rescue RuntimeError に警告を出します(the begin body never raises RuntimeError [unused-rescue]。Thread.value / Thread.join は Thread.raise を呼ぶプログラムでだけ数える、というヒント付き)。下の例ではこの節は何も捕まえず、String.undump の ArgumentError でプログラムが止まります。
begin
raise "plain"
rescue RuntimeError => e
p(e) # => #<RuntimeError: plain>
end
```ruby error begin String.undump(“"abc”) rescue RuntimeError => e # !> warning: rescue RuntimeError: the begin body never raises RuntimeError [unused-rescue] p(e) end
## ArgumentError
`ArgumentError.new(message)`
引数の値が受け付けられないとき。投げる操作: `Integer`、`Float`、`Rational` の読めない String、`format` の引数の過不足・数でない値・無いキー、`rand(0)` と負の上限、負のサイズ(`Array.first(a, -1)`)、`Integer ** 負の Integer`、`Regexp.new(s, flags)` の不正なフラグ、`Time.new`・`Time.at`・`Time.now` の範囲外の値や不正なゾーン、`Integer.to_s(n, base)` の不正な基数、`String.unpack` の不正な書式、`String.ljust` の空の詰め文字、`tr` の逆向きの範囲、dump された文字列でないものの `String.undump`(`unterminated dumped string`。Ruby では RuntimeError)、`ENV.set("", v)` と `Process.clock_gettime(999)`(OS が拒む引数)、`Zlib` の壊れたデータ(`incorrect header check`)、ブロックの引数の数の不一致。比べられない要素(`Array.sort`・`max`・`min`・`Tuple.max`・`Set.sort` と、ブロックの結果を通じた `*_by` の形)は、検査器が要素の型を見られるなら静的な `type` の問題で、実行時の `ArgumentError` に届くのは NaN のように見られない場合だけです。
```ruby
begin
Array.sort(Any[1.0, Float.NAN])
rescue ArgumentError => e
puts(Exception.message(e)) # => cannot compare elements of types Float
end
begin
2 ** -1
rescue ArgumentError => e
puts(Exception.message(e)) # => Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
end
begin
String.undump("\"abc")
rescue ArgumentError => e
puts(Exception.message(e)) # => unterminated dumped string
end
```ruby error Array.sort(Any[1, “a”]) # !> Array.sort: elements compared in order may be (Integer, String), which cannot be compared [type]
## TypeError
`TypeError.new(message)`
値の型が合わないとき: 操作が受け取った引数が違う型、左のオペランドの型が支えない演算子、型付き Array(`Integer[]` など)への違う型の書き込み、Tuple でも Array でもない値からの多重代入、凍結された String(Hash のキー、Set の要素、Symbol の名前、`ARGV` の要素)のその場での変更(`cannot change this String in place: it is a Hash key, a Set element, a Symbol's name, or a program argument`)、`Exception.message` に例外でない値。検査器が要素の型を見られなかった場所で操作の中の Ruby が `TypeError` や `NoMethodError` を出したときもこの型です: `Array.pack(Any["a"], "C")`(`no implicit conversion of String into Integer`)、`Range.first(1.0..2.0, 2)`(`can't iterate from Float`)、`--strict=0` で String でないキーや値を渡した `ENV.replace`(`keys and values must be String, got String => Integer`)。これらの大半は実行前の検査が静的に止めるので、実行時に届くのは検査が通した場所だけです。
```ruby
begin
Array.pack(Any["a"], "C")
rescue TypeError => e
puts(Exception.message(e)) # => no implicit conversion of String into Integer
end
```ruby error Array.push(Integer[], “a”) # !> Array.push: an element must be Integer, but is String
## KeyError
`KeyError.new(message)`
無いキー: `Hash.fetch`(既定値もブロックも無いとき)、`Hash.fetch_values`、`ENV.fetch`、無いフィールドを名乗る Record パターン。`format` の `%<name>` の無いキーは `ArgumentError` です。
```ruby
h = Hash.of("a" => 1)
begin
Hash.fetch(h, "b")
rescue KeyError => e
puts(Exception.message(e)) # => key not found: "b"
end
p(Hash.fetch(h, "b", 0)) # => 0
12.2 IndexError
IndexError.new(message)
範囲外の添字: Array.fetch、Tuple の外の添字(実行時に届いたもの。リテラルの添字は静的に止まります)、T の Array の末尾の先への書き込み(Integer[1, 2][5] = 1。隙間が nil になるため)、Array.insert の末尾より先の位置(index 3 is past the end of the Array (length 1); the gap would be nil。Ruby は隙間を nil で埋めます)、Regexp に無いグループの MatchData.begin(m, 5)(index 5 out of matches)、長さの違う行の Array.transpose、文字の途中を指す String.byteindex のオフセット。x[k] は範囲外で nil を返し、例外にはなりません。
def at(t, i) = t[i]
begin
at([1, 2], 5)
rescue IndexError => e
puts(Exception.message(e)) # => index 5 is outside a Tuple of length 2
end
begin
Array.fetch(Any[3, 1], 9)
rescue IndexError => e
puts(Exception.message(e)) # => index 9 outside of array bounds: -2...2
end
12.3 ZeroDivisionError
ZeroDivisionError.new(message)
Integer(と Rational)のゼロ除算: /、%、Integer.divmod、div、modulo、remainder、ceildiv、Rational(a, 0)、Rational.quo。Float の 1.0 / 0 は Ruby と同じく Infinity で、例外にはなりません。
begin
7 % 0
rescue ZeroDivisionError => e
puts(Exception.message(e)) # => divided by 0
end
p(1.0 / 0) # => Infinity
12.4 RangeError
RangeError.new(message)
値が表せる範囲の外: 有限の Range が要る操作に終端の無い Range(Range.to_a(1..)、Range.last(1..)、Range.sort など)、Range.first に始端の無い Range(cannot get the first element of beginless range)、Integer.chr の文字にならない Integer(256 out of char range)、Integer.pow(2, -1, 7) の負の指数、Range.sum の Complex の初期値。
begin
Range.to_a(1..)
rescue RangeError => e
puts(Exception.message(e)) # => cannot do this on an endless Range 1..
end
begin
Integer.chr(256)
rescue RangeError => e
puts(Exception.message(e)) # => 256 out of char range
end
12.5 FloatDomainError
FloatDomainError.new(message)
NaN や Infinity を Integer や Rational にしようとしたとき: Float.to_i、Arithmetic.to_i、Arithmetic.round・floor・ceil・truncate、Float.to_r、Float.rationalize、Float.numerator・denominator、Kernel.Integer(x)、Kernel.Rational(x)。メッセージは値の名前です。どれも rescue できます。
begin
Float.to_i(Float.INFINITY)
rescue FloatDomainError => e
puts(Exception.message(e)) # => Infinity
end
begin
Float.to_r(Float.NAN)
rescue FloatDomainError => e
puts(Exception.message(e)) # => NaN
end
begin
Integer(Float.NAN)
rescue FloatDomainError => e
puts(Exception.message(e)) # => NaN
end
12.6 IOError
IOError.new(message)
入出力の失敗: File.read、File.write、File.open、Dir.mkdir、Dir.children など File と Dir の操作(Ruby の Errno::ENOENT などがこの 1 つの型になり、メッセージは Ruby のもの)、閉じた IO への操作、ファイルでないストリームの IO.size(not a file)、端末でない IO への端末の操作(IO.winsize など。not a terminal)、ソケットのエラー(接続拒否、リセット、解決できない名前、タイムアウト)、閉じた Queue への Queue.push。操作の中の Ruby の EOFError も Sake の EOFError ではなくこの型になります。
begin
File.read("no-such-file")
rescue IOError => e
puts(Exception.message(e)) # => No such file or directory @ rb_sysopen - no-such-file
end
begin
IO.size(IO.stdin)
rescue IOError => e
puts(Exception.message(e)) # => not a file
end
q = Queue.new
Queue.close(q)
begin
Queue.push(q, 1)
rescue IOError => e
puts(Exception.message(e)) # => push to a closed Queue
end
12.7 EOFError
EOFError.new(message)
入力の終わりに達した読み出し。Ruby では IO#readline などが投げますが、Sake の読み出し操作(IO.read(io, n)、IO.gets、IO.getc、gets)は終わりで nil を返すので、これを投げる組み込みの操作はありません(操作の中の Ruby の EOFError は IOError になります)。プログラム自身の「もう入力が無い」に使えます。IOError とは別の型で、rescue IOError では捕まりません。
def next_token(xs) = Array.shift(xs) || raise(EOFError, "no more tokens")
begin
next_token(Any[])
rescue EOFError => e
puts(Exception.message(e)) # => no more tokens
end
12.8 EncodingError
EncodingError.new(message)
文字列のエンコーディングの誤り: String.encode で変換できないバイト、エンコーディングの合わない String の連結や比較など、操作の中で Ruby が EncodingError(Encoding::UndefinedConversionError など)を出したもの。
begin
String.encode("\xff", "UTF-16")
rescue EncodingError => e
puts(Exception.message(e)) # => "\xFF" on UTF-8
end
12.9 RegexpError
RegexpError.new(message)
不正な正規表現: 式を埋め込んだリテラル /#{s}/ のパターンが不正なとき、Regexp.new(s) の不正なパターン、String の操作に渡した不正なパターン String(String.match?("a+c", "+"))。リテラルに直接書いたパターンは構文エラーとして実行前に止まります。
s = "("
begin
r = /#{s}/
rescue RegexpError => e
puts(Exception.message(e)) # => end pattern with unmatched parenthesis: /(/
end
begin
Regexp.new("(")
rescue RegexpError => e
puts(Exception.message(e)) # => end pattern with unmatched parenthesis: /(/
end
begin
String.match?("a+c", "+")
rescue RegexpError => e
puts(Exception.message(e)) # => target of repeat operator is not specified: /+/
end
12.10 NoMatchingPatternError
NoMatchingPatternError.new(message)
case/in のどの枝にも値が合わず else も無いとき、x => pattern が合わないとき。検査器は値の型から合う枝が無いことを静的に見つけて type の問題として止めるので(下の例)、実行時に投げられるのは検査が通した場所、たとえば --strict=0 で走らせたプログラムです。メッセージは合わなかった値を述べます。
```ruby error
def kind(x)
case x
in Integer then “int”
end
end
kind(“s”) # !> case/in: no in branch matches String
## ThreadError
`ThreadError.new(message)`
スレッドの操作の誤り: 持っている Mutex をもう一度 `Mutex.lock` や `Mutex.synchronize`、持っていない Mutex の `Mutex.unlock`、今のスレッド自身の `Thread.join`。すべてのスレッドが待ちになるデッドロックも Sake の `ThreadError` としてプログラムを終えます([例外とエラー](../08-exceptions.md))。
```ruby
m = Mutex.new
Mutex.lock(m)
begin
Mutex.lock(m)
rescue ThreadError => e
puts(Exception.message(e)) # => deadlock; recursive locking
end
begin
Mutex.unlock(Mutex.new)
rescue ThreadError => e
puts(Exception.message(e)) # => Attempt to unlock a mutex which is not locked
end
begin
Thread.join(Thread.current)
rescue ThreadError => e
puts(Exception.message(e)) # => Target thread must not be current thread
end
13. Integer
Integer は Ruby と同じく大きさに上限の無い整数です: 42、-7、1_000_000、0x1f、0b101、0o17、2 ** 100(値と型)。変換 Integer("42")、Integer(3.9) はこの名前空間ではなく Kernel の操作です(Kernel.Integer。不正な入力は ArgumentError)。Integer は値であり、主語を変更する操作はここにはありません。すべての結果は新しい値です。
Integer に使える演算子は Integer が include するモジュールから来ます(演算子と添字): Arithmetic が + - * / % ** と単項の -x、Comparable が <=> < <= > >=、Bitwise が & | ^ << >> と単項の ~x を与え、== と != は Kernel のものです。a + b は a の型で振り分けられるので、左が Integer なら Integer.+(a, b) が走ります。以下の Integer.+(x, y) などはその関数形です。右側の型は閉じた表で決まります: Integer と Integer は Integer、Float とは Float、Rational とは Rational(** だけは Rational の指数で Rational | Float)、Complex とは Complex。< などは Integer、Float、Rational と比べられ、ビット演算子は Integer だけを取ります。それ以外の型の右側(String、nil、Symbol)は静的に type の問題(the operands are (Integer, String), which the left operand's type does not support)、実行時は TypeError です。関数形は第 1 引数も Integer でなければなりません(Integer.+(1.0, 2) は type の問題。Float.+ か演算子を使います)。
Ruby と違う点は 2 つ: Integer ** 負の Integer は Rational を返さず ArgumentError を投げます(a ** b の型が b の値に依存しないように)。また値に対するメソッド呼び出しはありません(1.+(2) は拒否され、Integer.+(1, 2) か 1 + 2 と書きます)。/ と % は Ruby と同じく床へ丸め(-7 / 2 は -4)、0 で割ると ZeroDivisionError です。
丸めの一族 round、floor、ceil、truncate と abs、to_f、to_i、zero? はモジュール Arithmetic(Arithmetic)にもあり、そちらはどの実数も取ります(Integer、Float、Rational の x に Arithmetic.round(x)。Ruby の x.round)。以下の項目は Integer 専用の形で、Float の主語は Arithmetic を使うようヒント付きで拒否されます。結果が nil になりうる操作はこの名前空間にはありません: どの結果も確定した Integer、Float、Rational、String、Boolean、Tuple、Array です。
13.1 Integer[]
Integer[*Any]
Integer の Array を作ります: 要素がすべて Integer でなければならない型付き Array で、構築時と書き込み(Array.push、a[i] = v など)のたびに検査されます。別の型のリテラル要素は実行前に拒否され(element 2 must be Integer, got String)、別の型の書き込みは静的に type の問題、実行時は TypeError です。空の Integer の Array は Integer[] です。Any[1, 2] は要素の型が推論され、書き込まれたものに応じて広がる点が違います。
a = Integer[1, 2, 3]
Array.push(a, 4)
p(a) # => [1, 2, 3, 4]
p(Array.size(Integer[])) # => 0
```ruby error Array.push(Integer[], 1.5) # !> Array.push: an element must be Integer, but is Float
## +, -, *
`Integer.+(x, Any)`
`Integer.-(x, Any)`
`Integer.*(x, Any)`
加算・減算・乗算。どんな大きさでも正確です。結果の型は右側に従います: Integer なら Integer、Float なら Float、Rational なら Rational、Complex なら Complex。それ以外の右側は `type` の問題です。単項マイナス `-x` は `Integer.-@` で、演算子としてだけ書けます。
```ruby
p(Integer.+(1, 2)) # => 3
p(Integer.-(1, 2)) # => -1
p(3 * 4) # => 12
p(2 + 1.5) # => 3.5
p(1 + 1r) # => (2/1)
p(2 ** 64 + 1) # => 18446744073709551617
```ruby error p(1 + “a”) # !> the operands are (Integer, String), which the left operand’s type does not support
## /, %
`Integer./(x, Any)`
`Integer.%(x, Any)`
除算と剰余。Integer 同士では Ruby と同じくどちらも**床**へ丸めます: 商は負の無限大の方向へ丸め、余りは除数の符号を取ります(`-7 / 2` は -4、`-7 % 3` は 2、`7 % -3` は -2)。Integer の除数 0 は `ZeroDivisionError`。右側が Float なら結果は Float で、`0.0` で割ると `Infinity`(IEEE)、Rational なら Rational です。`div`、`modulo`、`remainder`、`divmod`、`fdiv`、`ceildiv` も参照。
```ruby
p(7 / 2) # => 3
p(-7 / 2) # => -4
p(7 % 3) # => 1
p(-7 % 3) # => 2
p(Integer./(7, 2.0)) # => 3.5
p(1 / 0.0) # => Infinity
```ruby error p(Integer./(1, 0)) # !> ZeroDivisionError: Integer./: divided by 0
## **
`Integer.**(x, Any)`
べき乗。Integer に非負の Integer の指数なら正確な Integer。Float の指数は Float を与えます。**Rational の指数**は、整数値なら Rational(`2 ** 2r` は `(4/1)`)、そうでなければ Float(`2 ** (1r/2)` は `1.4142135623730951`)になるので、検査器はその結果を `Rational | Float` とします。どちらかとして使う前に `case`/`in` で絞ってください。Ruby と違い、**負の Integer の指数は `ArgumentError`** です(Ruby は Rational を返す)。メッセージが Rational を得る書き方(`2r ** -1`)を教えます。負の底に分数の指数(`(-8) ** 0.5`)は `Math::DomainError` です(Ruby は Complex を返す)。`**` は単項マイナスより強く結合するので Ruby と同じく `-2 ** 2` は -4、右結合です(`2 ** 3 ** 2` は 512)。`pow` も参照。
```ruby
p(2 ** 10) # => 1024
p(Integer.**(2, 0)) # => 1
p(2 ** 0.5) # => 1.4142135623730951
p(2 ** 2r) # => (4/1)
p(2 ** (1r/2)) # => 1.4142135623730951
p(-2 ** 2) # => -4
x = 2 ** 2r
case x
in Rational then p(Rational.numerator(x)) # => 4
in Float then p(x)
end
```ruby error p(2 ** -1) # !> ArgumentError: Arithmetic.**: Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
```ruby error
p((-8) ** 0.5) # !> Math::DomainError: Arithmetic.**: -8 ** 0.5 is not a real number (a negative base with a fractional exponent)
```ruby error p(Rational.numerator(2 ** 2r)) # !> Rational.numerator: argument 1 must be Rational, but can be Float
## ==, !=
`Integer.==(x, Any)`
`Integer.!=(x, Any)`
等価(`!=` はその否定)。数は型をまたいで比べられます: `1 == 1.0`、`1 == 1r`、`1 == Complex(1, 0)` は true。Ruby と同じく、別の型の値とは決して等しくありません(`1 == "1"` は false、`1 == nil` は false)。関数形 `Integer.==(x, y)` も、右側が他のどんな型でも同じく false(`!=` は true)を返し、エラーにはなりません。
```ruby
p(1 == 1) # => true
p(1 == 1.0) # => true
p(1 != 1) # => false
p(1 == "1") # => false
p(1 == nil) # => false
p(Integer.==(1, nil)) # => false
p(Integer.==(1, "a")) # => false
p(Integer.!=(1, "a")) # => true
13.2 <, <=, >, >=
Integer.<(x, Any)
Integer.<=(x, Any)
Integer.>(x, Any)
Integer.>=(x, Any)
順序。右側は Integer、Float、Rational です。Complex に順序は無く、String や nil は type の問題(Comparable.<: the operands are (Integer, String) ...)。結果は true か false。
p(1 < 2) # => true
p(1 <= 1) # => true
p(2 >= 3) # => false
p(1 < 1.5) # => true
p(Integer.<(1, 2r)) # => true
```ruby error p(1 < “a”) # !> Comparable.<: the operands are (Integer, String), which the left operand’s type does not support
## <=>
`Integer.<=>(x, Any)`
左が右(Integer、Float、Rational)より小さければ -1、等しければ 0、大きければ 1。数同士では nil にならないので、検査器は結果を Integer とします。それ以外の右側は `type` の問題です。
```ruby
p(1 <=> 2) # => -1
p(2 <=> 1) # => 1
p(1 <=> 1) # => 0
p(Integer.<=>(1, 1.5)) # => -1
13.3 &, |, ^
Integer.&(x, Any)
Integer.|(x, Any)
Integer.^(x, Any)
2 の補数のビットに対するビット積・ビット和・排他的論理和(Ruby と同じ)。負の数は上位に無限に 1 が並んでいるものとして振る舞います。両側とも Integer でなければならず、右側の Float は type の問題(Bitwise.&: the operands are (Integer, Float) ...)。単項の補数 ~x は Integer.~ で、演算子としてだけ書けます。
p(6 & 3) # => 2
p(6 | 3) # => 7
p(6 ^ 3) # => 5
p(~5) # => -6
p(Integer.&(6, 3)) # => 2
```ruby error p(6 & 1.5) # !> Bitwise.&: the operands are (Integer, Float), which the left operand’s type does not support
## <<, >>
`Integer.<<(x, Any)`
`Integer.>>(x, Any)`
Integer のビット数だけ左・右へシフトします(Ruby と同じ): `x << n` は `x * 2**n`(正確で、必要なだけ伸びる)、`x >> n` は床へ丸めます(`-1 >> 1` は -1)。負のビット数は逆方向へシフトします(`1 << -1` は 0)。右側は Integer でなければなりません。
```ruby
p(1 << 4) # => 16
p(256 >> 4) # => 16
p(-1 >> 1) # => -1
p(1 << 100 == 2 ** 100) # => true
13.4 bit_length
Integer.bit_length(x)
2 の補数で数を表すのに要るビット数(符号ビットを除く。Ruby と同じ): 255 は 8、256 は 9、0 と -1 は 0、-256 は 8。
p(Integer.bit_length(255)) # => 8
p(Integer.bit_length(256)) # => 9
p(Integer.bit_length(0)) # => 0
p(Integer.bit_length(-1)) # => 0
13.5 allbits?, anybits?, nobits?
Integer.allbits?(x, Integer)
Integer.anybits?(x, Integer)
Integer.nobits?(x, Integer)
マスクで立っているビットが数の中ですべて立っているか、どれかが立っているか、どれも立っていないか: x & mask == mask、x & mask != 0、x & mask == 0。
p(Integer.allbits?(6, 2)) # => true
p(Integer.allbits?(6, 3)) # => false
p(Integer.anybits?(6, 3)) # => true
p(Integer.nobits?(6, 1)) # => true
13.6 div, modulo, remainder
Integer.div(x, Integer|Float|Rational)
Integer.modulo(x, Integer)
Integer.remainder(x, Integer)
div は床へ丸めた商で、除数が Float や Rational でも Integer です(Integer.div(7, 2.0) は 3。7 / 2.0 は 3.5)。modulo は % と同じで、除数の符号を取る余り。remainder は Ruby と同じく被除数の符号を取る余りです(Integer.remainder(-7, 3) は -1。-7 % 3 は 2)。除数 0(0.0 も)は ZeroDivisionError。
p(Integer.div(-7, 2)) # => -4
p(Integer.div(7, 2.0)) # => 3
p(Integer.modulo(-7, 3)) # => 2
p(Integer.remainder(-7, 3)) # => -1
p(Integer.remainder(7, -3)) # => 1
```ruby error p(Integer.modulo(7, 0)) # !> ZeroDivisionError: Integer.modulo: divided by 0
## divmod
`Integer.divmod(x, Integer)`
床へ丸めた除算の Tuple `[商, 余り]`。`q * b + r == a` が成り立ち、`r` は除数の符号を取ります。除数 0 は `ZeroDivisionError`。Ruby と違い除数は Integer でなければなりません(`Float.divmod` は Float の主語を取ります)。
```ruby
p(Integer.divmod(7, 2)) # => [3, 1]
p(Integer.divmod(-7, 2)) # => [-4, 1]
q, r = Integer.divmod(17, 5)
p([q, r]) # => [3, 2]
```ruby error p(Integer.divmod(7, 0)) # !> ZeroDivisionError: Integer.divmod: divided by 0
## ceildiv
`Integer.ceildiv(x, Integer)`
正の無限大の方向へ丸めた商(Ruby の `ceildiv`): `Integer.ceildiv(7, 2)` は 4、`Integer.ceildiv(-7, 2)` は -3。除数 0 は `ZeroDivisionError`。
```ruby
p(Integer.ceildiv(7, 2)) # => 4
p(Integer.ceildiv(-7, 2)) # => -3
p(Integer.ceildiv(6, 2)) # => 3
13.7 fdiv
Integer.fdiv(x, Integer)
Float としての商、Integer.to_f(a) / b。除数 0 は例外にならず、Ruby と同じく Infinity、-Infinity、NaN(0 / 0)になります。Ruby と違い除数は Integer でなければなりません。
p(Integer.fdiv(7, 2)) # => 3.5
p(Integer.fdiv(1, 3)) # => 0.3333333333333333
p(Integer.fdiv(1, 0)) # => Infinity
13.8 pow
Integer.pow(x, Integer, [Integer])
Integer.pow(a, b) は Integer の指数の a ** b で、負の指数は同じ ArgumentError。Integer.pow(a, b, m) は a ** b を m で割った余りで、Ruby と同じく巨大なべきを作らずに計算します。結果は m の符号を取ります。法付きのとき、負の指数は RangeError(メッセージは Ruby のもので、指数を 1 番目の引数と数えます)、法 0 は ZeroDivisionError で、どちらも rescue できます。
p(Integer.pow(2, 10)) # => 1024
p(Integer.pow(2, 10, 1000)) # => 24
p(Integer.pow(3, 100, 7)) # => 4
p(Integer.pow(2, 10, -7)) # => -5
begin
Integer.pow(2, 3, 0)
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
```ruby error p(Integer.pow(2, -1)) # !> ArgumentError: Integer.pow: Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
```ruby error
p(Integer.pow(2, -1, 7)) # !> RangeError: Integer.pow: Integer#pow() 1st argument cannot be negative when 2nd argument specified
13.9 gcd, lcm
Integer.gcd(x, Integer)
Integer.lcm(x, Integer)
最大公約数と最小公倍数。Ruby と同じく常に非負です。gcd(0, n) は |n|、gcd(0, 0) は 0、lcm(0, n) は 0。
p(Integer.gcd(12, 18)) # => 6
p(Integer.gcd(-12, 18)) # => 6
p(Integer.gcd(0, 0)) # => 0
p(Integer.lcm(4, 6)) # => 12
p(Integer.lcm(0, 6)) # => 0
13.10 gcdlcm
Integer.gcdlcm(x, Integer)
Tuple [gcd, lcm]。
g, l = Integer.gcdlcm(4, 6)
p([g, l]) # => [2, 12]
13.11 sqrt
Integer.sqrt(x)
整数平方根: 2 乗が数を超えない最大の Integer(Ruby の Integer.sqrt)。どんな大きさでも正確で、Math.sqrt は Float を返すところです。負の数は Math::DomainError(メッセージは Ruby のもの)を投げ、rescue できます。
p(Integer.sqrt(16)) # => 4
p(Integer.sqrt(17)) # => 4
p(Integer.sqrt(10 ** 20)) # => 10000000000
```ruby error p(Integer.sqrt(-1)) # !> Math::DomainError: Integer.sqrt: Numerical argument is out of domain - “isqrt”
## abs, magnitude
`Integer.abs(x)`
`Integer.magnitude(x)`
絶対値(Integer)。`Arithmetic.abs(x)` はどの実数にも同じことをします。
```ruby
p(Integer.abs(-5)) # => 5
p(Integer.magnitude(5)) # => 5
13.12 even?, odd?
Integer.even?(x)
Integer.odd?(x)
偶数か奇数か。0 は偶数、-3 は奇数です。
p(Integer.even?(4)) # => true
p(Integer.even?(-3)) # => false
p(Integer.odd?(-3)) # => true
13.13 zero?, positive?, negative?
Integer.zero?(x)
Integer.positive?(x)
Integer.negative?(x)
数が 0 か、0 より大きいか、0 より小さいか。0 は正でも負でもありません。Arithmetic.zero?(x) はどの実数も取ります。
p(Integer.zero?(0)) # => true
p(Integer.positive?(0)) # => false
p(Integer.negative?(-1)) # => true
13.14 succ, next
Integer.succ(x)
Integer.next(x)
数に 1 を足したもの。
p(Integer.succ(5)) # => 6
p(Integer.next(-1)) # => 0
13.15 pred
Integer.pred(x)
数から 1 を引いたもの。
p(Integer.pred(5)) # => 4
p(Integer.pred(0)) # => -1
13.16 times
Integer.times(x) { }
0, 1, …, n-1 をブロックへ渡し、n(主語)を返します(Ruby と同じ)。n が 0 以下ならブロックは呼ばれません。ブロックは必須です(ブロック無しの Ruby の times は Enumerator を返す)。ブロックの引数は 1 つ。break v はループを終えて v を結果にし、next は次の回へ進み、関数の中の return は関数から戻ります。
n = Integer.times(3) { |i| puts(i) } # => 0
# => 1
# => 2
p(n) # => 3
sum = 0
Integer.times(4) { |i| sum += i }
p(sum) # => 6
p(Integer.times(0) { |i| puts(i) }) # => 0
p(Integer.times(3) { |i| break i * 10 if i == 1 }) # => 10
```ruby error Integer.times(3) # !> Integer.times requires a block
## upto
`Integer.upto(x, Integer) { }`
a, a+1, ..., b をブロックへ渡し、a(主語)を返します(Ruby の `a.upto(b)`)。a が b より大きければブロックは呼ばれません。ブロックは必須です。
```ruby
p(Integer.upto(1, 3) { |i| puts(i) }) # => 1
# => 2
# => 3
# => 1
p(Integer.upto(3, 1) { |i| puts(i) }) # => 3
13.17 downto
Integer.downto(x, Integer) { }
a, a-1, …, b をブロックへ渡し、a を返します(Ruby の a.downto(b))。a が b より小さければブロックは呼ばれません。ブロックは必須です。
p(Integer.downto(3, 1) { |i| puts(i) }) # => 3
# => 2
# => 1
# => 3
13.18 step
Integer.step(x, Integer, Integer) { }
Integer.step(a, limit, step) は a, a+step, a+2·step, … を、値が(step の向きで)limit を越えない間ブロックへ渡し、a を返します(Ruby の a.step(limit, step))。limit と step はどちらも必須の Integer です(Ruby の Float の刻みやキーワード形はありません)。刻み 0 は ArgumentError。負の刻みは下ります。
p(Integer.step(1, 10, 3) { |i| puts(i) }) # => 1
# => 4
# => 7
# => 10
# => 1
p(Integer.step(10, 1, -3) { |i| print(i, " ") }) # => 10 7 4 1 10
```ruby error Integer.step(1, 10, 0) { |i| puts(i) } # !> ArgumentError: Integer.step: step can’t be 0
## clamp
`Integer.clamp(x, Integer, Integer)`
数を区間に収めたもの: `lo` より下なら `lo`、`hi` より上なら `hi`、それ以外はそのまま。Ruby と違い Range の形(`clamp(1..10)`)はありません。`lo` が `hi` より大きいと `ArgumentError`(`min argument must be less than or equal to max argument`)です。
```ruby
p(Integer.clamp(5, 1, 10)) # => 5
p(Integer.clamp(-5, 1, 10)) # => 1
p(Integer.clamp(50, 1, 10)) # => 10
```ruby error p(Integer.clamp(5, 10, 1)) # !> ArgumentError: Integer.clamp: min argument must be less than or equal to max argument
## between?
`Integer.between?(x, Integer, Integer)`
`lo <= x <= hi` なら true。両端を含みます。`lo` が `hi` より大きければ false です。
```ruby
p(Integer.between?(5, 1, 10)) # => true
p(Integer.between?(1, 1, 10)) # => true
p(Integer.between?(0, 1, 10)) # => false
13.19 round
Integer.round(x, [Integer])
桁数無しなら数そのもの。負の桁数 -d なら 10^d の倍数へ丸め、半分はゼロから遠い側へ寄せます(Ruby と同じ。Integer.round(1250, -2) は 1300、Integer.round(-1250, -2) は -1300)。正の桁数は数を変えません。結果は常に Integer。主語は Integer だけで、Float は type の問題になり、どの実数も丸める Arithmetic.round(x) をヒントが示します。
p(Integer.round(1234)) # => 1234
p(Integer.round(1234, -2)) # => 1200
p(Integer.round(1250, -2)) # => 1300
p(Integer.round(-1250, -2)) # => -1300
p(Integer.round(1234, 2)) # => 1234
```ruby error p(Integer.round(2.5)) # !> Integer.round: argument 1 must be Integer, but is Float
## floor, ceil
`Integer.floor(x, [Integer])`
`Integer.ceil(x, [Integer])`
桁数無しなら数そのもの。負の桁数 -d なら 10^d の倍数へ、`floor` は下(負の無限大の方向)へ、`ceil` は上(正の無限大の方向)へ丸めます(Ruby と同じ)。常に Integer。Float には `Arithmetic.floor` か `Float.floor` を使います。
```ruby
p(Integer.floor(1234, -2)) # => 1200
p(Integer.floor(-1234, -2)) # => -1300
p(Integer.ceil(1234, -2)) # => 1300
p(Integer.ceil(-1234, -2)) # => -1200
p(Integer.floor(5)) # => 5
13.20 truncate
Integer.truncate(x, [Integer])
桁数無しなら数そのもの。負の桁数 -d なら 10^d の倍数へゼロの方向へ丸めます。常に Integer。
p(Integer.truncate(1234, -2)) # => 1200
p(Integer.truncate(-1234, -2)) # => -1200
13.21 digits
Integer.digits(x)
10 進の各桁を下の桁から並べた新しい Array(基数無しの Ruby の digits。Integer.digits(1234) は [4, 3, 2, 1]、0 は [0])。型付きの Integer[] で、別の型の値を push すると type の問題です。負の数は Math::DomainError(out of domain)を投げます。Ruby と違い基数の引数は無く、to_s(n, base) と String.chars を使います。
p(Integer.digits(1234)) # => [4, 3, 2, 1]
p(Integer.digits(0)) # => [0]
p(Array.sum(Integer.digits(999))) # => 27
d = Integer.digits(12)
Array.push(d, 9)
p(d) # => [2, 1, 9]
```ruby error Array.push(Integer.digits(12), “x”) # !> Array.push: an element must be Integer, but is String
```ruby error
p(Integer.digits(-1)) # !> Math::DomainError: Integer.digits: out of domain
13.22 to_s
Integer.to_s(x, [Integer])
数を指定の基数(2 から 36。省略時 10)で書いた String。9 を超える桁は小文字で、負の数には先頭に - が付きます。2..36 の外の基数は ArgumentError(invalid radix 1)。String.to_i(s, base) が読み戻します。
p(Integer.to_s(255)) # => "255"
p(Integer.to_s(255, 2)) # => "11111111"
p(Integer.to_s(-255, 16)) # => "-ff"
p(Integer.to_s(35, 36)) # => "z"
```ruby error p(Integer.to_s(255, 37)) # !> ArgumentError: Integer.to_s: invalid radix 37
## to_f
`Integer.to_f(x)`
数を Float にしたもの(正確に表せない大きな数は最も近いもの)。`Arithmetic.to_f(x)` はどの実数も取ります。
```ruby
p(Integer.to_f(3)) # => 3.0
13.23 to_i, to_int
Integer.to_i(x)
Integer.to_int(x)
数そのもの。どの実数向けにも書かれたコードのためにあり、Arithmetic.to_i(x) は Float や Rational を Integer へ切り捨てます。String の変換 Integer(s) は Kernel のもので、この操作ではありません。
p(Integer.to_i(3)) # => 3
p(Integer.to_int(-3)) # => -3
13.24 to_r
Integer.to_r(x)
数を分母 1 の Rational にしたもの。
p(Integer.to_r(3)) # => (3/1)
13.25 rationalize
Integer.rationalize(x, [Integer|Float|Rational])
数を Rational にしたもの(to_r と同じ)。省略可能な許容誤差は Float.rationalize との対称のために受け取るだけで、Integer では結果を変えません。
p(Integer.rationalize(3)) # => (3/1)
p(Integer.rationalize(3, 0.5)) # => (3/1)
13.26 numerator, denominator
Integer.numerator(x)
Integer.denominator(x)
分数と見たとき、数は自分自身を分子、1 を分母に持ちます。
p(Integer.numerator(6)) # => 6
p(Integer.denominator(6)) # => 1
13.27 integer?
Integer.integer?(x)
Integer では常に true(Ruby の Numeric#integer?)。別の型かもしれない値の型を調べるには x in Integer を使います。
p(Integer.integer?(6)) # => true
13.28 size
Integer.size(x)
機械表現のバイト数(Ruby と同じ): 機械語 1 語に収まる数は 8、それより大きい数はもっと多く。有効なビット数には bit_length を使います。
p(Integer.size(1)) # => 8
p(Integer.size(2 ** 100)) # => 13
13.29 chr
Integer.chr(x)
そのバイト値を持つ 1 文字の String。数は 0 から 255 でなければならず、外れると RangeError(256 out of char range)を投げます(rescue できます)。エンコーディング無しの Ruby の chr と同じく、0 から 127 は ASCII の String、128 から 255 は 1 バイトのバイナリ String で、UTF-8 の文字列とつなぐと EncodingError を投げます。String.ord が逆の操作です。255 を超えるコードポイントの String は別の方法で作ります(format("%c", n))。
p(Integer.chr(65)) # => "A"
puts(Integer.chr(97)) # => a
p(Integer.chr(10)) # => "\n"
p(String.ord(Integer.chr(200))) # => 200
begin
Integer.chr(200) + "é"
rescue EncodingError => e
p(Exception.message(e)) # => "incompatible character encodings: BINARY (ASCII-8BIT) and UTF-8"
end
```ruby error p(Integer.chr(256)) # !> RangeError: Integer.chr: 256 out of char range
## ord
`Integer.ord(x)`
数そのもの(Ruby の `Integer#ord`)。String の先頭文字のコードポイントを返す `String.ord` の対になるものです。
```ruby
p(Integer.ord(65)) # => 65
p(String.ord("A")) # => 65
14. Float
Float は IEEE 754 の倍精度浮動小数点数です。リテラルは 1.5、2.0、1e-3(値と型)。文字列からの変換 Float("1.5") は Kernel の操作で、Integer からは Integer.to_f(n) です。Integer を Float の代わりに渡す暗黙の変換はありません: Float[1] や Float.clamp(x, 1, 2) は静的に type の問題です。
Float に使える演算子は +、-、*、/、%、** と ==、!=、<、<=、>、>=、<=> です。右側は Integer、Float、Rational、Complex のどれでもよく、結果の型は閉じた表で決まります: Integer・Rational との演算は Float、Complex との演算は Complex(演算子と添字)。ここに挙げた Float.+(x, y) などはその演算子を関数の形で呼ぶもので、左側が Float と決まっている分だけ検査が細かくなります。
Ruby と同じく、0 で割ると例外ではなく Infinity・-Infinity・NaN になり(%・modulo・divmod は例外で ZeroDivisionError)、NaN・Infinity を Integer に変える操作(to_i、floor、ceil、round、truncate、to_r、numerator、denominator)は FloatDomainError を投げます。Ruby の Float::INFINITY などの定数は、Sake では Float.INFINITY のように引数なしの操作として読みます(Sake に値の定数はありません)。Ruby との大きな違いは、round(f, 2) が桁数を付けると常に Float を返すこと、負の底に分数の指数((-8.0) ** 0.5)が Complex ではなく Math::DomainError になること、divmod が Tuple を返すこと、<=> と infinite? の nil を検査器が追うことです。
14.1 Float[]
Float[*Any]
Float の Array を作ります(Float そのものではありません)。Integer[] などと同じ型付き Array で、要素はすべて Float でなければならず、Array.push などの書き込みも毎回検査されます。Integer は Float に変換されません: Float[1.5, 2] は静的に type の問題、実行時は TypeError です。空の Float の Array は Float[]。
fs = Float[1.5, 2.0]
Array.push(fs, 3.5)
p(fs) # => [1.5, 2.0, 3.5]
p(Array.size(Float[])) # => 0
p(Array.sum(fs)) # => 7.0
```ruby error Array.push(Float[], 1) # !> Array.push: an element must be Float, but is Integer
## INFINITY, NAN, EPSILON, MAX, MIN
`Float.INFINITY()`
`Float.NAN()`
`Float.EPSILON()`
`Float.MAX()`
`Float.MIN()`
Ruby の `Float::INFINITY` などに当たる値を返す、引数なしの操作です(Sake には値の定数が無いので、`Float.INFINITY` または `Float.INFINITY()` と書きます)。`INFINITY` は正の無限大(負は `-Float.INFINITY`)、`NAN` は非数、`EPSILON` は 1.0 と「1.0 より大きい最小の Float」との差、`MAX` は最大の有限値、`MIN` は正の最小の正規化数です。型はすべて Float。NaN はどの値とも `==` で等しくならず、自分自身とも等しくありません。
```ruby
p(Float.INFINITY) # => Infinity
p(-Float.INFINITY) # => -Infinity
p(Float.NAN == Float.NAN) # => false
p(Float.EPSILON) # => 2.220446049250313e-16
p(1.0 + Float.EPSILON > 1.0) # => true
p(Float.MAX) # => 1.7976931348623157e+308
p(Float.MAX * 2) # => Infinity
p(Float.MIN) # => 2.2250738585072014e-308
14.2 +, -, *, /, %, **
Float.+(x, Any)
Float.-(x, Any)
Float.*(x, Any)
Float./(x, Any)
Float.%(x, Any)
Float.**(x, Any)
x + y などの関数形で、左側が Float のものです。右側 y は Integer、Float、Rational、Complex のどれか。Integer・Rational との結果は Float、Complex との結果は Complex です。それ以外の型(String、nil など)は静的に type の問題(the operands are (Float, String), which the left operand's type does not support)、実行時は TypeError です。
割り算は IEEE に従い、1.0 / 0 は Infinity、0.0 / 0 は NaN で、例外は出ません。% は Ruby の Float#%(結果の符号は右側に従う)ですが、0(Integer の 0 でも Float の 0.0 でも)で割ると ZeroDivisionError です(rescue できます。Ruby 4 も同じく投げます)。** は Integer・Float・Rational の指数で Float を返します。Ruby と違い、負の底に分数の指数((-8.0) ** 0.5)は Complex ではなく Math::DomainError なので、結果は常に Float です。
p(1.5 + 2) # => 3.5
p(Float.+(1.5, 2r)) # => 3.5
p(Float.+(1.5, Complex(1, 2))) # => (2.5+2i)
p(Float.*(1.5, 2)) # => 3.0
p(Float./(1.0, 0)) # => Infinity
p(Float./(0.0, 0)) # => NaN
p(Float.%(7.5, 2)) # => 1.5
p(-7.5 % 2) # => 0.5
p(Float.**(2.0, 3)) # => 8.0
p(Float.**(2.0, 0.5)) # => 1.4142135623730951
p(Float.**(2.0, 2r)) # => 4.0
p((-8.0) ** 2) # => 64.0
begin
7.5 % 0.0
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
```ruby error p(Float.+(1.5, “a”)) # !> the operands are (Float, String), which the left operand’s type does not support
```ruby error
p(Float.%(7.5, 0)) # !> ZeroDivisionError: Float.%: divided by 0
```ruby error p(Float.(-8.0, 0.5)) # !> Math::DomainError: Float.: -8.0 ** 0.5 is not a real number (a negative base with a fractional exponent)
## ==, !=
`Float.==(x, Any)`
`Float.!=(x, Any)`
`x == y` の関数形。Float は Integer・Rational・Complex と値で比べられ、`1.0 == 1` は true です。NaN は自分自身を含むどの値とも等しくありません。別の型の値とは決して等しくなく(`1.0 == "1"` は false)、関数形の `Float.==(x, y)` も演算子と同じく、右側が他のどんな型でも false(`!=` は true)を返し、エラーにはなりません。
```ruby
p(Float.==(1.0, 1)) # => true
p(Float.==(1.0, 1r)) # => true
p(1.5 != 1.5) # => false
p(Float.!=(1.0, Float.NAN)) # => true
p(1.0 == "1") # => false
p(Float.==(1.0, "1")) # => false
p(Float.!=(1.0, nil)) # => true
14.3 <, <=, >, >=
Float.<(x, Any)
Float.<=(x, Any)
Float.>(x, Any)
Float.>=(x, Any)
x < y などの関数形。右側は Integer、Float、Rational のどれか(Complex に順序は無く、静的に type の問題)。NaN との比較はすべて false です。
p(1.5 < 2) # => true
p(Float.<=(1.5, 2r)) # => true
p(Float.>(1.5, 2)) # => false
p(Float.>=(1.5, 1.5)) # => true
p(Float.NAN < 1.0) # => false
```ruby error p(Float.<(1.0, Complex(1, 0))) # !> the operands are (Float, Complex), which the left operand’s type does not support
## <=>
`Float.<=>(x, Any)`
`x <=> y` の関数形で、-1、0、1 を返します。右側は Integer、Float、Rational。どちらかが NaN なら **nil** なので結果の型は `Integer | nil` で、`--strict`(レベル 2 の `nil`)は結果をそのまま算術に使うことを `the operands may be nil` と報告します。先に `if c` などで確かめてください。
```ruby
p(Float.<=>(1.5, 2)) # => -1
p(1.5 <=> 1.5) # => 0
p(Float.<=>(Float.NAN, 1.0)) # => nil
c = 2.0 <=> 1
if c
p(c + 1) # => 2
end
```ruby error c = Float.<=>(1.5, 2.0) p(c + 1) # !> the operands may be nil
## abs, magnitude
`Float.abs(x)`
`Float.magnitude(x)`
絶対値(Float)。`-0.0` は `0.0`、`-Infinity` は `Infinity` になります。
```ruby
p(Float.abs(-1.5)) # => 1.5
p(Float.magnitude(-1.5)) # => 1.5
p(Float.abs(-0.0)) # => 0.0
p(Float.abs(-Float.INFINITY)) # => Infinity
14.4 negative?, positive?, zero?
Float.negative?(x)
Float.positive?(x)
Float.zero?(x)
x < 0、x > 0、x == 0 を true/false で返します。-0.0 は zero? が true で、negative? も positive? も false です。NaN はどれも false。
p(Float.negative?(-1.5)) # => true
p(Float.negative?(-0.0)) # => false
p(Float.positive?(0.0)) # => false
p(Float.zero?(-0.0)) # => true
p(Float.zero?(1.5)) # => false
14.5 nan?, finite?, infinite?
Float.nan?(x)
Float.finite?(x)
Float.infinite?(x)
nan? は NaN のとき true。finite? は NaN でも無限大でもないとき true。infinite? は Ruby と同じく正の無限大で 1、負の無限大で -1、それ以外(NaN を含む)で nil を返すので、型は Integer | nil です。真偽として if Float.infinite?(x) のように使うのが普通で、値を算術に使うには先に nil を確かめます(--strict の nil が報告します)。
p(Float.nan?(0.0 / 0)) # => true
p(Float.nan?(1.5)) # => false
p(Float.finite?(1.5)) # => true
p(Float.finite?(Float.INFINITY)) # => false
p(Float.infinite?(1.5)) # => nil
p(Float.infinite?(Float.INFINITY)) # => 1
p(Float.infinite?(-Float.INFINITY)) # => -1
14.6 to_i, to_int, truncate
Float.to_i(x)
Float.to_int(x)
Float.truncate(x)
小数部分を切り捨てて 0 に近い方の Integer にします(Ruby の Float#to_i)。3 つは同じ結果です。NaN と Infinity は Integer にできないので FloatDomainError(メッセージはその値 NaN / Infinity)。Ruby の truncate(digits) の桁数引数は Float には無く、Arithmetic.truncate(x, n) を使います(Arithmetic)。
p(Float.to_i(1.9)) # => 1
p(Float.to_int(-1.9)) # => -1
p(Float.truncate(-1.8)) # => -1
begin
Float.to_i(Float.NAN)
rescue FloatDomainError => e
p(Exception.message(e)) # => "NaN"
end
```ruby error p(Float.truncate(Float.INFINITY)) # !> FloatDomainError: Float.truncate: Infinity
## floor, ceil
`Float.floor(x)`
`Float.ceil(x)`
`floor` は x 以下の最大の Integer、`ceil` は x 以上の最小の Integer を返します。NaN・Infinity は `FloatDomainError`。桁数引数は無く、`Arithmetic.floor(x, n)`・`Arithmetic.ceil(x, n)` が代わりです。
```ruby
p(Float.floor(1.8)) # => 1
p(Float.floor(-1.2)) # => -2
p(Float.ceil(1.2)) # => 2
p(Float.ceil(-1.2)) # => -1
```ruby error p(Float.floor(Float.INFINITY)) # !> FloatDomainError: Float.floor: Infinity
## round
`Float.round(x, [Integer], [half: Symbol])`
四捨五入します。桁数を省くと最も近い Integer を返し、.5 は既定では 0 から遠い方に丸めます(`Float.round(2.5)` は 3、`-2.5` は -3。Ruby の `round` と同じ)。桁数 `n` を付けると小数第 n 位に丸めた **Float** を返します。負の桁数は 10 の冪に丸めますが、結果はやはり Float です(Ruby の `1234.5.round(-2)` は Integer 1200 ですが、Sake では `1200.0`)。結果の型が桁数の有無で決まるのは、検査器が引数の個数を見るからです。キーワード `half:` は Ruby と同じく、ちょうど .5 のときの丸め方を選びます: `:up`(既定。0 から遠い方へ)、`:even`(偶数側へ。銀行家の丸め)、`:down`(0 に近い方へ)。他の Symbol は `ArgumentError`(`invalid rounding mode: foo`)、Symbol 以外は `type` の問題です。桁数なしの NaN・Infinity は `FloatDomainError`、桁数付きならそのまま NaN・Infinity を返します。
```ruby
p(Float.round(1.5)) # => 2
p(Float.round(2.5)) # => 3
p(Float.round(-2.5)) # => -3
p(Float.round(1.2345, 2)) # => 1.23
p(Float.round(3.14159, 3)) # => 3.142
p(Float.round(1234.5, -2)) # => 1200.0
p(Float.round(1.5, 0)) # => 2.0
p(Float.round(Float.NAN, 2)) # => NaN
p(Float.round(2.5, half: :even)) # => 2
p(Float.round(3.5, half: :even)) # => 4
p(Float.round(2.5, half: :down)) # => 2
p(Float.round(2.5, 0, half: :even)) # => 2.0
p(Float.round(1.25, 1, half: :even)) # => 1.2
p(Float.round(1250.0, -2, half: :even)) # => 1200.0
```ruby error p(Float.round(-Float.INFINITY)) # !> FloatDomainError: Float.round: -Infinity
```ruby error
p(Float.round(2.5, half: :foo)) # !> ArgumentError: Float.round: invalid rounding mode: foo
14.7 divmod
Float.divmod(x, Any)
商と余りの Tuple [q, r] を返します。q は x / y を floor した Integer、r は x - q * y の Float で、符号は y に従います(Ruby の 7.5.divmod(2) が [3, 1.5] なのと同じ)。y は Float か Integer で、他の型(Rational を含む)は静的に type の問題です。0(Integer の 0 でも Float の 0.0 でも)で割ると ZeroDivisionError、x が NaN・Infinity なら FloatDomainError(メッセージはその値)です。どちらも rescue できます。
p(Float.divmod(7.5, 2)) # => [3, 1.5]
p(Float.divmod(-7.5, 2.0)) # => [-4, 0.5]
q, r = Float.divmod(7.5, 2)
p(q + 1) # => 4
p(r + 0.5) # => 2.0
begin
Float.divmod(Float.NAN, 2)
rescue FloatDomainError => e
p(Exception.message(e)) # => "NaN"
end
```ruby error p(Float.divmod(7.5, 0)) # !> ZeroDivisionError: Float.divmod: divided by 0
```ruby error
p(Float.divmod(7.5, 0.0)) # !> ZeroDivisionError: Float.divmod: divided by 0
14.8 fdiv, quo
Float.fdiv(x, Integer|Float|Rational)
Float.quo(x, Integer|Float|Rational)
浮動小数点の割り算 x / y(Float)。/ と同じ結果で、0 で割れば Infinity か NaN です(例外は出ません)。2 つは同じ働きです。
p(Float.fdiv(7.5, 2)) # => 3.75
p(Float.quo(1.0, 4r)) # => 0.25
p(Float.fdiv(1.0, 0)) # => Infinity
14.9 modulo
Float.modulo(x, Integer|Float|Rational)
x % y と同じ余り(Float。符号は y に従う)で、% と同じ働きです。0(Integer の 0 でも 0.0 でも)で割ると ZeroDivisionError を投げます。
p(Float.modulo(7.5, 2)) # => 1.5
p(Float.modulo(-7.5, 2)) # => 0.5
p(Float.modulo(5.5, 2.5)) # => 0.5
begin
Float.modulo(7.5, 0)
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
14.10 between?
Float.between?(x, Float, Float)
lo <= x <= hi のとき true。lo、hi は Float でなければならず、Integer は静的に type の問題です(argument 2 must be Float, but is Integer)。
p(Float.between?(1.5, 1.0, 2.0)) # => true
p(Float.between?(2.5, 1.0, 2.0)) # => false
```ruby error p(Float.between?(1.5, 1, 2)) # !> Float.between?: argument 2 must be Float, but is Integer
## clamp
`Float.clamp(x, Float, Float)`
`x` を `lo..hi` に収めた Float を返します(`lo` 未満なら `lo`、`hi` より大きければ `hi`)。`lo`、`hi` は Float のみ。`lo > hi` は `ArgumentError`(`min argument must be less than or equal to max argument`)で、`rescue` できます。
```ruby
p(Float.clamp(2.5, 1.0, 2.0)) # => 2.0
p(Float.clamp(0.5, 1.0, 2.0)) # => 1.0
p(Float.clamp(1.5, 1.0, 2.0)) # => 1.5
```ruby error p(Float.clamp(1.5, 1, 2)) # !> Float.clamp: argument 2 must be Float, but is Integer
```ruby error
p(Float.clamp(1.5, 2.0, 1.0)) # !> ArgumentError: Float.clamp: min argument must be less than or equal to max argument
14.11 next_float, prev_float
Float.next_float(x)
Float.prev_float(x)
x の次に大きい・次に小さい表現可能な Float。next_float(1.0) - 1.0 が Float.EPSILON です。Float.MAX の次は Infinity、NaN の次は NaN。
p(Float.next_float(1.0)) # => 1.0000000000000002
p(Float.prev_float(1.0)) # => 0.9999999999999999
p(Float.next_float(0.0)) # => 5.0e-324
p(Float.next_float(1.0) - 1.0 == Float.EPSILON) # => true
p(Float.next_float(Float.MAX)) # => Infinity
14.12 to_r, rationalize
Float.to_r(x)
Float.rationalize(x)
Rational に変えます。to_r は 2 進の値をそのまま分数にするので 0.1 は 3602879701896397/36028797018963968 になります。rationalize は元の Float に丸め戻せる範囲で最も簡単な分数を選ぶので 0.1 は 1/10 です。NaN・Infinity は FloatDomainError。Ruby の rationalize(eps) の許容誤差引数は Float には無く、Rational.rationalize(r, eps) が代わりです(Rational)。
p(Float.to_r(0.75)) # => (3/4)
p(Float.to_r(0.1)) # => (3602879701896397/36028797018963968)
p(Float.rationalize(0.1)) # => (1/10)
p(Float.rationalize(0.333)) # => (333/1000)
p(Float.to_r(-1.5)) # => (-3/2)
```ruby error p(Float.to_r(Float.NAN)) # !> FloatDomainError: Float.to_r: NaN
## numerator, denominator
`Float.numerator(x)`
`Float.denominator(x)`
`Float.to_r(x)` の分子・分母(Integer)。`0.1` の分母は `36028797018963968` のように 2 進表現の値になります。Ruby では NaN の `numerator` は NaN そのもの、`denominator` は 1 ですが、Sake では NaN・Infinity は `FloatDomainError` で、結果は常に Integer です。
```ruby
p(Float.numerator(0.75)) # => 3
p(Float.denominator(0.75)) # => 4
p(Float.numerator(1.5)) # => 3
p(Float.denominator(0.1)) # => 36028797018963968
```ruby error p(Float.numerator(Float.NAN)) # !> FloatDomainError: Float.numerator: NaN
## angle, arg, phase
`Float.angle(x)`
`Float.arg(x)`
`Float.phase(x)`
複素平面での偏角。正の数(と `0.0`)では Integer の `0`、負の数(と `-0.0`)では Float の `π` を返すので、型は `Integer | Float` です。NaN では NaN。3 つは同じ働きです。
```ruby
p(Float.angle(1.5)) # => 0
p(Float.arg(-1.5)) # => 3.141592653589793
p(Float.phase(-0.0)) # => 3.141592653589793
p(Float.angle(Float.NAN)) # => NaN
14.13 to_f, to_s
Float.to_f(x)
Float.to_s(x)
to_f は x 自身を返します(型が Integer | Float の値をまとめて Float にするには Arithmetic.to_f(x))。to_s は Ruby の表記で、整数値でも .0 が付き、おおよそ 1e16 以上と 1e-4 未満は 1.0e+20 のような指数表記になります。Infinity、-Infinity、NaN もそのまま文字にします。puts(1.5) も同じ表記です。
p(Float.to_f(1.5)) # => 1.5
p(Float.to_s(1.0)) # => "1.0"
p(Float.to_s(0.1 + 0.2)) # => "0.30000000000000004"
p(Float.to_s(1e20)) # => "1.0e+20"
p(Float.to_s(1e-5)) # => "1.0e-05"
p(Float.to_s(Float.INFINITY)) # => "Infinity"
p(Float.to_s(-0.0)) # => "-0.0"
15. Rational
Rational は分子と分母が Integer の有理数で、常に既約で表されます。リテラルは Ruby と同じ 2r、1/3r(1 / 3r の割り算)、0.5r(1/2)で、Rational(1, 3)・Rational("1/3")・Rational(0.5)(Ruby と同じく (1/2))は Kernel の操作です(Float からは Float.to_r・Float.rationalize でも変換できます。値と型)。Rational の値を p で表示すると (1/3) の形になります。
Rational に使える演算子は +、-、*、/、%、** と ==、!=、<、<=、>、>=、<=> です。右側は Integer、Float、Rational、Complex のどれでもよく、結果の型は閉じた表で決まります: Integer との演算は Rational、Float との演算は Float、Complex との演算は Complex(演算子と添字)。1 + 1r/2 のように Integer が左でも Rational になります。Integer の負の冪 2 ** -1 は Ruby なら Rational ですが Sake では ArgumentError で、2r ** -1 と書きます。ここに挙げた Rational.+(x, y) などはその演算子を関数の形で呼ぶものです。
Ruby と同じく、Rational を 0 で割ると ZeroDivisionError です(Float と違い、無限大にはなりません)。Ruby との違いは、round・floor・ceil・truncate に桁数引数が無いこと(Arithmetic.round(r, n) を使います。Arithmetic)、quo と rationalize の引数が Float を取らないことです。
15.1 Rational[]
Rational[*Any]
Rational の Array を作ります(Rational そのものではありません)。要素はすべて Rational でなければならず、Array.push などの書き込みも毎回検査されます。Integer は Rational に変換されず、静的に type の問題、実行時は TypeError です。空の Rational の Array は Rational[]。
rs = Rational[1r/2, 2r]
Array.push(rs, Rational(1, 3))
p(rs) # => [(1/2), (2/1), (1/3)]
p(Array.size(Rational[])) # => 0
```ruby error Array.push(Rational[], 1) # !> Array.push: an element must be Rational, but is Integer
## +, -, *, /, %, **
`Rational.+(x, Any)`
`Rational.-(x, Any)`
`Rational.*(x, Any)`
`Rational./(x, Any)`
`Rational.%(x, Any)`
`Rational.**(x, Any)`
`x + y` などの関数形で、左側が Rational のものです。右側 `y` は Integer、Float、Rational、Complex のどれか。Integer・Rational との結果は Rational、Float との結果は Float、Complex との結果は Complex です。それ以外の型は静的に `type` の問題、実行時は `TypeError`。`/` と `%` の右側が 0(`0`・`0r`)なら `ZeroDivisionError`(`0.0` なら Float の規則で `Infinity`)。`%` は Ruby の `Rational#%` で、結果の符号は右側に従います。`**` は Integer の指数なら Rational(負でもよく、`2r ** -1` は `(1/2)`)、Float の指数なら Float です。**Rational の指数**では、整数値なら Rational(`4r ** 2r` は `(16/1)`)、そうでなければ Float(`4r ** (1r/2)` は `2.0`)になるので、検査器はその結果を `Rational | Float` とします。どちらかとして使う前に `case`/`in` で絞ってください(`Rational.numerator(4r ** 2r)` は `type` の問題)。負の底に分数の指数は `Math::DomainError` です(Ruby は Complex を返す)。
```ruby
p(1r/3 + 1r/6) # => (1/2)
p(Rational.+(1r/3, 1)) # => (4/3)
p(Rational.+(1r/3, 0.5)) # => 0.8333333333333333
p(Rational.+(1r/3, Complex(1, 1))) # => ((4/3)+1i)
p(Rational.-(1r, 1r/3)) # => (2/3)
p(Rational.*(2r/3, 3r/4)) # => (1/2)
p(Rational./(1r/3, 2)) # => (1/6)
p(Rational.%(7r/3, 1r/2)) # => (1/3)
p(Rational.**(2r, -1)) # => (1/2)
p(Rational.**(1r/2, 3)) # => (1/8)
p(Rational.**(4r, 2r)) # => (16/1)
p(Rational.**(2r, 1r/2)) # => 1.4142135623730951
x = 4r ** (1r/2)
case x
in Rational then p(Rational.numerator(x))
in Float then p(x) # => 2.0
end
```ruby error p(Rational./(1r, 0)) # !> ZeroDivisionError: Rational./: divided by 0
```ruby error
p(Rational.numerator(4r ** 2r)) # !> Rational.numerator: argument 1 must be Rational, but can be Float
```ruby error p((-8r) ** (1r/2)) # !> Math::DomainError: Arithmetic.**: -8/1 ** 1/2 is not a real number (a negative base with a fractional exponent)
## ==, !=
`Rational.==(x, Any)`
`Rational.!=(x, Any)`
`x == y` の関数形。Rational は Integer・Float・Complex と値で比べられ、`1r/2 == 0.5` も `2r == 2` も true です。別の型の値とは決して等しくなく、関数形の `Rational.==(x, y)` も演算子と同じく、右側が他のどんな型でも false(`!=` は true)を返し、エラーにはなりません。
```ruby
p(Rational.==(1r/2, 0.5)) # => true
p(Rational.==(2r, 2)) # => true
p(Rational.!=(1r/2, 1r/3)) # => true
p(1r == nil) # => false
p(Rational.==(1r/2, "x")) # => false
p(Rational.!=(1r/2, "x")) # => true
15.2 <, <=, >, >=
Rational.<(x, Any)
Rational.<=(x, Any)
Rational.>(x, Any)
Rational.>=(x, Any)
x < y などの関数形。右側は Integer、Float、Rational のどれか(Complex に順序は無く、静的に type の問題)。Rational 同士・Integer との比較は正確で、Float とは Float の値で比べます。
p(1r/3 < 1r/2) # => true
p(Rational.<(1r/3, 0.5)) # => true
p(Rational.<=(1r/2, 0.5)) # => true
p(Rational.>(1r/3, 0.3)) # => true
p(Rational.>=(1r, 1)) # => true
```ruby error p(Rational.<(1r, Complex(1, 0))) # !> the operands are (Rational, Complex), which the left operand’s type does not support
## <=>
`Rational.<=>(x, Any)`
`x <=> y` の関数形で、-1、0、1 を返します。右側が Integer か Rational なら結果は常に Integer です。右側が Float のときは NaN なら **nil** になるので型は `Integer | nil` で、`--strict`(レベル 2 の `nil`)は結果をそのまま算術に使うことを報告します。
```ruby
p(Rational.<=>(1r/3, 1r/2)) # => -1
p(Rational.<=>(1r/2, 1r/2)) # => 0
p(Rational.<=>(1r, 1r/2)) # => 1
p(Rational.<=>(1r/2, Float.NAN)) # => nil
c = Rational.<=>(1r/3, 1r/2)
p(c + 1) # => 0
```ruby error c = Rational.<=>(1r/3, 0.5) p(c + 1) # !> the operands may be nil
## abs, magnitude
`Rational.abs(x)`
`Rational.magnitude(x)`
絶対値(Rational)。2 つは同じ働きです。
```ruby
p(Rational.abs(-1r/3)) # => (1/3)
p(Rational.magnitude(-1r/3)) # => (1/3)
p(Rational.abs(-5r/2)) # => (5/2)
15.3 negative?, positive?, zero?
Rational.negative?(x)
Rational.positive?(x)
Rational.zero?(x)
x < 0、x > 0、x == 0 を true/false で返します。
p(Rational.positive?(1r/3)) # => true
p(Rational.positive?(0r)) # => false
p(Rational.negative?(-1r/3)) # => true
p(Rational.zero?(0r)) # => true
p(Rational.zero?(1r/3)) # => false
15.4 numerator, denominator
Rational.numerator(x)
Rational.denominator(x)
既約分数にしたときの分子・分母(Integer)。符号は分子が持ち、分母は常に正です。整数値の分母は 1。
p(Rational.numerator(-6r/4)) # => -3
p(Rational.denominator(-6r/4)) # => 2
p(Rational.numerator(1r/2 + 1r/2)) # => 1
p(Rational.denominator(1r/2 + 1r/2)) # => 1
15.5 to_i, truncate, floor, ceil, round
Rational.to_i(x)
Rational.truncate(x)
Rational.floor(x)
Rational.ceil(x)
Rational.round(x)
Integer にします。to_i と truncate は 0 に近い方へ、floor は下へ、ceil は上へ、round は最も近い Integer へ(ちょうど .5 は 0 から遠い方へ: Rational.round(5r/2) は 3、-5r/2 は -3)。Ruby の round(digits) などの桁数引数はこれらには無く、桁を指定するには Arithmetic.round(x, n) を使います(結果は Rational)。
p(Rational.to_i(-7r/2)) # => -3
p(Rational.truncate(7r/3)) # => 2
p(Rational.floor(-7r/2)) # => -4
p(Rational.ceil(7r/2)) # => 4
p(Rational.round(7r/2)) # => 4
p(Rational.round(5r/2)) # => 3
p(Rational.round(-1r/2)) # => -1
```ruby error p(Rational.round(7r/3, 1)) # !> wrong number of arguments for Rational.round (given 2, expected 1)
## fdiv
`Rational.fdiv(x, Integer|Float|Rational)`
浮動小数点の割り算 `x / y` の結果を **Float** で返します。0 で割ると `Infinity`(例外は出ません)。
```ruby
p(Rational.fdiv(1r/3, 2)) # => 0.16666666666666666
p(Rational.fdiv(2r/3, 1r/3)) # => 2.0
p(Rational.fdiv(1r, 0)) # => Infinity
15.6 quo
Rational.quo(x, Integer|Rational)
正確な割り算 x / y(Rational)。y は Integer か Rational で、Float は静的に type の問題です(Float で割るには / を使います)。0 で割ると ZeroDivisionError。
p(Rational.quo(1r/3, 2)) # => (1/6)
p(Rational.quo(1r/3, 2r/3)) # => (1/2)
```ruby error p(Rational.quo(1r/3, 0)) # !> ZeroDivisionError: Rational.quo: divided by 0
## rationalize
`Rational.rationalize(x, [Rational])`
許容誤差 `eps`(Rational)の範囲 `x - eps .. x + eps` で最も簡単な分数を返します(Ruby の `Rational#rationalize`)。`eps` を省くと x 自身を返します。`eps` は Rational でなければならず、Float は静的に `type` の問題です。
```ruby
pi = 3141592653589793r/1000000000000000
p(Rational.rationalize(pi, 1r/100)) # => (22/7)
p(Rational.rationalize(pi, 1r/1000000)) # => (355/113)
p(Rational.rationalize(1r/3)) # => (1/3)
```ruby error p(Rational.rationalize(1r/3, 0.1)) # !> Rational.rationalize: argument 2 must be Rational, but is Float
## to_f
`Rational.to_f(x)`
最も近い Float にします。
```ruby
p(Rational.to_f(1r/4)) # => 0.25
p(Rational.to_f(2r/3)) # => 0.6666666666666666
15.7 to_r
Rational.to_r(x)
x 自身を返します(型が Integer | Rational の値をまとめて扱うときの形合わせ用)。
p(Rational.to_r(1r/3)) # => (1/3)
15.8 to_s
Rational.to_s(x)
"分子/分母" の形の String(Ruby と同じ)。整数値でも "2/1" と分母が付きます。p の表示 (1/3) とは括弧の分だけ違います。
p(Rational.to_s(1r/3)) # => "1/3"
p(Rational.to_s(2r)) # => "2/1"
p(Rational.to_s(-6r/4)) # => "-3/2"
16. Complex
Complex は実部と虚部を持つ複素数で、各部は Integer、Float、Rational のどれかです。リテラルは Ruby と同じ虚数単位の 2i、3.5i と、それを使った 1 + 2i で、Complex(1, 2)(虚部省略で Complex(1.5))は Kernel の操作です(引数は実数のみ。値と型)。p で表示すると (1+2i) の形になります。
Complex に使える演算子は +、-、*、/、** と ==、!= です。Complex には順序が無いので < などの比較は無く、% もありません(静的に type の問題)。右側は Integer、Float、Rational、Complex のどれでもよく、結果は常に Complex です。Integer・Float・Rational が左側でも、右側が Complex なら結果は Complex です(演算子と添字)。ここに挙げた Complex.+(x, y) などはその演算子を関数の形で呼ぶものです。
各部の型は値の中に残り、Complex(1, 2) / 2 は ((1/2)+1i) と Rational の実部になります(Ruby と同じ)。実部・虚部を取り出す real・imag の結果の型が Integer | Float | Rational なのはそのためで、Integer として使うには case/in などで型を確かめます。Ruby との違いは、各部に Complex を渡せないことです。
16.1 Complex[]
Complex[*Any]
Complex の Array を作ります(Complex そのものではありません)。要素はすべて Complex でなければならず、Array.push などの書き込みも毎回検査されます。実数は Complex に変換されず、静的に type の問題、実行時は TypeError です。空の Complex の Array は Complex[]。
cs = Complex[Complex(1, 2), 3i]
Array.push(cs, Complex(0, 0))
p(cs) # => [(1+2i), (0+3i), (0+0i)]
p(Array.size(Complex[])) # => 0
```ruby error Array.push(Complex[], 1) # !> Array.push: an element must be Complex, but is Integer
## +, -, *, /, **
`Complex.+(x, Any)`
`Complex.-(x, Any)`
`Complex.*(x, Any)`
`Complex./(x, Any)`
`Complex.**(x, Any)`
`x + y` などの関数形で、左側が Complex のものです。右側 `y` は Integer、Float、Rational、Complex のどれかで、結果は Complex。他の型は静的に `type` の問題、実行時は `TypeError` です。`/` は各部の型に従って正確に計算し、Integer 同士の部は Rational になります。各部がすべて正確(Integer・Rational)な Complex を 0 や `Complex(0, 0)` で割ると `ZeroDivisionError`、Float の部を含んでいても Integer の 0 で割れば同じく `ZeroDivisionError` ですが、`0.0` で割ると Float の規則で `Infinity` の部になります(Ruby と同じ)。`**` は整数乗なら正確、それ以外は Float の部になります。`%` は Complex にありません。
```ruby
c = Complex(1, 2)
p(c + Complex(3, 4)) # => (4+6i)
p(Complex.+(c, 1.5)) # => (2.5+2i)
p(Complex.-(Complex(5, 3), 2)) # => (3+3i)
p(Complex.*(c, c)) # => (-3+4i)
p(Complex.*(Complex(1, 1), Complex(1, -1))) # => (2+0i)
p(Complex./(c, 2)) # => ((1/2)+1i)
p(Complex./(Complex(1.0, 2), 2)) # => (0.5+1i)
p(Complex./(Complex(1, 2), 0.0)) # => (Infinity+Infinity*i)
p(Complex.**(Complex(0, 1), 2)) # => (-1+0i)
p(Complex.**(c, -1)) # => ((1/5)-(2/5)*i)
p(1.5 * c) # => (1.5+3.0i)
```ruby error p(Complex./(Complex(1, 2), 0)) # !> ZeroDivisionError: Complex./: divided by 0
## ==, !=
`Complex.==(x, Any)`
`Complex.!=(x, Any)`
`x == y` の関数形。実部と虚部がそれぞれ `==` なら true で、`Complex(1, 0) == 1` や `Complex(1.0, 0) == 1` も true です。別の型の値とは決して等しくなく(`Complex(1, 2) == "x"` は false)、関数形の `Complex.==(x, y)` も演算子と同じく、右側が他のどんな型でも false(`!=` は true)を返し、エラーにはなりません。
```ruby
p(Complex.==(Complex(1, 2), Complex(1, 2))) # => true
p(Complex.==(Complex(1, 0), 1)) # => true
p(Complex.==(Complex(2, 0), 2.0)) # => true
p(Complex.!=(Complex(1, 2), Complex(2, 1))) # => true
p(Complex(1, 2) == "x") # => false
p(Complex.==(Complex(1, 2), "x")) # => false
p(Complex.==(Complex(1, 2), nil)) # => false
16.2 real, imag, imaginary
Complex.real(x)
Complex.imag(x)
Complex.imaginary(x)
実部・虚部を返します。型は作ったときの部の型(Integer | Float | Rational)です。imag と imaginary は同じ働きです。
c = Complex(1, 2.5)
p(Complex.real(c)) # => 1
p(Complex.imag(c)) # => 2.5
p(Complex.imaginary(Complex(3, 4))) # => 4
p(Complex.real(Complex(1r/2, 1))) # => (1/2)
r = Complex.real(Complex(1, 2))
p(r + 1) # => 2
16.3 rect, rectangular
Complex.rect(x)
Complex.rectangular(x)
実部と虚部の Tuple [re, im](各位置の型は Integer | Float | Rational)。多重代入で両方を受け取れます。2 つは同じ働きです。
p(Complex.rect(Complex(1, 2))) # => [1, 2]
p(Complex.rectangular(Complex(1.5, 2r))) # => [1.5, (2/1)]
re, im = Complex.rectangular(Complex(1, 2))
p(re + im) # => 3
16.4 polar
Complex.polar(x)
極形式の Tuple [r, θ]。r は abs とまったく同じ絶対値(Integer | Float。Rational の絶対値は Float になり、Complex.polar(Complex(3r, 0)) は [3.0, 0.0])、θ は偏角(arg と同じ Float。実軸上の値では 0.0・π)です。
p(Complex.polar(Complex(3, 4))) # => [5.0, 0.9272952180016122]
p(Complex.polar(Complex(0, 2))) # => [2, 1.5707963267948966]
p(Complex.polar(Complex(-2, 0))) # => [2, 3.141592653589793]
p(Complex.polar(Complex(3r, 0))) # => [3.0, 0.0]
r, th = Complex.polar(Complex(3, 4))
p(r) # => 5.0
16.5 abs, magnitude
Complex.abs(x)
Complex.magnitude(x)
絶対値 sqrt(re² + im²)。普通は Float ですが、Ruby と同じく片方の部が正確な 0 ならもう一方の部の絶対値をそのまま返すので、Complex(3, 0) では Integer の 3 です。Ruby と違い、Rational の絶対値は Float になります(Complex.abs(Complex(3r, 0)) は 3.0)。型はちょうど Integer | Float です。2 つは同じ働きです。
p(Complex.abs(Complex(3, 4))) # => 5.0
p(Complex.abs(Complex(3, 0))) # => 3
p(Complex.abs(Complex(0, -2))) # => 2
p(Complex.abs(Complex(3r, 0))) # => 3.0
p(Complex.magnitude(Complex(1, 1))) # => 1.4142135623730951
a = Complex.abs(Complex(3, 4))
p(a + 1) # => 6.0
16.6 abs2
Complex.abs2(x)
絶対値の 2 乗 re² + im²。平方根を取らないので正確で、型は各部に従う Integer | Float | Rational です。
p(Complex.abs2(Complex(3, 4))) # => 25
p(Complex.abs2(Complex(1.5, 0))) # => 2.25
p(Complex.abs2(Complex(1r/2, 1r/2))) # => (1/2)
16.7 arg, angle, phase
Complex.arg(x)
Complex.angle(x)
Complex.phase(x)
偏角(Float、-π から π)。Math.atan2(im, re) と同じで、Complex(0, 0) では 0.0、負の実軸上では π です。3 つは同じ働きです。
p(Complex.arg(Complex(1, 1))) # => 0.7853981633974483
p(Complex.angle(Complex(0, 1))) # => 1.5707963267948966
p(Complex.phase(Complex(-1, 0))) # => 3.141592653589793
p(Complex.arg(Complex(0, -1))) # => -1.5707963267948966
p(Complex.arg(Complex(0, 0))) # => 0.0
16.8 conj, conjugate
Complex.conj(x)
Complex.conjugate(x)
共役複素数(虚部の符号を反転した Complex)。2 つは同じ働きです。
p(Complex.conj(Complex(1, 2))) # => (1-2i)
p(Complex.conjugate(Complex(1.5, -2))) # => (1.5+2i)
p(Complex.conjugate(Complex(3, 0))) # => (3+0i)
16.9 real?
Complex.real?(x)
常に false を返します(Ruby の Complex#real? と同じく、Complex という型は虚部が 0 でも実数ではないとみなします)。実数かどうかを値で知るには Complex.imag(x) == 0 を使います。
p(Complex.real?(Complex(1, 2))) # => false
p(Complex.real?(Complex(1, 0))) # => false
p(Complex.imag(Complex(1, 0)) == 0) # => true
16.10 finite?, infinite?
Complex.finite?(x)
Complex.infinite?(x)
finite? は実部と虚部がともに有限(NaN でも無限大でもない)のとき true。infinite? は Ruby と同じく、どちらかの部が無限大なら 1、そうでなければ nil(NaN を含む)を返すので型は Integer | nil です。真偽として使うのが普通で、値を算術に使うには先に nil を確かめます(--strict の nil が報告します)。
p(Complex.finite?(Complex(1, 2))) # => true
p(Complex.finite?(Complex(Float.INFINITY, 2))) # => false
p(Complex.finite?(Complex(Float.NAN, 2))) # => false
p(Complex.infinite?(Complex(1, 2))) # => nil
p(Complex.infinite?(Complex(1, -Float.INFINITY))) # => 1
p(Complex.infinite?(Complex(Float.NAN, 2))) # => nil
```ruby error x = Complex.infinite?(Complex(1, 2)) p(x + 1) # !> the operands may be nil
## fdiv
`Complex.fdiv(x, Integer|Float|Rational)`
各部を Float にしてから実数 `y` で割った Complex(各部は Float)。`y` に Complex は渡せません(静的に `type` の問題。Complex で割るには `/` か `quo`)。0 で割ると `Infinity` の部になり、例外は出ません。
```ruby
p(Complex.fdiv(Complex(3, 4), 2)) # => (1.5+2.0i)
p(Complex.fdiv(Complex(1, 2), 2r)) # => (0.5+1.0i)
p(Complex.fdiv(Complex(1, 2), 0)) # => (Infinity+Infinity*i)
```ruby error p(Complex.fdiv(Complex(1, 2), Complex(1, 1))) # !> Complex.fdiv: argument 2 must be Integer|Float|Rational, but is Complex
## quo
`Complex.quo(x, Integer|Float|Rational|Complex)`
割り算 `x / y`(Complex)。`/` と同じで、Integer 同士の部は Rational になります。正確な 0 で割ると `ZeroDivisionError`。
```ruby
p(Complex.quo(Complex(3, 4), 2)) # => ((3/2)+2i)
p(Complex.quo(Complex(3, 4), 2.0)) # => (1.5+2.0i)
p(Complex.quo(Complex(3, 4), Complex(1, 1))) # => ((7/2)+(1/2)*i)
```ruby error p(Complex.quo(Complex(1, 2), 0)) # !> ZeroDivisionError: Complex.quo: divided by 0
## numerator, denominator
`Complex.numerator(x)`
`Complex.denominator(x)`
`denominator` は実部と虚部の分母の最小公倍数(Integer)、`numerator` は `x * denominator`(各部が整数になった Complex)です。Integer の部の分母は 1。
```ruby
c = Complex(1r/2, 1r/4)
p(Complex.denominator(c)) # => 4
p(Complex.numerator(c)) # => (2+1i)
p(Complex.denominator(Complex(3, 4))) # => 1
p(Complex.numerator(Complex(3, 4))) # => (3+4i)
16.11 to_i, to_f, to_r, rationalize
Complex.to_i(x)
Complex.to_f(x)
Complex.to_r(x)
Complex.rationalize(x, [Rational])
虚部が正確な 0(Integer の 0 か 0r)のときだけ、実部を Integer・Float・Rational にします(rationalize は実部の rationalize。許容誤差 eps は Rational)。虚部が 0 でないとき、または 0.0(Float の 0)のときは RangeError(メッセージは Ruby のもの: can't convert 1+2i into Float)を投げ、rescue できます。
p(Complex.to_i(Complex(1.9, 0))) # => 1
p(Complex.to_f(Complex(3, 0))) # => 3.0
p(Complex.to_r(Complex(1.5, 0))) # => (3/2)
p(Complex.rationalize(Complex(0.333, 0))) # => (333/1000)
p(Complex.rationalize(Complex(0.333, 0), 1r/100)) # => (1/3)
begin
Complex.to_f(Complex(1, 2))
rescue RangeError => e
p(Exception.message(e)) # => "can't convert 1+2i into Float"
end
```ruby error p(Complex.to_f(Complex(1, 0.0))) # !> RangeError: Complex.to_f: can’t convert 1+0.0i into Float
## to_c
`Complex.to_c(x)`
x 自身を返します(実数を Complex にするのは Kernel の `Complex(x)`)。
```ruby
p(Complex.to_c(Complex(3, 4))) # => (3+4i)
16.12 to_s
Complex.to_s(x)
"re+imi" の形の String(Ruby と同じ)。p の表示 (3+4i) から括弧を外したもので、Rational の部は 1/2+3i のように括弧なしです。
p(Complex.to_s(Complex(3, 4))) # => "3+4i"
p(Complex.to_s(Complex(-1, -1))) # => "-1-1i"
p(Complex.to_s(Complex(0.5, 2))) # => "0.5+2i"
p(Complex.to_s(Complex(1r/2, 3))) # => "1/2+3i"
17. Arithmetic
Arithmetic は算術演算子 +、-、*、/、%、** と単項の -x、+x が属するモジュールです。a + b は Arithmetic.+(a, b) の略記で、左側の値の型(Integer、Float、Rational、Complex、String、Time、Set、または include Arithmetic したクラス)の + に振り分けられます(演算子と添字、プログラムの構造)。
この章に挙げる 8 つの操作は、演算子ではなく、どの実数(Integer、Float、Rational)にも使える丸めと変換です。Sake の操作は普通 Float.round(f) のように型を名乗りますが、値の型が Integer | Float のように定まらないとき(Array.sum の結果、JSON から読んだ数など)に、Ruby の x.round と同じく値の型を見て振り分けるのがこれらです。結果の型は引数の型に従います: 桁数なしの round・floor・ceil・truncate と to_i は Integer、桁数付きの丸めと abs は引数と同じ型(引数の型が和なら結果も和)、to_f は Float、zero? は true/false。検査器は引数の各型についてこの規則を当てます。
引数が Complex や String なら静的に type の問題です(argument 1 must be Integer|Float|Rational, but is Complex)。NaN・Infinity を Integer にしようとすると FloatDomainError を投げ、rescue FloatDomainError => e で受けられます。
17.1 round, floor, ceil, truncate
Arithmetic.round(Integer|Float|Rational, [Integer], [half: Symbol])
Arithmetic.floor(Integer|Float|Rational, [Integer])
Arithmetic.ceil(Integer|Float|Rational, [Integer])
Arithmetic.truncate(Integer|Float|Rational, [Integer])
Ruby の x.round(n)・x.floor(n)・x.ceil(n)・x.truncate(n) です(結果の型に 1 つ違いがあります)。round は最も近い値へ(.5 は既定では 0 から遠い方へ)、floor は下へ、ceil は上へ、truncate は 0 に近い方へ丸めます。
- 桁数
nを省くと結果は Integer です(引数が Integer でもそのまま Integer)。 - 桁数を付けると結果は 引数と同じ型: Float には Float、Rational には Rational、Integer には Integer(正の桁数では値はそのまま)。負の桁数は 10 の冪に丸めます。Ruby と違い、0 以下の桁数でもこの規則どおりです:
Arithmetic.round(1234.5, -2)は1200.0(Ruby の1234.5.round(-2)は Integer の 1200)、Arithmetic.round(5r/2, -1)は(0/1)。つまり結果の型は引数の型と桁数の有無だけで決まります。 roundはFloat.roundと同じキーワードhalf:を、どの型の引数にも取ります::up(既定。0 から遠い方へ)、:even(偶数側へ)、:down(0 に近い方へ)。他の Symbol はArgumentError(invalid rounding mode: foo)です。floor・ceil・truncateはhalf:を取りません(静的なエラー)。- NaN・Infinity は桁数なしなら
FloatDomainError(rescueできます)、桁数付きならそのまま返します。
p(Arithmetic.round(1.5)) # => 2
p(Arithmetic.round(7r/2)) # => 4
p(Arithmetic.round(3)) # => 3
p(Arithmetic.round(1.2345, 2)) # => 1.23
p(Arithmetic.round(7r/3, 2)) # => (233/100)
p(Arithmetic.round(1234, -2)) # => 1200
p(Arithmetic.round(1234.5, -2)) # => 1200.0
p(Arithmetic.round(5r/2, -1)) # => (0/1)
p(Arithmetic.round(2.5, half: :even)) # => 2
p(Arithmetic.round(5r/2, half: :even)) # => 2
p(Arithmetic.round(25, -1, half: :even)) # => 20
p(Arithmetic.round(1.25, 1, half: :even)) # => 1.2
p(Arithmetic.floor(-1.5)) # => -2
p(Arithmetic.floor(1.567, 1)) # => 1.5
p(Arithmetic.floor(1234.5, -2)) # => 1200.0
p(Arithmetic.ceil(7r/2)) # => 4
p(Arithmetic.ceil(1234, -2)) # => 1300
p(Arithmetic.truncate(-7r/2)) # => -3
p(Arithmetic.truncate(1.999, 2)) # => 1.99
p(Arithmetic.round(Float.NAN, 2)) # => NaN
begin
Arithmetic.round(Float.NAN)
rescue FloatDomainError => e
p(Exception.message(e)) # => "NaN"
end
型が定まらない値にも一つの操作で使えます:
def tenth(x)
Arithmetic.round(x, 1)
end
p(tenth(1.25)) # => 1.3
p(tenth(3)) # => 3
p(tenth(1r/3)) # => (3/10)
```ruby error p(Arithmetic.round(“1.5”)) # !> Arithmetic.round: argument 1 must be Integer|Float|Rational, but is String
```ruby error
p(Arithmetic.round(2.5, half: :foo)) # !> ArgumentError: Arithmetic.round: invalid rounding mode: foo
```ruby error p(Arithmetic.round(Float.INFINITY)) # !> FloatDomainError: Arithmetic.round: Infinity
## abs
`Arithmetic.abs(Integer|Float|Rational)`
絶対値。結果は引数と同じ型です(Ruby の `x.abs`)。
```ruby
p(Arithmetic.abs(-3)) # => 3
p(Arithmetic.abs(-1.5)) # => 1.5
p(Arithmetic.abs(-1r/3)) # => (1/3)
```ruby error p(Arithmetic.abs(Complex(3, 4))) # !> Arithmetic.abs: argument 1 must be Integer|Float|Rational, but is Complex
## to_i, to_f
`Arithmetic.to_i(Integer|Float|Rational)`
`Arithmetic.to_f(Integer|Float|Rational)`
`to_i` は 0 に近い方へ切り捨てた Integer、`to_f` は最も近い Float を返します。`Integer | Float` の値を Float に揃えるときの `to_f` が代表的な使い方です(検査器は `Float.floor(x)` に `Integer | Float` を渡すと、`Arithmetic.floor(x)` を使うよう助言します)。`to_i` の NaN・Infinity は `FloatDomainError` で、`rescue` できます。
```ruby
p(Arithmetic.to_i(1.9)) # => 1
p(Arithmetic.to_i(7r/2)) # => 3
p(Arithmetic.to_i(7)) # => 7
p(Arithmetic.to_f(3)) # => 3.0
p(Arithmetic.to_f(1r/4)) # => 0.25
p(Arithmetic.to_f(1.5)) # => 1.5
```ruby error p(Arithmetic.to_i(Float.NAN)) # !> FloatDomainError: Arithmetic.to_i: NaN
## zero?
`Arithmetic.zero?(Integer|Float|Rational)`
`x == 0` のとき true(`0.0` と `-0.0` も true)。
```ruby
p(Arithmetic.zero?(0)) # => true
p(Arithmetic.zero?(0.0)) # => true
p(Arithmetic.zero?(0r)) # => true
p(Arithmetic.zero?(1)) # => false
18. Math
Math は Ruby の Math モジュールにあたる初等関数の集まりです。型ではなく名前空間で、Math の値はありません。どの関数も実数(Integer、Float、Rational)を取り、結果は常に Float です(Math.sqrt(4) は 2.0。Complex は渡せません)。atan2 と hypot の 2 引数の関数と ldexp の指数だけは Rational を取らず、Integer か Float です。
Ruby の Math::PI・Math::E は Sake では引数なしの操作 Math.PI・Math.E です(Sake に値の定数はありません)。Math::PI と Ruby の形で書いても同じ操作として読みます。Math.log(x, base) は Ruby と同じく底を取れます(Integer か Float)。
定義域の外の引数(sqrt(-1)、log(-1)、acos(2) など)は Ruby と同じく Math::DomainError を投げ、rescue Math::DomainError => e で受けられます(例外とエラー)。NaN を渡せば NaN が返り、無限大は IEEE の規則に従います(log(0) は -Infinity、exp(1000) は Infinity。例外は出ません)。
18.1 PI, E
Math.PI()
Math.E()
円周率 π と自然対数の底 e(Float)。Math.PI または Math.PI() と書きます。
p(Math.PI) # => 3.141592653589793
p(Math.E) # => 2.718281828459045
p(Math.cos(Math.PI)) # => -1.0
p(Math.exp(1) == Math.E) # => true
18.2 sqrt, cbrt
Math.sqrt(Integer|Float|Rational)
Math.cbrt(Integer|Float|Rational)
平方根・立方根(Float)。sqrt の負の引数は Math::DomainError。cbrt は負の数も取れます(cbrt(-8) は -2.0)。Integer の正確な平方根が欲しいときは Integer.sqrt(n) です。
p(Math.sqrt(4)) # => 2.0
p(Math.sqrt(2.0)) # => 1.4142135623730951
p(Math.sqrt(9r/4)) # => 1.5
p(Math.cbrt(-8)) # => -2.0
p(Math.sqrt(Float.INFINITY)) # => Infinity
begin
Math.sqrt(-1)
rescue Math::DomainError => e
p(Exception.message(e)) # => "Numerical argument is out of domain - sqrt"
end
```ruby error p(Math.sqrt(-1)) # !> Math::DomainError: Math.sqrt: Numerical argument is out of domain - sqrt
## sin, cos, tan
`Math.sin(Integer|Float|Rational)`
`Math.cos(Integer|Float|Rational)`
`Math.tan(Integer|Float|Rational)`
三角関数。引数はラジアンです。結果は Float で、`sin(π)` のような値は正確な 0 にはならず、丸め誤差を含みます。
```ruby
p(Math.sin(0)) # => 0.0
p(Math.cos(0)) # => 1.0
p(Math.sin(Math.PI / 2)) # => 1.0
p(Math.cos(Math.PI / 3)) # => 0.5000000000000001
p(Math.tan(Math.PI / 4)) # => 0.9999999999999999
18.3 asin, acos, atan
Math.asin(Integer|Float|Rational)
Math.acos(Integer|Float|Rational)
Math.atan(Integer|Float|Rational)
逆三角関数(ラジアンの Float)。asin・acos の引数は -1 から 1 の間でなければならず、外れると Math::DomainError。atan はどの実数も取り、-π/2 から π/2 を返します。
p(Math.asin(1)) # => 1.5707963267948966
p(Math.acos(1)) # => 0.0
p(Math.asin(0.5)) # => 0.5235987755982989
p(Math.atan(1) * 4 == Math.PI) # => true
p(Math.atan(Float.INFINITY)) # => 1.5707963267948966
```ruby error p(Math.acos(2)) # !> Math::DomainError: Math.acos: Numerical argument is out of domain - acos
## atan2
`Math.atan2(Integer|Float, Integer|Float)`
`atan2(y, x)`: 点 `(x, y)` の偏角を `-π` から `π` のラジアン(Float)で返します。`atan(y / x)` と違い、象限を区別し、`x` が 0 でも使えます。引数は Integer か Float で、Rational は静的に `type` の問題です。
```ruby
p(Math.atan2(1, 1)) # => 0.7853981633974483
p(Math.atan2(1.0, -1.0)) # => 2.356194490192345
p(Math.atan2(1, 0)) # => 1.5707963267948966
p(Math.atan2(0, -1)) # => 3.141592653589793
p(Math.atan2(-1, -1)) # => -2.356194490192345
```ruby error p(Math.atan2(1r, 1)) # !> Math.atan2: argument 1 must be Integer|Float, but is Rational
## hypot
`Math.hypot(Integer|Float, Integer|Float)`
`sqrt(x² + y²)`(Float)。中間の 2 乗で溢れないように計算します。引数は Integer か Float で、Rational は静的に `type` の問題です。
```ruby
p(Math.hypot(3, 4)) # => 5.0
p(Math.hypot(5, 12)) # => 13.0
p(Math.hypot(1.0, 1.0)) # => 1.4142135623730951
```ruby error p(Math.hypot(1r, 1)) # !> Math.hypot: argument 1 must be Integer|Float, but is Rational
## sinh, cosh, tanh
`Math.sinh(Integer|Float|Rational)`
`Math.cosh(Integer|Float|Rational)`
`Math.tanh(Integer|Float|Rational)`
双曲線関数(Float)。
```ruby
p(Math.sinh(0)) # => 0.0
p(Math.cosh(0)) # => 1.0
p(Math.sinh(1)) # => 1.1752011936438014
p(Math.cosh(1)) # => 1.5430806348152437
p(Math.tanh(Float.INFINITY)) # => 1.0
18.4 asinh, acosh, atanh
Math.asinh(Integer|Float|Rational)
Math.acosh(Integer|Float|Rational)
Math.atanh(Integer|Float|Rational)
逆双曲線関数(Float)。acosh の引数は 1 以上、atanh の引数は -1 から 1 の間でなければならず、外れると Math::DomainError。atanh(1) は Infinity、atanh(-1) は -Infinity です。
p(Math.asinh(1)) # => 0.881373587019543
p(Math.acosh(2)) # => 1.3169578969248168
p(Math.atanh(0.5)) # => 0.5493061443340549
p(Math.atanh(1)) # => Infinity
```ruby error p(Math.acosh(0)) # !> Math::DomainError: Math.acosh: Numerical argument is out of domain - acosh
## exp
`Math.exp(Integer|Float|Rational)`
`e` の x 乗(Float)。大きな引数は `Infinity` になります(例外は出ません)。
```ruby
p(Math.exp(0)) # => 1.0
p(Math.exp(2)) # => 7.38905609893065
p(Math.exp(0.5r)) # => 1.6487212707001282
p(Math.exp(1000)) # => Infinity
p(Math.exp(-Float.INFINITY)) # => 0.0
18.5 log, log2, log10
Math.log(Integer|Float|Rational, [Integer|Float])
Math.log2(Integer|Float|Rational)
Math.log10(Integer|Float|Rational)
自然対数・2 を底とする対数・10 を底とする対数(Float)。log(0) は -Infinity、負の引数は Math::DomainError。Math.log(x, base) は Ruby と同じく底 base の対数で、Math.log(8, 2) は 3.0 です。底は Integer か Float(Rational は静的に type の問題)。負の底は Math::DomainError、底 0 は Ruby と同じく -0.0、底 1 は NaN です。大きな Integer も、Float に直せない大きさでも対数を取れます(Ruby と同じ)。
p(Math.log(Math.E)) # => 1.0
p(Math.log(1)) # => 0.0
p(Math.log(0)) # => -Infinity
p(Math.log2(8)) # => 3.0
p(Math.log10(0.001)) # => -3.0
p(Math.log(8r)) # => 2.0794415416798357
p(Math.log(8, 2)) # => 3.0
p(Math.log(100, 10)) # => 2.0
p(Math.log(8r, 2.0)) # => 3.0
p(Math.log(10 ** 400)) # => 921.0340371976183
p(Math.log(10 ** 400, 10)) # => 400.0
```ruby error p(Math.log(8, 2r)) # !> Math.log: argument 2 must be Integer|Float, but is Rational
```ruby error
p(Math.log(8, -2)) # !> Math::DomainError: Math.log: Numerical argument is out of domain - log
18.6 erf, erfc
Math.erf(Integer|Float|Rational)
Math.erfc(Integer|Float|Rational)
誤差関数と補誤差関数 1 - erf(x)(Float)。
p(Math.erf(0)) # => 0.0
p(Math.erf(1)) # => 0.8427007929497149
p(Math.erfc(1)) # => 0.15729920705028513
p(Math.erf(Float.INFINITY)) # => 1.0
18.7 gamma, lgamma
Math.gamma(Integer|Float|Rational)
Math.lgamma(Integer|Float|Rational)
gamma はガンマ関数 Γ(x)(Float。正の整数 n では (n-1)!)。gamma(0) は Infinity、負の整数と -Infinity は Math::DomainError、172 以上は Infinity。lgamma は Tuple [log|Γ(x)|, sign] を返します: 第 1 要素は絶対値の自然対数(Float)、第 2 要素は Γ(x) の符号(Integer の 1 か -1)です。gamma が溢れる大きな x でも使えます。
p(Math.gamma(5)) # => 24.0
p(Math.gamma(0.5)) # => 1.772453850905516
p(Math.gamma(0)) # => Infinity
p(Math.gamma(172)) # => Infinity
p(Math.lgamma(6)) # => [4.787491742782046, 1]
p(Math.lgamma(-0.5)) # => [1.2655121234846454, -1]
v, sign = Math.lgamma(-0.5)
p(sign) # => -1
```ruby error p(Math.gamma(-1)) # !> Math::DomainError: Math.gamma: Numerical argument is out of domain - gamma
## frexp, ldexp
`Math.frexp(Integer|Float|Rational)`
`Math.ldexp(Integer|Float|Rational, Integer)`
`frexp(x)` は x を仮数と指数に分けた Tuple `[fraction, exponent]`(`[Float, Integer]`)を返します。`fraction` の絶対値は 0.5 以上 1 未満で、`x == fraction * 2 ** exponent`。0 では `[0.0, 0]`。`ldexp(fraction, exponent)` はその逆で、`fraction * 2 ** exponent` を Float で返します。指数は Integer でなければならず、Float は静的に `type` の問題です。
```ruby
p(Math.frexp(8.0)) # => [0.5, 4]
p(Math.frexp(0.75)) # => [0.75, 0]
p(Math.frexp(10)) # => [0.625, 4]
p(Math.frexp(0)) # => [0.0, 0]
f, e = Math.frexp(8.0)
p(Math.ldexp(f, e)) # => 8.0
p(Math.ldexp(1.0, -2)) # => 0.25
p(Math.ldexp(3, 2)) # => 12.0
ruby error
p(Math.ldexp(0.5, 4.0)) # !> Math.ldexp: argument 2 must be Integer, but is Float
19. String
String は Ruby の String と同じく、エンコーディングを持つ文字の並びです。リテラルは "abc"、'abc'、"a#{x}"(補間は値を to_s で表示します。値と型)で、リテラルは UTF-8、評価のたびに新しい String を作ります。String は可変です: 名前が ! で終わる操作と concat、append_as_bytes、prepend、insert、replace、clear、setbyte、bytesplice、force_encoding は主語をその場で変え、それ以外の操作は主語に触れず新しい String を返します。プログラムが変えられない String は Hash のキー、Set の要素、Symbol の名前(Symbol.name)、MatchData が持つ String(MatchData.string)、プログラムの引数(ARGV)で、それらをその場で変えようとすると実行時に TypeError です(cannot change this String in place: it is a Hash key, a Set element, a Symbol's name, or a program argument)。
操作はすべて型を付けて書きます: String.upcase(s) であって s.upcase ではありません。String に使える演算子は +(String, String)、*(String, Integer)、%(書式)、==、!=、<、<=、>、>=、<=>(String, String)、=~、!~(String, Regexp)、添字 s[i]、s[i, n]、s[range] です(演算子と添字)。以下の String.+(x, y) などは、その演算子を関数の形で呼ぶものです。s[i] = v はありません: String は添字への書き込みができず(静的に type の問題)、String.sub!、String.insert、String.bytesplice、String.replace で変えます。
多くの操作が「外れ」に nil を返します: index、rindex、byteindex、byterindex、match、getbyte、casecmp、unpack1、slice!、sub!、gsub!、および何も変わらなかったときに nil を返すその場の形(upcase!、strip!、chomp!、tr!、…)です。その結果を検査せずに使うことは --strict レベル 2 で nil の問題になります。添字の形 s[i]、s[i, n]、s[range]、String.slice、String.byteslice の nil だけは「外れの nil」(index-nil)で、レベル 3 でのみ報告されます(概観)。
Ruby との違い: sub と gsub には置換文字列かブロックが要ります。MatchData は m[i] で読みます($~、$1 はありません)。scan、partition、rpartition は Ruby が Array を返すところで Tuple を返します。count、start_with?、end_with?、include? は引数を 1 つだけ取ります(chomp、delete、squeeze はそれぞれの ! 形と同じもの、つまり区切りや複数の集合を取れます)。反復(each_char など)にはブロックが要ります。組み込みの操作の表に操作の一覧があります。
19.1 String[]
String[*Any]
String の Array を作ります(String そのものではありません)。Integer[] や Tuple[] と同じ型付き Array で、要素はすべて String でなければならず、Array.push などの書き込みも毎回検査されます。String 以外の要素は静的に type の問題、実行時は TypeError です。空の String の Array は String[] です。
names = String["a", "b"]
Array.push(names, "c")
p(names) # => ["a", "b", "c"]
p(Array.size(String[])) # => 0
```ruby error Array.push(String[], 1) # !> Array.push: an element must be String, but is Integer
## length, size
`String.length(x)`
`String.size(x)`
文字の個数(Integer)。バイト数ではありません: `"héllo"` は 5 文字、6 バイト(`bytesize`)です。
```ruby
p(String.length("hello")) # => 5
p(String.size("héllo")) # => 5
p(String.size("")) # => 0
19.2 bytesize
String.bytesize(x)
その String のエンコーディングでのバイト数(Integer)。
p(String.bytesize("héllo")) # => 6
p(String.bytesize("")) # => 0
19.3 empty?
String.empty?(x)
文字が 1 つも無いとき true。
p(String.empty?("")) # => true
p(String.empty?(" ")) # => false
19.4 +
String.+(x, Any)
a + b の関数形。a の文字に b の文字を続けた新しい String を返します。両辺とも String でなければならず、"a" + 1 は静的に type の問題です(Ruby は TypeError)。どちらの被演算子も変わりません。
s = "abc"
p(s + "d") # => "abcd"
p(String.+("a", "b")) # => "ab"
p(s) # => "abc"
```ruby error p(“a” + 1) # !> the operands are (String, Integer)
## *
`String.*(x, Integer)`
`s * n` の関数形。`s` を `n` 回繰り返した新しい String(0 なら `""`)。負の `n` は演算子でも関数形でも `ArgumentError`(`negative argument`)で、rescue できます。
```ruby
p("ab" * 3) # => "ababab"
p(String.*("-", 0)) # => ""
begin
p("ab" * -1)
rescue ArgumentError => e
puts(Exception.message(e)) # => negative argument
end
```ruby error p(String.(“ab”, -1)) # !> ArgumentError: String.: negative argument -1
## %
`String.%(x, Any)`
`fmt % value` の関数形で、`fmt` を書式とする Ruby の `format` です: `"%d items" % 3`。右辺は値 1 つ(Integer、Float、String、Symbol、nil、true、false)か、複数の指示子に対する値の Tuple(`"%s-%s" % [a, b]`)か、名前付き指示子 `%<name>d`、`%{name}` に対する Record または Symbol キーの Hash(`"%<a>05d" % {a: 42}`、`"%<a>05d" % Hash.of(a: 42)`)です。Array は静的に `type` の問題です。指示子は Ruby のもの(`%d`、`%s`、`%f`、`%x`、`%05d`、`%-4s`、`%.2f`、`%p`、...)。指示子に合わない値(`"%d" % "x"`)や値の不足は `ArgumentError`、Record や Hash に無い名前は `KeyError`(`key<b> not found`)、余った値は無視されます。
```ruby
p("%05d|%-4s|%.2f|%x" % [42, "ab", 3.14159, 255]) # => "00042|ab |3.14|ff"
p("%d items" % 3) # => "3 items"
p("%s" % :sym) # => "sym"
p("%p" % nil) # => "nil"
p(String.%("%03d", 7)) # => "007"
p("%<a>05d" % {a: 42}) # => "00042"
p("%{a}-%<b>s" % {a: 1, b: "x"}) # => "1-x"
p("%<a>05d" % Hash.of(a: 42)) # => "00042"
```ruby error p(“%d” % {a: 42}) # !> KeyError: Arithmetic.%: key not found
```ruby error
p("%d %d" % 1) # !> ArgumentError: Arithmetic.%: too few arguments
19.5 ==, !=
String.==(x, Any)
String.!=(x, Any)
両方が String で同じ文字の並びのとき true(!= はその否定)。同一性ではなく内容の比較です。右辺が別の型(Integer、Symbol、nil)なら、演算子でも関数形でもただ等しくないだけです: String.==("a", 1) は false、String.!=("a", 1) は true で、誤りにはなりません。
p("a" == "a") # => true
p("a" != "b") # => true
p("1" == 1) # => false
p("a" == nil) # => false
p(String.==("a", nil)) # => false
p(String.==("a", 1)) # => false
p(String.!=("a", 1)) # => true
19.6 <, <=, >, >=
String.<(x, Any)
String.<=(x, Any)
String.>(x, Any)
String.>=(x, Any)
2 つの String を Ruby と同じくバイト単位で比べます: 大文字が先なので "B" < "a"、"10" < "9"、接頭辞は長い方より小さい。両辺とも String でなければならず、別の型との比較は静的に type の問題です。
p("a" < "b") # => true
p("B" >= "a") # => false
p("10" < "9") # => true
p("ab" < "abc") # => true
p(String.<=("a", "a")) # => true
```ruby error p(“a” < 1) # !> the operands are (String, Integer)
## <=>
`String.<=>(x, Any)`
その比較の結果を -1、0、1 で返します。両辺とも String でなければならないので、結果が nil になることはありません(Ruby は String 以外に nil を返します)。
```ruby
p("a" <=> "b") # => -1
p("a" <=> "a") # => 0
p(String.<=>("b", "a")) # => 1
19.7 between?
String.between?(x, String, String)
< の順で lo <= x <= hi のとき true。
p(String.between?("b", "a", "c")) # => true
p(String.between?("z", "a", "c")) # => false
19.8 clamp
String.clamp(x, String, String)
x が lo と hi の間にあればそのまま、外れていれば近い方の端を返します。lo > hi は ArgumentError です。
p(String.clamp("b", "a", "c")) # => "b"
p(String.clamp("z", "a", "c")) # => "c"
```ruby error p(String.clamp(“b”, “c”, “a”)) # !> ArgumentError: String.clamp: min argument must be less than or equal to max argument
## casecmp
`String.casecmp(x, String)`
ASCII 文字の大小を無視して 2 つの String を比べ、-1、0、1(Integer)を返します。2 つのエンコーディングが互換でないときは nil で、その結果を検査せずに使うことは `--strict` レベル 2 で `nil` の問題です。
```ruby
p(String.casecmp("a", "A")) # => 0
p(String.casecmp("a", "B")) # => -1
p(String.casecmp("a", String.encode("a", "UTF-16LE"))) # => nil
```ruby error p(String.casecmp(“a”, “b”) + 1) # !> the operands may be nil
## casecmp?
`String.casecmp?(x, String)`
Unicode のケースフォールディングの後で 2 つが等しいとき true。結果は Boolean で nil にはなりません: エンコーディングが互換でないとき(Ruby のメソッドが nil を返し、`casecmp` も nil になるところ)は **false** です。
```ruby
p(String.casecmp?("a", "A")) # => true
p(String.casecmp?("a", "b")) # => false
p(String.casecmp?("a", String.encode("a", "UTF-16LE"))) # => false
19.9 =~
String.=~(x, Any)
s =~ re の関数形。Regexp re が s に最初に一致する位置(Integer)、一致しなければ nil。右辺は Regexp でなければならず、String は静的に type の問題です。nil を検査せずに使うことは --strict レベル 2 で nil の問題です。$~ は無いので、グループを読むには String.match を使います。
p("hello" =~ /l+/) # => 2
p("hello" =~ /z/) # => nil
p(String.=~("ab", /b/)) # => 1
```ruby error p((“abc” =~ /b/) + 1) # !> the operands may be nil
## !~
`String.!~(x, Any)`
`s !~ re` の関数形。Regexp が一致しないとき true。
```ruby
p("hello" !~ /z/) # => true
p(String.!~("ab", /b/)) # => false
19.10 []
String.[](x, Any, [Integer])
s[i]、s[i, n]、s[range] の関数形。s[i] は位置 i(Integer。負の値は末尾から)の 1 文字の String、範囲外なら nil。s[i, n] は i から最大 n 文字の部分文字列で、i が末尾を越えるか n が負なら nil、i が長さに等しければ ""。s[range] は Range の範囲の文字で、始点が末尾を越えていれば nil。Ruby と同じく String と Regexp も添字にでき、s["b"] はその最初の出現(新しい String)か nil、s[/re/] は最初の一致、s[/re/, 1] はそのグループです(String.slice と同じ。2026-10-11 から)。Float などの添字は静的に type の問題で、Range と個数の組は TypeError です。結果の型は String | nil で、この nil は「外れの nil」(index-nil)なので --strict レベル 3 でのみ報告されます。
s = "hello"
p(s[1]) # => "e"
p(s[-1]) # => "o"
p(s[10]) # => nil
p(s[1, 3]) # => "ell"
p(s[5, 1]) # => ""
p(s[6, 1]) # => nil
p(s[1..2]) # => "el"
p(s[2..]) # => "llo"
p(String.[](s, 0)) # => "h"
s = "hello world"
p(s["wor"]) # => "wor"
p(s["xyz"]) # => nil
p(s[/o w/]) # => "o w"
p(s[/(\w+) (\w+)/, 2]) # => "world"
```ruby error p(“abc”[1.5]) # !> the index must be Integer, but is Float
## slice
`String.slice(x, Integer|Range|String|Regexp, [Integer])`
`s[i]`、`s[i, n]`、`s[range]` を操作として書いたもの。結果も nil(`index-nil`、レベル 3)も同じです。Ruby の `slice` と同じく、`slice!` が取るものも取れます: String はその最初の出現(新しい String)か nil、Regexp は最初の一致、第 2 引数にグループ番号を添えればそのグループの String(グループが一致に関与しなければ nil)です。String や Range の後に個数を添えると `TypeError` です。
```ruby
s = "hello"
p(String.slice(s, 1)) # => "e"
p(String.slice(s, 1, 2)) # => "el"
p(String.slice(s, 1..2)) # => "el"
p(String.slice(s, 9)) # => nil
p(String.slice(s, "ll")) # => "ll"
p(String.slice(s, "zz")) # => nil
p(String.slice(s, /l+/)) # => "ll"
p(String.slice(s, /(l)(o)/, 2)) # => "o"
```ruby error p(String.slice(“hello”, “ll”, 1)) # !> TypeError: String.slice: no implicit conversion of String into Integer
## slice!
`String.slice!(x, Integer|Range|String|Regexp, [Integer])`
`s[i]`、`s[i, n]`、`s[range]` の部分を主語から**その場で**取り除き、それを返します。String を渡すとその最初の出現を、Regexp なら最初の一致を取り除きます。取り除くものが無いとき(末尾を越えた添字、現れない String や Regexp)は nil で、主語は変わりません。その結果を検査せずに使うことは `--strict` レベル 2 で `nil` の問題です。
```ruby
s = "hello"
p(String.slice!(s, 0)) # => "h"
p(String.slice!(s, /l+/)) # => "ll"
p(String.slice!(s, "zz")) # => nil
p(s) # => "eo"
```ruby error s = “hello” c = String.slice!(s, 1) p(String.upcase(c)) # !> argument 1 may be nil
## byteslice
`String.byteslice(x, Integer|Range, [Integer])`
バイト位置 `i` のバイト(1 バイトの String)、`i` から `n` バイト、またはバイト位置の Range の範囲のバイト列。主語のエンコーディングのままなので、多バイト文字を切ると不正な String になりえます。`i`(または Range の始点)が末尾を越えるか `n` が負なら nil(`index-nil`、レベル 3)。Ruby と同じです。
```ruby
p(String.byteslice("héllo", 1)) # => "\xC3"
p(String.byteslice("héllo", 1, 2)) # => "é"
p(String.byteslice("héllo", 1..2)) # => "é"
p(String.byteslice("hello", 9..10)) # => nil
p(String.byteslice("héllo", 9)) # => nil
19.11 getbyte
String.getbyte(x, Integer)
バイト位置 i(負の値は末尾から)のバイト(0..255 の Integer)、範囲外なら nil。その結果を検査せずに使うことは --strict レベル 2 で nil の問題です。
s = "héllo"
p(String.getbyte(s, 0)) # => 104
p(String.getbyte(s, 1)) # => 195
p(String.getbyte(s, -1)) # => 111
p(String.getbyte(s, 99)) # => nil
```ruby error p(String.getbyte(“a”, 5) + 1) # !> the operands may be nil
## setbyte
`String.setbyte(x, Integer, Integer)`
バイト位置 `i` にバイト `b` を**その場で**格納し(Ruby と同じく `b` の下位 8 ビット)、`b` を返します。範囲外の位置は `IndexError` です。
```ruby
s = "abc"
p(String.setbyte(s, 0, 65)) # => 65
p(s) # => "Abc"
```ruby error p(String.setbyte(“abc”, 10, 1)) # !> IndexError: String.setbyte: index 10 out of string
## bytesplice
`String.bytesplice(x, Integer, Integer, String)`
バイト位置 `i` から `n` バイトを String `str` で**その場で**置き換え、主語を返します。範囲外の位置は `IndexError`。位置は文字の境界になければなりません。
```ruby
s = "hello"
p(String.bytesplice(s, 0, 2, "J")) # => "Jllo"
p(s) # => "Jllo"
```ruby error p(String.bytesplice(“abc”, 10, 1, “x”)) # !> IndexError: String.bytesplice: index 10 out of string
## upcase, downcase, capitalize, swapcase
`String.upcase(x)`
`String.downcase(x)`
`String.capitalize(x)`
`String.swapcase(x)`
文字を大文字に、小文字に、先頭だけ大文字で残りを小文字に、各文字の大小を入れ替えた新しい String。大小の対応は Ruby と同じく Unicode のものです(`"straße"` の upcase は `"STRASSE"`)。
```ruby
p(String.upcase("abc")) # => "ABC"
p(String.downcase("ABC")) # => "abc"
p(String.capitalize("hELLO")) # => "Hello"
p(String.swapcase("Ab")) # => "aB"
p(String.upcase("straße")) # => "STRASSE"
19.12 upcase!, downcase!, capitalize!, swapcase!
String.upcase!(x)
String.downcase!(x)
String.capitalize!(x)
String.swapcase!(x)
同じ変換をその場で行います。変わったときは主語を、すでにその形だったときは nil を返します。その結果を検査せずに使うことは --strict レベル 2 で nil の問題です。
s = "Hello"
p(String.upcase!(s)) # => "HELLO"
p(s) # => "HELLO"
p(String.upcase!(s)) # => nil
p(String.capitalize!("Abc")) # => nil
```ruby error s = “Hello” r = String.upcase!(s) p(String.size(r)) # !> argument 1 may be nil
## strip, lstrip, rstrip
`String.strip(x)`
`String.lstrip(x)`
`String.rstrip(x)`
先頭と末尾の空白(`strip`)、先頭だけ(`lstrip`)、末尾だけ(`rstrip`)を除いた新しい String。空白は Ruby と同じく、空白文字、タブ、改行、NUL です。
```ruby
p(String.strip(" a ")) # => "a"
p(String.lstrip(" a ")) # => "a "
p(String.rstrip(" a ")) # => " a"
19.13 strip!, lstrip!, rstrip!
String.strip!(x)
String.lstrip!(x)
String.rstrip!(x)
同じことをその場で行います。何か取り除いたときは主語、取り除くものが無かったときは nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = " a "
p(String.strip!(s)) # => "a"
p(String.strip!(s)) # => nil
p(String.lstrip!("a")) # => nil
19.14 chomp
String.chomp(x, [String])
末尾の行末 1 つ("\n"、"\r\n"、"\r")を除いた新しい String。行末が無ければそのままの複製です。suffix を渡すと代わりにその接尾辞を除きます(Ruby と同じく、"" は末尾の改行をすべて除きます)。
p(String.chomp("a\n")) # => "a"
p(String.chomp("a\r\n")) # => "a"
p(String.chomp("a")) # => "a"
p(String.chomp("abc!", "!")) # => "abc"
p(String.chomp("abc", "x")) # => "abc"
p(String.chomp("abc\n\n", "")) # => "abc"
19.15 chomp!
String.chomp!(x, [String])
末尾の行末 1 つをその場で除きます。suffix を渡すとその接尾辞を除きます。何か除いたときは主語、除くものが無かったときは nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = "a\n"
p(String.chomp!(s)) # => "a"
p(String.chomp!(s)) # => nil
p(String.chomp!("abc", "bc")) # => "a"
p(String.chomp!("abc", "x")) # => nil
```ruby error p(String.chomp!(“abc”) + “!”) # !> the operands may be nil
## chop
`String.chop(x)`
最後の文字を除いた新しい String(`"\r\n"` は 1 文字と数えます)。`""` は `""` です。
```ruby
p(String.chop("abc")) # => "ab"
p(String.chop("a\r\n")) # => "a"
p(String.chop("")) # => ""
19.16 chop!
String.chop!(x)
最後の文字をその場で除きます。主語を返し、空だったときは nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = "abc"
p(String.chop!(s)) # => "ab"
p(String.chop!("")) # => nil
19.17 chr
String.chr(x)
先頭の文字を String で返します。空の String なら ""(Ruby の chr)。
p(String.chr("abc")) # => "a"
p(String.chr("")) # => ""
19.18 delete_prefix, delete_suffix
String.delete_prefix(x, String)
String.delete_suffix(x, String)
先頭の prefix、末尾の suffix を除いた新しい String。それで始まって(終わって)いなければ、そのままの複製です。
p(String.delete_prefix("foobar", "foo")) # => "bar"
p(String.delete_suffix("foobar", "bar")) # => "foo"
p(String.delete_prefix("foobar", "x")) # => "foobar"
19.19 delete_prefix!, delete_suffix!
String.delete_prefix!(x, String)
String.delete_suffix!(x, String)
同じことをその場で行います。接頭辞(接尾辞)があって除いたときは主語、無かったときは nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = "foobar"
p(String.delete_prefix!(s, "foo")) # => "bar"
p(String.delete_prefix!(s, "foo")) # => nil
p(String.delete_suffix!(s, "ar")) # => "b"
p(s) # => "b"
19.20 include?, start_with?, end_with?
String.include?(x, String)
String.start_with?(x, String)
String.end_with?(x, String)
t が現れるとき、t で始まるとき、t で終わるとき true。どれも String をちょうど 1 つ取ります(Ruby の start_with? は複数の引数や Regexp も取ります)。
s = "hello world"
p(String.include?(s, "wor")) # => true
p(String.start_with?(s, "he")) # => true
p(String.end_with?(s, "x")) # => false
19.21 index
String.index(x, String|Regexp, [Integer])
String t の最初の出現、または Regexp の最初の一致の、位置 pos(既定 0。負の値は末尾から)以降での文字位置(Integer)。無ければ nil で、検査せずに使うことは --strict レベル 2 で nil の問題です。"" は pos で見つかります。
s = "hello world"
p(String.index(s, "o")) # => 4
p(String.index(s, "o", 5)) # => 7
p(String.index(s, /w./)) # => 6
p(String.index(s, "l", -3)) # => 9
p(String.index(s, "z")) # => nil
```ruby error s = “hello” i = String.index(s, “o”) p(i + 1) # !> the operands may be nil
## rindex
`String.rindex(x, String|Regexp, [Integer])`
`pos`(既定: 末尾)以前に始まる、`t` の最後の出現(Regexp なら最後の一致)の位置。無ければ nil(検査せずに使うと `--strict` レベル 2 で `nil` の問題)。
```ruby
s = "hello world"
p(String.rindex(s, "o")) # => 7
p(String.rindex(s, "o", 5)) # => 4
p(String.rindex(s, "z")) # => nil
```ruby error p(String.rindex(“abc”, “b”) + 1) # !> the operands may be nil
## byteindex
`String.byteindex(x, String|Regexp, [Integer])`
`index` と同じですが、結果と `pos` は**バイト**位置です。出現が無ければ nil(検査せずに使うと `--strict` レベル 2 で `nil` の問題)。`pos` は文字の境界になければならず、多バイト文字の途中の位置は `IndexError`(`offset 2 does not land on character boundary`)で、rescue できます。
```ruby
p(String.index("héllo", "l")) # => 2
p(String.byteindex("héllo", "l")) # => 3
p(String.byteindex("héllo", "z")) # => nil
p(String.byteindex("héllo", "l", 3)) # => 3
```ruby error p(String.byteindex(“héllo”, “l”, 2)) # !> IndexError: String.byteindex: offset 2 does not land on character boundary
## byterindex
`String.byterindex(x, String|Regexp, [Integer])`
`rindex` のバイト位置版: バイト位置 `pos` 以前に始まる最後の出現。無ければ nil(検査せずに使うと `--strict` レベル 2 で `nil` の問題)。
```ruby
p(String.byterindex("héllo", "l")) # => 4
p(String.byterindex("héllo", "l", 3)) # => 3
```ruby error p(String.byterindex(“abc”, “b”) + 1) # !> the operands may be nil
## match
`String.match(x, String|Regexp, [Integer])`
位置 `pos`(既定 0)以降で Regexp が最初に一致したところの `MatchData`。一致しなければ nil で、結果を検査せずに使うこと(`if m` 無しの `m[1]`)は `--strict` レベル 2 で `nil` の問題です。String のパターンは Ruby と同じく Regexp として解釈されます(`"."` は任意の 1 文字。`"+"` のような不正なパターンは `RegexpError` で、rescue できます)。`$~` や `$1` は無く、グループは MatchData から `m[0]`、`m[1]`、`m["name"]` で読みます。
```ruby
m = String.match("hello world", /(w)(o)/)
if m
p(m[0]) # => "wo"
p(m[2]) # => "o"
end
p(String.match("hello", "zzz")) # => nil
```ruby error p(String.match(“a+c”, “+”)) # !> RegexpError: String.match: target of repeat operator is not specified: /+/
```ruby error
m = String.match("abc", /b/)
p(m[0]) # !> the operands may be nil (MatchData | nil)
19.22 match?
String.match?(x, String|Regexp, [Integer])
位置 pos(既定 0)以降でパターンが一致するとき true。MatchData は作りません。String のパターンは Regexp として解釈され、不正なパターンは match と同じく RegexpError です。
p(String.match?("hello", /l+/)) # => true
p(String.match?("hello", "o", 8)) # => false
p(String.match?("abc", "b.")) # => true
begin
p(String.match?("a+c", "+"))
rescue RegexpError => e
puts(Exception.message(e)) # => target of repeat operator is not specified: /+/
end
19.23 scan
String.scan(x, String|Regexp)
重ならないすべての一致を左から順に Array で返します。グループが無ければ一致した String の Array。グループがあれば、グループごとに 1 つの String を持つ Tuple の Array です(Ruby は Array)。一致に関与しなかったグループはその位置が nil で、これは「外れの nil」(index-nil、レベル 3)です。
p(String.scan("a1b22", /\d+/)) # => ["1", "22"]
p(String.scan("abab", "ab")) # => ["ab", "ab"]
p(String.scan("a1b22", /(\w)(\d)/)) # => [["a", "1"], ["b", "2"]]
p(String.scan("a1 b", /(\w)(\d)?/)) # => [["a", "1"], ["b", nil]]
19.24 count
String.count(x, String)
集合 chars に属する文字の個数(Integer)。集合は Ruby の tr の書き方です: "lo" は文字の列挙、"a-z" は範囲、先頭の ^ は否定。集合は 1 つだけです(Ruby は複数の積を取ります)。"z-a" のような逆順の範囲は ArgumentError(invalid range "z-a" in string transliteration)で、tr、delete、squeeze でも同じです。
s = "hello world"
p(String.count(s, "lo")) # => 5
p(String.count(s, "a-z")) # => 10
p(String.count(s, "^l")) # => 8
```ruby error p(String.count(“abc”, “z-a”)) # !> ArgumentError: String.count: invalid range “z-a” in string transliteration
## partition, rpartition
`String.partition(x, String|Regexp)`
`String.rpartition(x, String|Regexp)`
`sep`(String か Regexp)の最初(`partition`)または最後(`rpartition`)の出現で分け、**Tuple** `[前, 区切り, 後]` を返します(Ruby は Array)。`sep` が現れないとき、`partition` は `[s, "", ""]`、`rpartition` は `["", "", s]` です。
```ruby
p(String.partition("a=b=c", "=")) # => ["a", "=", "b=c"]
p(String.rpartition("a=b=c", "=")) # => ["a=b", "=", "c"]
p(String.partition("hello", /l+/)) # => ["he", "ll", "o"]
p(String.partition("abc", "x")) # => ["abc", "", ""]
p(String.rpartition("abc", "x")) # => ["", "", "abc"]
19.25 sub, gsub
String.sub(x, String|Regexp, [String|Hash]) [{ }]
String.gsub(x, String|Regexp, [String|Hash]) [{ }]
pattern の最初の一致(sub)またはすべての一致(gsub)を置き換えた新しい String。String のパターンは文字どおりに一致します("." はピリオドで、任意の文字ではありません)。置換は 3 つの形のどれかです: String(\0 が一致全体、\1 が最初のグループ、\k<name> が名前付きグループ)、Hash(キーが一致した String、値を to_s で表示したものが置換。キーに無い一致は取り除かれる)、ブロック(一致した String を受け取り(MatchData ではありません)、その結果を to_s で表示したものが置換)。置換もブロックも無いと実行時に ArgumentError です(Ruby の Enumerator を返す形はありません)。
s = "hello"
p(String.sub(s, "l", "L")) # => "heLlo"
p(String.gsub(s, "l", "L")) # => "heLLo"
p(String.gsub(s, /l+/, "<\\0>")) # => "he<ll>o"
p(String.gsub(s, /(l)(o)/, "\\2\\1")) # => "helol"
p(String.gsub(s, /[el]/, Hash.of("e" => "3", "l" => 1))) # => "h311o"
p(String.gsub(s, /l/) { |m| String.upcase(m) }) # => "heLLo"
p(String.sub(s, /l/) { |m| 7 }) # => "he7lo"
p(s) # => "hello"
```ruby error p(String.sub(“abc”, “b”)) # !> ArgumentError: String.sub: sub needs a replacement or a block
## sub!, gsub!
`String.sub!(x, String|Regexp, [String|Hash]) [{ }]`
`String.gsub!(x, String|Regexp, [String|Hash]) [{ }]`
同じ置換を**その場で**行います。何か置き換えたときは主語、パターンが一致しなかったときは nil(主語は変わりません)を返し、結果を検査せずに使うことは `--strict` レベル 2 で `nil` の問題です。置換の形は `sub`、`gsub` と同じです。
```ruby
s = "hello"
p(String.sub!(s, "l", "L")) # => "heLlo"
p(s) # => "heLlo"
p(String.gsub!(s, "zz", "y")) # => nil
String.gsub!(s, /l/i) { |m| "_" }
p(s) # => "he__o"
```ruby error s = “hello” p(String.sub!(s, “x”, “y”) + “!”) # !> the operands may be nil
## tr
`String.tr(x, String, String)`
集合 `from` の各文字を、`to` の同じ位置の文字で置き換えた新しい String(Ruby の `tr`)。`"a-y"` は範囲、`from` の先頭の `^` は否定、`from` より短い `to` は最後の文字を繰り返し、空の `to` は削除です。逆順の範囲は `ArgumentError`(`invalid range "z-a" in string transliteration`)です。
```ruby
p(String.tr("hello", "el", "ip")) # => "hippo"
p(String.tr("hello", "a-y", "b-z")) # => "ifmmp"
p(String.tr("hello", "^l", "*")) # => "**ll*"
p(String.tr("hello", "lo", "")) # => "he"
```ruby error p(String.tr(“abc”, “z-a”, “x”)) # !> ArgumentError: String.tr: invalid range “z-a” in string transliteration
## tr!
`String.tr!(x, String, String)`
`tr` を**その場で**行います。何か変わったときは主語、変わらなければ nil(検査せずに使うと `--strict` レベル 2 で `nil` の問題)。
```ruby
s = "hello"
p(String.tr!(s, "l", "L")) # => "heLLo"
p(String.tr!(s, "z", "Z")) # => nil
19.26 tr_s
String.tr_s(x, String, String)
tr と同じ置き換えをしてから、置き換えた文字の連続を 1 つに縮めます(Ruby の tr_s)。
p(String.tr_s("hello", "l", "r")) # => "hero"
p(String.tr_s("aabbcc", "a-c", "x")) # => "x"
19.27 tr_s!
String.tr_s!(x, String, String)
tr_s をその場で行います。何か変わったときは主語、変わらなければ nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = "aabb"
p(String.tr_s!(s, "a", "x")) # => "xbb"
p(String.tr_s!(s, "z", "x")) # => nil
19.28 delete
String.delete(x, String, *String)
渡したすべての集合(tr の書き方: "a-k"、"^l")に属する文字を除いた新しい String。複数の集合は Ruby と同じく積です: delete("hello", "l", "o") は両方の集合に属する文字が無いので何も除きません。逆順の範囲は ArgumentError です。
p(String.delete("hello", "l")) # => "heo"
p(String.delete("hello", "a-k")) # => "llo"
p(String.delete("hello", "^l")) # => "ll"
p(String.delete("hello", "l", "o")) # => "hello"
p(String.delete("hello", "a-z", "^l")) # => "ll"
19.29 delete!
String.delete!(x, String, *String)
渡したすべての集合に属する文字をその場で削除します(複数の集合は Ruby と同じく積)。何か削除したときは主語、しなかったときは nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = "hello"
p(String.delete!(s, "l")) # => "heo"
p(String.delete!(s, "z")) # => nil
p(String.delete!("hello", "l", "^l")) # => nil
```ruby error p(String.delete!(“a”, “z-a”)) # !> ArgumentError: String.delete!: invalid range “z-a”
## squeeze
`String.squeeze(x, *String)`
同じ文字の連続を 1 つに縮めた新しい String。集合を渡すと、そのすべてに属する文字の連続だけを縮めます(複数の集合は Ruby と同じく積)。
```ruby
p(String.squeeze("aaabbb c")) # => "ab c"
p(String.squeeze("aaabbb c", "a")) # => "abbb c"
p(String.squeeze("aaabbb c", "a-b")) # => "ab c"
p(String.squeeze("aaabbb c", "a-b", "b")) # => "aaab c"
19.30 squeeze!
String.squeeze!(x, *String)
squeeze をその場で行います。集合はいくつでも渡せます(Ruby と同じく積)。何か変わったときは主語、変わらなければ nil(検査せずに使うと --strict レベル 2 で nil の問題)。
s = "aaabbb"
p(String.squeeze!(s)) # => "ab"
p(String.squeeze!(s)) # => nil
p(String.squeeze!("aabb", "a")) # => "abb"
19.31 split
String.split(x, [String|Regexp], [Integer])
String の Array に分けます。sep 無しか " " なら空白の連続で分け、先頭の空白は無視します(awk 流)。他の String ならその各出現で、"" なら 1 文字ずつ、Regexp なら各一致で分け、Regexp のグループは結果に含まれます(Ruby の規則)。limit(既定 0)は個数の上限です: 正なら最大その個数で最後の要素に残りが入り、0 なら末尾の空文字列を落とし、負なら残します。ブロックの形はありません。
p(String.split("a b c")) # => ["a", "b", "c"]
p(String.split("a,b,,c", ",")) # => ["a", "b", "", "c"]
p(String.split("a,b,,c", ",", 2)) # => ["a", "b,,c"]
p(String.split("a,b,,", ",")) # => ["a", "b"]
p(String.split("a,b,,", ",", -1)) # => ["a", "b", "", ""]
p(String.split("a1b2c", /\d/)) # => ["a", "b", "c"]
p(String.split("a1b", /(\d)/)) # => ["a", "1", "b"]
p(String.split("abc", "")) # => ["a", "b", "c"]
p(String.split("", ",")) # => []
19.32 chars
String.chars(x)
文字の Array。各要素は 1 文字の String です。
p(String.chars("aé")) # => ["a", "é"]
p(String.chars("")) # => []
19.33 lines
String.lines(x)
行の Array。各行は "\n" を保ちます(最後の行には無いことがあります)。
p(String.lines("a\nb\n")) # => ["a\n", "b\n"]
p(String.lines("a\nb")) # => ["a\n", "b"]
p(String.lines("")) # => []
19.34 bytes
String.bytes(x)
バイトの Array(0..255 の Integer)。
p(String.bytes("aé")) # => [97, 195, 169]
19.35 codepoints
String.codepoints(x)
Unicode のコードポイントの Array(Integer)。
p(String.codepoints("aé")) # => [97, 233]
19.36 grapheme_clusters
String.grapheme_clusters(x)
グラフェムクラスタ(読む人が 1 文字と見るもの: 基底文字と結合文字の組)の Array。各要素は String です。
s = "éa"
p(String.length(s)) # => 3
p(Array.size(String.grapheme_clusters(s))) # => 2
p(Array.map(String.grapheme_clusters(s)) { |g| String.length(g) }) # => [2, 1]
19.37 each_char
String.each_char(x) { }
各文字(1 文字の String)を順にブロックに渡し、主語を返します。ブロックは必須で、無いと静的な誤りです(Ruby の Enumerator を返す形はありません)。
r = String.each_char("ab") { |c| p(c) }
# => "a"
# => "b"
p(r) # => "ab"
```ruby error String.each_char(“ab”) # !> String.each_char requires a block
## each_line
`String.each_line(x) { }`
各行を `"\n"` を保ったままブロックに渡し、主語を返します。ブロックは必須です。区切りの引数はありません(Ruby の `each_line(sep)`)。
```ruby
String.each_line("a\nb") { |l| p(l) }
# => "a\n"
# => "b"
19.38 each_byte
String.each_byte(x) { }
各バイト(Integer)をブロックに渡し、主語を返します。ブロックは必須です。
String.each_byte("aé") { |b| p(b) }
# => 97
# => 195
# => 169
19.39 each_codepoint
String.each_codepoint(x) { }
各コードポイント(Integer)をブロックに渡し、主語を返します。ブロックは必須です。
String.each_codepoint("aé") { |c| p(c) }
# => 97
# => 233
19.40 each_grapheme_cluster
String.each_grapheme_cluster(x) { }
各グラフェムクラスタ(String)をブロックに渡し、主語を返します。ブロックは必須です。
String.each_grapheme_cluster("éa") { |g| p(String.length(g)) }
# => 2
# => 1
19.41 upto
String.upto(x, String) { }
x、その String.succ、…と last まで(last を含む)をブロックに渡します。Ruby の upto("a".upto("e"))と同じ規則で、値が last より長くなったところで止まるので、x がすでに last を越えていれば何も渡しません。数字だけの String は数として進みます("9" から "11")。主語を返します。
String.upto("a", "c") { |s| p(s) }
# => "a"
# => "b"
# => "c"
String.upto("az", "bb") { |s| p(s) }
# => "az"
# => "ba"
# => "bb"
String.upto("9", "11") { |s| p(s) }
# => "9"
# => "10"
# => "11"
String.upto("b", "a") { |s| p(s) }
19.42 succ, next
String.succ(x)
String.next(x)
Ruby の意味で x の「次」の新しい String: 右端の英字または数字を 1 つ進め、桁あふれは左へ繰り上げます("az" は "ba"、"a9" は "b0"、"Zz" は "AAa")。"" は "" です。
p(String.succ("az")) # => "ba"
p(String.next("Zz")) # => "AAa"
p(String.succ("a9")) # => "b0"
p(String.succ("")) # => ""
19.43 succ!, next!
String.succ!(x)
String.next!(x)
主語をその場でその「次」に置き換え、主語を返します(nil にはなりません)。
s = "az"
p(String.succ!(s)) # => "ba"
p(String.next!(s)) # => "bb"
p(s) # => "bb"
19.44 reverse
String.reverse(x)
文字の順を逆にした新しい String。
p(String.reverse("abc")) # => "cba"
19.45 reverse!
String.reverse!(x)
主語をその場で逆順にし、主語を返します(nil にはなりません)。
s = "abc"
p(String.reverse!(s)) # => "cba"
p(s) # => "cba"
19.46 ljust, rjust, center
String.ljust(x, Integer, [String])
String.rjust(x, Integer, [String])
String.center(x, Integer, [String])
width 文字の新しい String: 主語の右(ljust)、左(rjust)、両側(center。余りは右)に pad(既定 " ")の繰り返しを、幅に合わせて切って詰めます。width が長さ以下なら、そのままの複製です。空の pad は ArgumentError(zero width padding)です。
p(String.ljust("ab", 5)) # => "ab "
p(String.rjust("ab", 5, "0")) # => "000ab"
p(String.center("ab", 6, "*")) # => "**ab**"
p(String.ljust("ab", 5, "xy")) # => "abxyx"
p(String.center("ab", 1)) # => "ab"
```ruby error p(String.ljust(“a”, 3, “”)) # !> ArgumentError: String.ljust: zero width padding
## to_s
`String.to_s(x)`
その String 自身。どの型にも `T.to_s(x)` と書けるようにあります。
```ruby
p(String.to_s("x")) # => "x"
19.47 to_i
String.to_i(x, [Integer])
String の先頭に書かれた Integer を基数 base(2..36、既定 10)で読みます。Ruby の to_i と同じく、先頭の空白、符号、数字の間の下線を許し、それ以外の文字で読むのをやめ、数字が無ければ 0 です(誤りにはなりません。組み込みの操作の Integer(s) は代わりに例外を投げます)。基数 0 は接頭辞を読みます: 0x(16)、0b(2)、0o または先頭の 0(8)。基数 16 では 0x の接頭辞を許します。2..36 の外の基数(0 以外)は ArgumentError です。
p(String.to_i("42")) # => 42
p(String.to_i(" -12abc")) # => -12
p(String.to_i("abc")) # => 0
p(String.to_i("ff", 16)) # => 255
p(String.to_i("0x1f", 16)) # => 31
p(String.to_i("0b101", 0)) # => 5
p(String.to_i("012", 0)) # => 10
p(String.to_i("z", 36)) # => 35
```ruby error p(String.to_i(“1”, 37)) # !> ArgumentError: String.to_i: invalid radix 37
## to_f
`String.to_f(x)`
String の先頭に書かれた Float(`"3.5e2"`。下線を許します)。無ければ 0.0 です(誤りにはなりません。`Float(s)` は例外を投げます)。Float に収まらない値は `Infinity` です。
```ruby
p(String.to_f("3.5e2")) # => 350.0
p(String.to_f("1_000.5")) # => 1000.5
p(String.to_f("abc")) # => 0.0
19.48 to_r
String.to_r(x)
String の先頭に書かれた Rational: "3/4"、"0.75"、"1e2"。数が無ければ (0/1) です。
p(String.to_r("3/4")) # => (3/4)
p(String.to_r("0.75")) # => (3/4)
p(String.to_r("abc")) # => (0/1)
19.49 to_c
String.to_c(x)
String の先頭に書かれた Complex("1+2i")。無ければ (0+0i) です。
p(String.to_c("1+2i")) # => (1+2i)
p(String.to_c("x")) # => (0+0i)
19.50 to_sym, intern
String.to_sym(x)
String.intern(x)
その名前の Symbol。どんな String も Symbol になれ、識別子でないものは引用符付きで表示されます。
p(String.to_sym("abc")) # => :abc
p(String.intern("a b")) # => :"a b"
19.51 hex
String.hex(x)
String を 16 進の Integer として読みます(Ruby の hex)。符号と 0x の接頭辞を許し、16 進数字でない文字で読むのをやめ、数字が無ければ 0 です。
p(String.hex("ff")) # => 255
p(String.hex("0x1F")) # => 31
p(String.hex("-a")) # => -10
p(String.hex("zz")) # => 0
19.52 oct
String.oct(x)
String を 8 進の Integer として読みます(Ruby の oct)。接頭辞 0x、0b、0o があれば Ruby と同じく基数が変わります。数字が無ければ 0 です。
p(String.oct("17")) # => 15
p(String.oct("0x1f")) # => 31
p(String.oct("0b11")) # => 3
p(String.oct("9")) # => 0
19.53 ord
String.ord(x)
先頭の文字のコードポイント(Integer)。空の String は ArgumentError です。
p(String.ord("a")) # => 97
p(String.ord("é")) # => 233
```ruby error p(String.ord(“”)) # !> ArgumentError: String.ord: empty string
## sum
`String.sum(x, [Integer])`
バイトの和を 2 の `bits` 乗(既定 16)で割った余り。Ruby の簡単なチェックサムです。
```ruby
p(String.sum("abc")) # => 294
p(String.sum("abc", 8)) # => 38
19.54 crypt
String.crypt(x, String)
システムの crypt(3) による、salt を使った String の一方向ハッシュ(Ruby と同じ)。2 バイトより短い salt は ArgumentError です。結果はプラットフォームの libc に依存します。
p(String.size(String.crypt("abc", "ab")) > 2) # => true
```ruby error p(String.crypt(“abc”, “a”)) # !> ArgumentError: String.crypt: salt too short (need >=2 bytes)
## concat
`String.concat(x, *String|Integer)`
各引数を主語の末尾に**その場で**追加し、主語を返します。Integer はそのコードポイントの文字として追加されます。
```ruby
s = "a"
p(String.concat(s, "b", 99, "d")) # => "abcd"
p(s) # => "abcd"
19.55 append_as_bytes
String.append_as_bytes(x, *String|Integer)
各 String 引数のバイト列を、Integer は 1 バイト(下位 8 ビット)として、エンコーディングの変換も妥当性の検査も無しにその場で追加します(Ruby と同じ)。結果は不正な String になりえます。主語を返します。
s = "a"
p(String.append_as_bytes(s, "é", 255)) # => "aé\xFF"
p(String.bytesize(s)) # => 4
p(String.valid_encoding?(s)) # => false
19.56 prepend
String.prepend(x, *String)
引数を順に主語の先頭にその場で挿入し、主語を返します。
s = "c"
p(String.prepend(s, "a", "b")) # => "abc"
p(s) # => "abc"
19.57 insert
String.insert(x, Integer, String)
位置 i の文字の前に str をその場で挿入します。負の i は末尾から数えた文字の後に挿入するので、-1 は末尾への追加です。主語を返します。範囲外の位置は IndexError です。
s = "ac"
p(String.insert(s, 1, "b")) # => "abc"
p(String.insert(s, -1, "d")) # => "abcd"
p(String.insert(s, 0, "_")) # => "_abcd"
```ruby error p(String.insert(“abc”, 10, “x”)) # !> IndexError: String.insert: index 10 out of string
## replace
`String.replace(x, String)`
主語の内容を `str` の内容で**その場で**置き換え、主語を返します。主語を持つすべての変数から新しい内容が見えます。
```ruby
s = "old"
t = s
String.replace(s, "new")
p(t) # => "new"
19.58 clear
String.clear(x)
すべての文字をその場で取り除き、主語("" になっています)を返します。
s = "hello"
p(String.clear(s)) # => ""
p(String.empty?(s)) # => true
19.59 encoding
String.encoding(x)
その String のエンコーディングの名前を String で返します("UTF-8"、"ASCII-8BIT"、…)。Ruby は Encoding オブジェクトを返しますが、Sake にその型はありません。
p(String.encoding("é")) # => "UTF-8"
p(String.encoding(String.b("é"))) # => "ASCII-8BIT"
19.60 force_encoding
String.force_encoding(x, String)
主語のバイト列にエンコーディング enc のラベルをその場で付け替え、主語を返します(Ruby の force_encoding と同じ)。バイト列は変換されず、新しいエンコーディングで妥当かは valid_encoding? で分かります。未知のエンコーディング名は ArgumentError、変えられない String(Hash のキー、Symbol の名前など)は TypeError です。
s = String.b("\xC3\xA9")
p(String.encoding(s)) # => "ASCII-8BIT"
t = String.force_encoding(s, "UTF-8")
p(String.encoding(s)) # => "UTF-8"
p(Kernel.equal?(s, t)) # => true
p(s) # => "é"
p(String.valid_encoding?(String.force_encoding("\xff", "UTF-8"))) # => false
```ruby error p(String.force_encoding(“a”, “bogus”)) # !> ArgumentError: String.force_encoding: unknown encoding name - bogus
## encode
`String.encode(x, String, [String])`
文字をエンコーディング `to` に変換した新しい String。`from`(既定: 主語のエンコーディング)はバイト列の読み方を指定します。`to` で表せない文字、主語の不正なバイト、未知のエンコーディング名は `EncodingError` です。
```ruby
u = String.encode("é", "UTF-16LE")
p(String.encoding(u)) # => "UTF-16LE"
p(String.bytesize(u)) # => 2
p(String.encode("\xE9", "UTF-8", "ISO-8859-1")) # => "é"
begin
String.encode("é", "US-ASCII")
rescue EncodingError => e
p(Exception.message(e)) # => "U+00E9 from UTF-8 to US-ASCII"
end
19.61 valid_encoding?
String.valid_encoding?(x)
バイト列がその String のエンコーディングで妥当なとき true。
p(String.valid_encoding?("é")) # => true
p(String.valid_encoding?("\xff")) # => false
19.62 ascii_only?
String.ascii_only?(x)
すべての文字が ASCII のとき true(空の String も true)。
p(String.ascii_only?("abc")) # => true
p(String.ascii_only?("é")) # => false
19.63 b
String.b(x)
同じバイト列に ASCII-8BIT(バイナリ)のラベルを付けた複製。長さはバイト数になります。
b = String.b("é")
p(b) # => "\xC3\xA9"
p(String.length(b)) # => 2
19.64 scrub
String.scrub(x, [String])
不正なバイト列をすべて repl(既定: 置換文字 "�")で置き換えた新しい String。
p(String.scrub("a\xffb", "?")) # => "a?b"
p(String.valid_encoding?(String.scrub("a\xffb"))) # => true
19.65 scrub!
String.scrub!(x, [String])
scrub をその場で行います。置き換えるものが無かったときも主語を返します(他の ! 形と違い、Ruby と同じく nil にはなりません)。
s = "a\xffb"
p(String.scrub!(s, "!")) # => "a!b"
p(String.scrub!("ok")) # => "ok"
19.66 unicode_normalize
String.unicode_normalize(x, [Symbol])
Unicode 正規化形 form にした新しい String: :nfc(既定)、:nfd、:nfkc、:nfkd。他の Symbol は ArgumentError、Unicode でないエンコーディングの String(バイナリの String)は EncodingError です。
d = String.unicode_normalize("é", :nfd)
p(String.length(d)) # => 2
p(String.length(String.unicode_normalize(d))) # => 1
```ruby error p(String.unicode_normalize(“a”, :bogus)) # !> ArgumentError: String.unicode_normalize: Invalid normalization form bogus
## unicode_normalize!
`String.unicode_normalize!(x, [Symbol])`
`unicode_normalize` を**その場で**行い、主語を返します(nil にはなりません)。
```ruby
s = "é"
p(String.unicode_normalize!(s)) # => "é"
p(String.length(s)) # => 1
19.67 unicode_normalized?
String.unicode_normalized?(x, [Symbol])
String がすでに形 form(既定 :nfc)になっているとき true。
p(String.unicode_normalized?("é")) # => true
p(String.unicode_normalized?("é", :nfd)) # => false
19.68 dump
String.dump(x)
主語を引用符で囲んだ ASCII だけのソース形にした新しい String: 印字できない文字と非 ASCII 文字は \n や é のようなエスケープになり、" と \ はエスケープされます。undump が元に戻します。
p(String.dump("a\né")) # => "\"a\\n\\u00E9\""
19.69 undump
String.undump(x)
dump が主語を作った元の String: 引用符を外し、エスケープを読みます。dump の形でない String は Ruby のメッセージを持つ ArgumentError です(Ruby のメソッドは RuntimeError を投げますが、Sake の RuntimeError は raise "msg" と Thread.raise からしか生まれません)。
p(String.undump("\"a\\n\"")) # => "a\n"
p(String.undump(String.dump("é"))) # => "é"
begin
p(String.undump("abc"))
rescue ArgumentError => e
puts(Exception.message(e)) # => invalid dumped string; not wrapped with '"' nor '"...".force_encoding("...")' form
end
```ruby error p(String.undump(“abc”)) # !> ArgumentError: String.undump: invalid dumped string
## unpack
`String.unpack(x, String)`
バイト列を `Array.pack` の書式 `fmt` に従って Array に復号します(Ruby の `unpack`): `C` は符号無しバイト、`v` はリトルエンディアンの 16 ビット Integer、`e` はリトルエンディアンの Float、`a`/`A`/`Z` は String、`*` は繰り返し。要素の型は指示子に従います(Integer、String、Float)。バイトが残っていない指示子は nil を与えます。未知の指示子は `ArgumentError` です。
```ruby
p(String.unpack("AB", "C*")) # => [65, 66]
p(String.unpack("\x01\x00", "v")) # => [1]
p(String.unpack("ab", "a1a1")) # => ["a", "b"]
p(String.unpack("\x00\x00\x80\x3f", "e")) # => [1.0]
p(String.unpack("", "C")) # => [nil]
```ruby error p(String.unpack(“abc”, “Z%”)) # !> ArgumentError: String.unpack: % is not supported
## unpack1
`String.unpack1(x, String)`
`unpack(x, fmt)` の最初の要素。そのためのバイトが無ければ nil で、結果を検査せずに使うことは `--strict` レベル 2 で `nil` の問題です。
```ruby
p(String.unpack1("\x01\x00", "v")) # => 1
p(String.unpack1("abc", "a*")) # => "abc"
p(String.unpack1("", "C")) # => nil
ruby error
p(String.unpack1("ab", "C") + 1) # !> the operands may be nil
20. Symbol
Symbol は名前そのものを値にしたもので、リテラル :name が作ります(空白などを含む名前は :"hello world"。値と型)。同じ名前の Symbol は常に同じ 1 つの値で、Hash のキー(Hash.of(name: 1) のラベルは Symbol キー)や case/in の分岐の目印に使います。String から作るには String.to_sym(s)(String.intern)、String に戻すには Symbol.to_s。Ruby の %i[a b] はまだ使えず(静的に拒否)、Symbol の Array は Symbol[:a, :b] と書きます。
Ruby の Symbol#to_proc(&:name)はありません。ブロックは値ではないので、Array.map(xs, &:to_s) は静的に拒否され、{ |x| Integer.to_s(x) } と書きます(関数とブロック)。
case s in :a ... in :b ... end のようにリテラルで分岐するとき、s がリテラル以外から来た Symbol(String.to_sym の結果など)なら、--strict=3 は取りこぼす値があることを exhaustive として報告します(else を足します。制御構造)。
Symbol に使える演算子は ==、!=、<、<=、>、>=、<=> で、比較は Symbol 同士に限ります(右側が String や Integer だと < などは静的に type の問題)。:a == "a" は false で、関数形 Symbol.==(:a, "a") も false です。Symbol.<(x, y) などはその演算子の関数形です(演算子と添字)。
文字列風の操作(upcase、slice、match、start_with? など)は、名前を String として処理した結果を返します。Symbol を返すもの(upcase など)と String を返すもの(slice)があります。どれも Symbol 自体を変えません(Symbol は不変です)。
20.1 Symbol[]
Symbol[*Any]
Symbol の Array を作ります。Integer[] と同じ型付き Array で、要素はすべて Symbol でなければならず、Array.push などの書き込みも毎回検査されます。Symbol 以外は静的に type の問題、実行時は TypeError。空の Symbol の Array は Symbol[]。
syms = Symbol[:a, :b]
Array.push(syms, :c)
p(syms) # => [:a, :b, :c]
p(Array.size(Symbol[])) # => 0
```ruby error Array.push(Symbol[:a], “x”) # !> Array.push: an element must be Symbol, but is String
## to_s, id2name, name
`Symbol.to_s(x)`
`Symbol.id2name(x)`
`Symbol.name(x)`
名前を String で返します。`to_s` と `id2name` は毎回新しい(書き換えられる)String を返し、`name` は Symbol が持つ名前そのものを返すので、Ruby と同じく凍結されていて、`String.upcase!` などで書き換えると `TypeError` です。
```ruby
p(Symbol.to_s(:hello)) # => "hello"
p(Symbol.id2name(:"with space")) # => "with space"
s = Symbol.to_s(:abc)
String.upcase!(s)
p(s) # => "ABC"
p(Symbol.name(:abc)) # => "abc"
```ruby error s = Symbol.name(:abc) String.upcase!(s) # !> TypeError: String.upcase!: cannot change this String in place
## to_sym, intern
`Symbol.to_sym(x)`
`Symbol.intern(x)`
その Symbol 自身を返します(Ruby と同じ)。String から Symbol を作るのは `String.to_sym`、`String.intern` です。
```ruby
p(Symbol.to_sym(:a)) # => :a
p(Symbol.intern(:a) == :a) # => true
p(String.to_sym("a") == :a) # => true
20.2 length, size
Symbol.length(x)
Symbol.size(x)
名前の文字数(Integer。バイト数ではありません)。
p(Symbol.length(:hello)) # => 5
p(Symbol.size(:日本)) # => 2
20.3 empty?
Symbol.empty?(x)
名前が空(:"")なら true。
p(Symbol.empty?(:"")) # => true
p(Symbol.empty?(:a)) # => false
20.4 encoding
Symbol.encoding(x)
名前のエンコーディングの名前を String で返します(Ruby は Encoding オブジェクトを返します)。ASCII だけの名前は "US-ASCII"、それ以外は "UTF-8"。
p(Symbol.encoding(:hello)) # => "US-ASCII"
p(Symbol.encoding(:日本)) # => "UTF-8"
20.5 upcase, downcase, capitalize, swapcase
Symbol.upcase(x)
Symbol.downcase(x)
Symbol.capitalize(x)
Symbol.swapcase(x)
名前を大文字に・小文字に・先頭だけ大文字(残りは小文字)に・大小反転にした新しい Symbol を返します。Ruby の String の同名の操作と同じで、Unicode の文字も変換されます。オプション引数(:ascii など)はありません。
p(Symbol.upcase(:hello)) # => :HELLO
p(Symbol.downcase(:HeLLo)) # => :hello
p(Symbol.capitalize(:hello_world)) # => :Hello_world
p(Symbol.swapcase(:HeLLo)) # => :hEllO
p(Symbol.downcase(:ÀB)) # => :àb
20.6 succ, next
Symbol.succ(x)
Symbol.next(x)
名前を Ruby の String#succ で 1 つ進めた新しい Symbol(:a → :b、:az → :ba、:zz → :aaa、:a9 → :b0)。
p(Symbol.succ(:a)) # => :b
p(Symbol.next(:az)) # => :ba
p(Symbol.succ(:zz)) # => :aaa
p(Symbol.succ(:a9)) # => :b0
20.7 start_with?, end_with?
Symbol.start_with?(x, String)
Symbol.end_with?(x, String)
名前が String s で始まる・終わるなら true。引数は String で、Symbol を渡すのは静的に type の問題です(Ruby の複数引数や Regexp の形はありません)。
p(Symbol.start_with?(:hello, "he")) # => true
p(Symbol.end_with?(:hello, "lo")) # => true
p(Symbol.start_with?(:hello, "x")) # => false
```ruby error p(Symbol.start_with?(:abc, :a)) # !> Symbol.start_with?: argument 2 must be String, but is :a
## slice
`Symbol.slice(x, Integer, [Integer])`
名前の一部を String で返します。`slice(s, i)` は位置 `i` の 1 文字(負の値は末尾から)、`slice(s, i, n)` は位置 `i` から `n` 文字。範囲外なら nil(Ruby の `String#slice` と同じで、`i` が長さに等しいときの `slice(s, i, n)` は `""`)。結果は nil になりうるので、そのまま使うと level 2 で報告されます。Range や String を添字にする形はありません(静的に `type` の問題)。
```ruby
p(Symbol.slice(:hello, 1)) # => "e"
p(Symbol.slice(:hello, -1)) # => "o"
p(Symbol.slice(:hello, 1, 3)) # => "ell"
p(Symbol.slice(:hello, 10)) # => nil
p(Symbol.slice(:abc, 3, 1)) # => ""
```ruby error p(Symbol.slice(:abc, 0..1)) # !> Symbol.slice: argument 2 must be Integer, but is Range[Integer]
## match
`Symbol.match(x, Regexp|String)`
名前にパターンを当て、最初に一致した MatchData を返します。一致しなければ nil(level 2 で報告)。パターンが String なら正規表現のソースとして扱います(Ruby と同じ)。
```ruby
m = Symbol.match(:hello, /l+/)
p(m) # => #<MatchData "ll">
if m
p(MatchData.to_s(m)) # => "ll"
end
p(Symbol.match(:hello, "l+")) # => #<MatchData "ll">
p(Symbol.match(:hello, /z/)) # => nil
20.8 match?
Symbol.match?(x, Regexp|String)
名前がパターンに一致すれば true。MatchData を作らないので、一致したかどうかだけが要るときはこちらを使います。
p(Symbol.match?(:hello, /ell/)) # => true
p(Symbol.match?(:hello, "x")) # => false
20.9 casecmp
Symbol.casecmp(x, Symbol)
大文字小文字を区別せず(ASCII の範囲で)2 つの Symbol の名前を比べ、-1、0、1 を返します。引数は Symbol でなければなりません(String は静的に type の問題)。Ruby の casecmp が比べられないときに nil を返すことに合わせて結果の型は Integer | nil なので、そのまま計算に使うと level 2 で報告されます。
p(Symbol.casecmp(:a, :A)) # => 0
p(Symbol.casecmp(:a, :B)) # => -1
p(Symbol.casecmp(:b, :A)) # => 1
```ruby error p(Symbol.casecmp(:a, “b”)) # !> Symbol.casecmp: argument 2 must be Symbol, but is String
## casecmp?
`Symbol.casecmp?(x, Symbol)`
大文字小文字を区別せず(Unicode の case folding で)2 つの Symbol の名前が等しければ true。`casecmp` と違い、結果は Boolean で nil にはなりません: Ruby のメソッドが nil を返すところ(エンコーディングが互換でないとき)は false です。
```ruby
p(Symbol.casecmp?(:a, :A)) # => true
p(Symbol.casecmp?(:a, :b)) # => false
p(Symbol.casecmp?(:Straße, :STRASSE)) # => true
20.10 ==, !=
Symbol.==(x, Any)
Symbol.!=(x, Any)
同じ名前の Symbol なら true(!= はその否定)。右側が Symbol でないもの(String、Integer、nil)なら、演算子でも関数形でもただ等しくないだけです: Symbol.==(:a, "a") は false で、誤りにはなりません。
p(:a == :a) # => true
p(:a != :b) # => true
p(:a == "a") # => false
p(Symbol.==(:a, :a)) # => true
p(Symbol.==(:a, "a")) # => false
p(Symbol.!=(:a, "a")) # => true
20.11 <, <=, >, >=
Symbol.<(x, Any)
Symbol.<=(x, Any)
Symbol.>(x, Any)
Symbol.>=(x, Any)
2 つの Symbol の名前を String として比べます(Ruby と同じ、バイト順)。右側は Symbol でなければならず、String や Integer は静的に type の問題です。
p(:a < :b) # => true
p(:b >= :c) # => false
p(Symbol.<=(:a, :a)) # => true
```ruby error p(:a < “a”) # !> which the left operand’s type does not support
## <=>
`Symbol.<=>(x, Any)`
名前の比較の結果を -1、0、1 で返します。規則は `<` と同じで、右側は Symbol に限ります。`Array.sort_by` のキーに使えます。
```ruby
p(:a <=> :b) # => -1
p(:aa <=> :b) # => -1
p(Symbol.<=>(:a, :a)) # => 0
21. Regexp
Regexp は正規表現です。リテラル /\d+/ と、Ruby と同じフラグ i(大文字小文字を区別しない)、m(. が改行にも一致)、x(空白とコメントを無視)、n(バイト列として扱う。/a/n のエンコーディングは US-ASCII)が使えます。/a#{x}/ で String を埋め込めます。文字列から組み立てるには Regexp.new(s, flags)。文法は Ruby(Onigmo)のものそのままで、名前付きグループ (?<name>...) も使えます(値と型)。
Ruby との大きな違いは、一致の結果がグローバルに残らないことです。$~、$1、Regexp.last_match は存在せず、/(?<y>\d+)/ =~ s が局所変数 y を作ることもありません(静的なエラー)。一致の結果は Regexp.match が返す MatchData を変数に置いて読みます(MatchData)。
Regexp に使える演算子は =~(String との一致位置、無ければ nil)、==、!= です。String 側の s =~ re、s !~ re もあります。String.match、match?、scan、sub、gsub、split、index なども Regexp を取ります(演算子と添字)。Regexp は Hash のキーや Set の要素にはなれません(TypeError)。
21.1 new
Regexp.new(String, [String])
ソースの String から Regexp を作ります。第 2 引数はフラグの文字を並べた String(省略時 ““)で、リテラルと同じ i、m、x、n が使えます。それ以外の文字は実行時に ArgumentError。ソースが正規表現として正しくなければ RegexpError で、rescue できます(Ruby の Regexp.new(s, Regexp::IGNORECASE) のような Integer のフラグは取りません)。ソースの特殊文字をそのまま一致させたいときは Regexp.escape を通します。
p(Regexp.new("a.b")) # => /a.b/
re = Regexp.new("ab+", "i")
p(re) # => /ab+/i
p(Regexp.match?(re, "ABB")) # => true
p(Regexp.options(Regexp.new("a", "mi"))) # => 5
p(Regexp.new("a", "n")) # => /a/n
begin
p(Regexp.new("("))
rescue RegexpError => e
puts(Exception.message(e)) # => end pattern with unmatched parenthesis: /(/
end
```ruby error p(Regexp.new(“a”, “q”)) # !> ArgumentError: Regexp.new: unknown regexp option: q
```ruby error
p(Regexp.new("(")) # !> RegexpError: Regexp.new: end pattern with unmatched parenthesis: /(/
21.2 escape
Regexp.escape(String)
String の中の正規表現の特殊文字(.、*、(、空白など)をバックスラッシュで逃がした String。Regexp.new(Regexp.escape(s)) が、s そのものに一致する Regexp です。
p(Regexp.escape("a.b*c")) # => "a\\.b\\*c"
word = "a.b"
re = Regexp.new(Regexp.escape(word))
p(Regexp.match?(re, "a.b")) # => true
p(Regexp.match?(re, "axb")) # => false
21.3 source
Regexp.source(x)
正規表現のソース(String)。フラグは含みません。埋め込みは展開済みの形です。
p(Regexp.source(/\d+/)) # => "\\d+"
p(Regexp.source(/a b/x)) # => "a b"
x = "c"
p(Regexp.source(/a#{x}/i)) # => "ac"
21.4 options
Regexp.options(x)
フラグをビットで表した Integer(Ruby と同じ: i が 1、x が 2、m が 4、n が 32)。
p(Regexp.options(/a/)) # => 0
p(Regexp.options(/a/i)) # => 1
p(Regexp.options(/a/imx)) # => 7
p(Regexp.options(/a/n)) # => 32
21.5 casefold?
Regexp.casefold?(x)
フラグ i が付いているとき true。
p(Regexp.casefold?(/a/i)) # => true
p(Regexp.casefold?(/a/)) # => false
21.6 encoding
Regexp.encoding(x)
正規表現のエンコーディングの名前(String)。ASCII だけのソースは “US-ASCII”、非 ASCII 文字を含むと “UTF-8”、/.../n は “US-ASCII”(バイト列として一致します)。
p(Regexp.encoding(/a/)) # => "US-ASCII"
p(Regexp.encoding(/あ/)) # => "UTF-8"
p(Regexp.encoding(/a/n)) # => "US-ASCII"
21.7 fixed_encoding?
Regexp.fixed_encoding?(x)
エンコーディングが固定されているとき true(非 ASCII 文字を含むソース)。ASCII だけのソースと /.../n は false で、どのエンコーディングの String にも一致を試せます。Ruby と同じです。
p(Regexp.fixed_encoding?(/a/)) # => false
p(Regexp.fixed_encoding?(/あ/)) # => true
21.8 timeout
Regexp.timeout(x)
その Regexp に設定された一致のタイムアウト(秒、Float)か nil。Sake ではタイムアウトを設定する手段が無いので常に nil で、--strict(レベル 2)は確かめずに使うと報告します。
p(Regexp.timeout(/a/)) # => nil
21.9 names
Regexp.names(x)
名前付きグループの名前の Array(String。現れた順、重複は 1 つ)。名前付きグループが無ければ空の Array。
p(Regexp.names(/(?<y>\d+)-(?<m>\d+)/)) # => ["y", "m"]
p(Regexp.names(/(\d+)/)) # => []
21.10 named_captures
Regexp.named_captures(x)
名前付きグループの名前(String)から、その番号の Array への Hash。同じ名前を 2 度使うと番号が 2 つ並びます。
p(Regexp.named_captures(/(?<y>\d+)-(?<m>\d+)/)) # => {"y" => [1], "m" => [2]}
p(Regexp.named_captures(/(?<a>.)(?<a>.)/)) # => {"a" => [1, 2]}
p(Regexp.named_captures(/(a)/)) # => {}
21.11 match
Regexp.match(x, String, [Integer])
String の中で最初に一致した箇所の MatchData。一致しなければ nil で、--strict(レベル 2)は確かめずに使うと報告します(if m や or raise で確かめます)。第 3 引数は探し始める位置(文字単位。省略時 0、負なら末尾から)。String.match(s, re) も同じです。Ruby と違い $~ には何も残りません。
re = /(?<y>\d+)-(?<m>\d+)/
m = Regexp.match(re, "on 2026-10")
if m
p(m["y"]) # => "2026"
end
p(Regexp.match(re, "none")) # => nil
n = Regexp.match(/\d/, "a1b2", 2) or raise("no digit")
p(n[0]) # => "2"
```ruby error m = Regexp.match(/a/, “a”) p(m[0]) # !> the operands may be nil (MatchData | nil)
## match?
`Regexp.match?(x, String, [Integer])`
一致するところがあれば true。MatchData を作らないので、一致したかだけを知りたいときはこちらが軽いです。第 3 引数は探し始める位置。
```ruby
p(Regexp.match?(/\d+/, "a1")) # => true
p(Regexp.match?(/\d+/, "abc")) # => false
p(Regexp.match?(/\d/, "1abc", 1)) # => false
21.12 =~
Regexp.=~(x, Any)
re =~ s の関数形。最初に一致した位置(Integer、文字単位)か、一致しなければ nil(--strict レベル 2 で報告)。右側は String でなければなりません。s =~ re も同じ結果で、s !~ re は一致しないとき true。Ruby と違い、名前付きグループが局所変数になることはなく、$~ も残りません。
p(/b/ =~ "abc") # => 1
p(/z/ =~ "abc") # => nil
p("abc" =~ /c/) # => 2
p("abc" !~ /z/) # => true
p(Regexp.=~(/b/, "abc")) # => 1
```ruby error
if /(?
## ==, !=
`Regexp.==(x, Any)`
`Regexp.!=(x, Any)`
2 つの Regexp のソースとフラグが同じとき true(`!=` はその否定)。右側が Regexp 以外なら等しくありません。
```ruby
p(/a/ == /a/) # => true
p(/a/ == /a/i) # => false
p(/a/ != /b/) # => true
p(/a/ == "a") # => false
21.13 union
Regexp.union(*String|Regexp|Array)
引数のどれかに一致する Regexp。String は escape した上で、Regexp はそのフラグを保ったまま | でつなぎます。引数が無ければ何にも一致しない /(?!)/。パターンは個々に並べるか、Ruby と同じく String と Regexp の Array を 1 つだけ渡します(Regexp.union(Any["a", "b"]))。他の引数と並べた Array や、String でも Regexp でもない要素は実行時に TypeError です。
p(Regexp.union("a.b", /c/i)) # => /a\.b|(?i-mx:c)/
p(Regexp.match?(Regexp.union("cat", "dog"), "hotdog")) # => true
p(Regexp.union()) # => /(?!)/
p(Regexp.union(Any["a", "b"])) # => /a|b/
p(Regexp.union(Any["a.b", /c/])) # => /a\.b|(?-mix:c)/
ruby error
p(Regexp.union("a", Any["b"])) # !> TypeError: Regexp.union: no implicit conversion of Array into String
22. MatchData
MatchData は正規表現が一致した結果です。Regexp.match(re, s) と String.match(s, re) が返し、一致しなかったときは nil なので、まず確かめてから使います(Regexp)。Ruby の $~、$1 のようなグローバルは無く、MatchData を変数に置いて添字で読みます(値と型)。
グループは Ruby と同じ番号です: 0 が一致全体、1 から順に (...) のグループ。名前付きグループ (?<name>...) は名前(String)でも読めます。Ruby と同じく、名前付きグループが 1 つでもあると、名前の無い (...) はグループとして数えません。一致に参加しなかったグループ((a)? が空振りしたとき)は nil です。
MatchData に使える演算子は添字 m[k] だけで、m[k] = v はできません(演算子と添字)。MatchData 自体は Hash のキーにはなれません。
22.1 []
MatchData.[](x, Any)
m[k] の関数形。k が Integer ならその番号のグループの String(0 は一致全体、負の値は末尾から)、String か Symbol ならその名前のグループの String です。一致に参加しなかったグループと、範囲外の番号は nil。一方、無い名前は実行時に IndexError です。戻り値は x[k] の nil(添字外れの nil)なので、--strict のレベル 2 では確かめずに使っても報告されず、レベル 3(index-nil)で報告されます。Integer と String 以外の添字は TypeError。
m = Regexp.match(/(?<y>\d+)-(?<m>\d+)?/, "ab 12- cd") or raise("no match")
p(m[0]) # => "12-"
p(m[1]) # => "12"
p(m[2]) # => nil
p(m[9]) # => nil
p(m["y"]) # => "12"
p(m[:y]) # => "12"
p(MatchData.[](m, -1)) # => nil
```ruby error
m = Regexp.match(/(?
## captures
`MatchData.captures(x)`
グループ 1 以降の String の Array(一致全体は含みません)。参加しなかったグループは nil で、要素の型は String か nil です。
```ruby
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.captures(m)) # => ["12", nil]
22.2 named_captures
MatchData.named_captures(x)
名前付きグループの名前(String)から一致した String(参加しなければ nil)への Hash。名前付きグループが無ければ空の Hash。
m = Regexp.match(/(?<y>\d+)-(?<m>\d+)?/, "12-") or raise("no match")
p(MatchData.named_captures(m)) # => {"y" => "12", "m" => nil}
22.3 names
MatchData.names(x)
名前付きグループの名前の Array(Regexp.names と同じ)。
m = Regexp.match(/(?<y>\d+)-(?<m>\d+)?/, "12-") or raise("no match")
p(MatchData.names(m)) # => ["y", "m"]
22.4 to_a
MatchData.to_a(x)
一致全体とすべてのグループの Array(m[0]、m[1]、…)。参加しなかったグループは nil。
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.to_a(m)) # => ["12-", "12", nil]
22.5 to_s
MatchData.to_s(x)
一致全体の String(m[0] と同じですが、nil にはなりません)。
m = Regexp.match(/\d+/, "ab 12 cd") or raise("no match")
p(MatchData.to_s(m)) # => "12"
22.6 values_at
MatchData.values_at(x, *Integer)
指定した番号のグループの String を並べた Array(Ruby の Array.values_at と同じ要領)。参加しなかったグループと範囲外の番号は nil。番号は Integer だけで、名前は取れません(静的に type の問題)。
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.values_at(m, 0, 2, 9)) # => ["12-", nil, nil]
p(MatchData.values_at(m)) # => []
22.7 match
MatchData.match(x, Integer)
番号のグループの String(参加しなければ nil。「外れの nil」(index-nil)なので --strict レベル 3 でのみ報告)。m[i] との違いは、範囲外の番号が nil ではなく実行時に IndexError になることです。
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.match(m, 1)) # => "12"
p(MatchData.match(m, 2)) # => nil
```ruby error m = Regexp.match(/(a)/, “a”) or raise(“no match”) p(MatchData.match(m, 5)) # !> IndexError: MatchData.match: index 5 out of matches
## match_length
`MatchData.match_length(x, Integer)`
番号のグループが一致した文字数(Integer)。参加しなければ nil(「外れの nil」、`index-nil`、レベル 3)、範囲外の番号は `IndexError`。
```ruby
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.match_length(m, 1)) # => 2
p(MatchData.match_length(m, 2)) # => nil
22.8 pre_match, post_match
MatchData.pre_match(x)
MatchData.post_match(x)
一致した箇所より前の String と後の String(Ruby の $` と $')。
m = Regexp.match(/\d+/, "ab 12 cd") or raise("no match")
p(MatchData.pre_match(m)) # => "ab "
p(MatchData.post_match(m)) # => " cd"
22.9 begin, end
MatchData.begin(x, Integer)
MatchData.end(x, Integer)
番号のグループが一致した始めの位置と終わりの位置(文字単位の Integer。end は最後の文字の次)。参加しなかったグループでは nil です。結果の型は Integer | nil で、この nil は「外れの nil」(index-nil)なので --strict レベル 3 でのみ報告されます(bytebegin、byteend、offset、byteoffset、match、match_length の nil も同じです)。範囲外の番号は IndexError(index 5 out of matches)で、rescue できます。
m = Regexp.match(/(\d+)-(\d+)?/, "ab 12-") or raise("no match")
p(MatchData.begin(m, 0)) # => 3
p(MatchData.end(m, 0)) # => 6
p(MatchData.begin(m, 1)) # => 3
p(MatchData.end(m, 1)) # => 5
p(MatchData.begin(m, 2)) # => nil
b = MatchData.begin(m, 2)
if b
p(b)
else
puts("group 2 did not take part") # => group 2 did not take part
end
```ruby error m = Regexp.match(/(a)/, “a”) or raise(“no match”) p(MatchData.begin(m, 5)) # !> IndexError: MatchData.begin: index 5 out of matches
## offset
`MatchData.offset(x, Integer)`
番号のグループの `[begin, end]` の Tuple。参加しなかったグループは `[nil, nil]`、範囲外の番号は `IndexError`。
```ruby
m = Regexp.match(/(\d+)-(\d+)?/, "ab 12-") or raise("no match")
p(MatchData.offset(m, 1)) # => [3, 5]
p(MatchData.offset(m, 2)) # => [nil, nil]
```ruby error m = Regexp.match(/(a)/, “a”) or raise(“no match”) p(MatchData.offset(m, 5)) # !> IndexError: MatchData.offset: index 5 out of matches
## bytebegin, byteend
`MatchData.bytebegin(x, Integer)`
`MatchData.byteend(x, Integer)`
`begin`、`end` のバイト単位の版(Integer)。マルチバイト文字を含む String で文字の位置と違ってきます。参加しなかったグループは nil(「外れの nil」、`index-nil`、レベル 3)、範囲外の番号は `IndexError`。
```ruby
m = Regexp.match(/(い)/, "あいう") or raise("no match")
p(MatchData.begin(m, 1)) # => 1
p(MatchData.bytebegin(m, 1)) # => 3
p(MatchData.byteend(m, 1)) # => 6
22.10 byteoffset
MatchData.byteoffset(x, Integer)
offset のバイト単位の版: [bytebegin, byteend] の Tuple。参加しなかったグループは [nil, nil]。
m = Regexp.match(/(い)/, "あいう") or raise("no match")
p(MatchData.byteoffset(m, 1)) # => [3, 6]
p(MatchData.offset(m, 1)) # => [1, 2]
22.11 length, size
MatchData.length(x)
MatchData.size(x)
グループの個数 + 1(一致全体を含む。to_a の長さ)。
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.size(m)) # => 3
p(MatchData.length(m)) # => 3
22.12 regexp
MatchData.regexp(x)
この一致を作った Regexp。
m = Regexp.match(/\d+/i, "12") or raise("no match")
p(MatchData.regexp(m)) # => /\d+/i
22.13 string
MatchData.string(x)
一致を試した String 全体。凍結されていて、String.upcase! のようなその場の変更は TypeError です。
m = Regexp.match(/\d+/, "ab 12 cd") or raise("no match")
p(MatchData.string(m)) # => "ab 12 cd"
23. Array
Array は長さが変わる並びです。Any[1, 2, 3] が Array を作ります。Ruby の [1, 2, 3] は Array ですが、Sake のリテラル [1, 2, 3] は長さ固定の Tuple で、Array.push などに渡すと静的に拒否されます(値と型)。Integer[1, 2]、String[]、Point[p] のような型付き Array は、要素の型が宣言された Array で、Array.push・append・unshift・prepend・insert・concat・map!・fill・a[i] = v などの書き込みが毎回検査されます(違う型は静的に type の問題、実行時は TypeError)。型付き Array にも、この章の操作がすべて使えます。map・select・sort・first(a, n)・+ などが返す新しい Array には要素の型の宣言はありません。dup だけが要素の型を保ちます。
検査器は、1 つの Array(構築した場所)に対してプログラム全体で 1 つの要素型を与えます。Array.map! などの書き換えで別の型を入れると、以後その Array は両方の型を持つものとして扱われ、片方の型にしか合わない操作が type(partial)になります(値と型)。
要素が無いことを nil で表す操作が 2 種類あります。「外した nil」(x[k]、first、last、pop、shift、min、max、minmax、min_by、max_by、minmax_by、at、slice、slice!、dig、sample、delete_at。空の Array や範囲外の添字)を未検査で使うのは --strict=3 の index-nil の問題で、--strict(レベル 2)は見逃します。find、index、find_index、rindex、bsearch、delete、uniq! など、それ以外の nil はレベル 2 の nil の問題です(概要)。
Array に使える演算子は +、-、*、==、!=、<、<=、>、>=、<=>(2 つの Array を辞書順に比べる)と添字 a[i]、a[i, n]、a[range]、a[i] = v です。Array.+(x, y) などはその演算子を関数の形で呼ぶものです(演算子と添字)。要素を比べる操作(sort、min、max、< など)は、要素が互いに比べられる型でなければなりません。Integer と Float は混ぜて比べられ、Tuple は要素ごとに、クラスのインスタンスはその型の <=>(include Comparable)で比べます。
ブロックを取る操作は Ruby と同じ形で書きます(Array.map(xs) { |x| x * 2 })。Ruby の Enumerator(ブロック無しの each など)はありません。ブロックが省ける操作(each_with_index、each_slice、each_cons)は、ブロック無しでは結果を Array で返します。
23.1 Array[]
Array[*Any]
Array の Array を作ります。どの T でも T[...] が T の Array を作るのと同じで、各要素は Array でなければならず、書き込みのたびに検査されます(別の型のリテラルは静的なエラー、その他の値は type の問題、実行時は TypeError)。何でも入る Array は Any[...] です(Kernel)。
rows = Array[Any[1, 2], Any[3]]
Array.push(rows, Any[4, 5])
p(rows) # => [[1, 2], [3], [4, 5]]
```ruby error p(Array[1]) # !> Array[]: element 1 must be Array, got Integer
## new
`Array.new(Integer, [Any]) [{ }]`
長さ `n` の新しい Array を作ります。`Array.new(n)` は nil が n 個、`Array.new(n, v)` は同じ値 `v` が n 個(同じ 1 つの値を共有します。`Array.new(2, Any[])` の 2 要素は同じ Array です)、`Array.new(n) { |i| ... }` は添字 i ごとにブロックの値です。要素の型は nil、`v` の型、またはブロックの結果の型です。負の `n` は `ArgumentError`。
```ruby
p(Array.new(3)) # => [nil, nil, nil]
p(Array.new(2, "a")) # => ["a", "a"]
p(Array.new(3) { |i| i * i }) # => [0, 1, 4]
rows = Array.new(2, Any[])
Array.push(Array.fetch(rows, 0), 1)
p(rows) # => [[1], [1]]
rows2 = Array.new(2) { |i| Any[] }
Array.push(Array.fetch(rows2, 0), 1)
p(rows2) # => [[1], []]
```ruby error Array.new(-1) # !> ArgumentError: Array.new: negative size -1
## dup
`Array.dup(x)`
同じ要素を持つ新しい Array を返します(浅いコピー。要素は共有します)。型付き Array の複製は同じ要素の型を持つ型付き Array です。この章の他の操作が返す新しい Array と違い、要素型の宣言を保つのはこの操作だけです。
```ruby
xs = Integer[1, 2]
ys = Array.dup(xs)
Array.push(ys, 3)
p(xs) # => [1, 2]
p(ys) # => [1, 2, 3]
```ruby error ys = Array.dup(Integer[1]) Array.push(ys, “s”) # !> Array.push: an element must be Integer, but is String
## to_a
`Array.to_a(x)`
主語そのもの(同じ Array)を返します。コピーではありません。`Tuple.to_a` や `Set.to_a`、`Hash.to_a` など他の型から Array を得る操作と形を揃えるためのものです。
```ruby
xs = Any[1, 2]
p(Array.to_a(xs)) # => [1, 2]
p(Kernel.equal?(Array.to_a(xs), xs)) # => true
23.2 length, size
Array.length(x)
Array.size(x)
要素の個数(Integer)。
p(Array.size(Any[3, 1, 2])) # => 3
p(Array.length(Any[])) # => 0
23.3 empty?
Array.empty?(x)
要素が 1 つも無いとき true。
p(Array.empty?(Any[])) # => true
p(Array.empty?(Any[1])) # => false
23.4 []
Array.[](x, Any, [Integer])
a[i]、a[i, n]、a[range] の関数形。a[i] は位置 i(Integer。負の値は末尾から)の要素で、範囲外なら nil です。その nil は「外した nil」で、未検査で使うのは --strict=3 の index-nil の問題(レベル 2 では報告されません)。a[i, n] は位置 i から n 個、a[range] はその範囲の要素を新しい Array で返し、開始位置が末尾を越えているときは nil です。例外にしたいときは Array.fetch を使います。添字が Integer でも Range でもない値は TypeError。
xs = Any[3, 1, 2]
p(xs[1]) # => 1
p(xs[-1]) # => 2
p(xs[9]) # => nil
p(xs[1, 2]) # => [1, 2]
p(xs[0..1]) # => [3, 1]
p(xs[5, 1]) # => nil
p(Array.[](xs, 0)) # => 3
p(Array.[](xs, 0, 2)) # => [3, 1]
23.5 []=
Array.[]=(x, Any, Any)
a[i] = v の関数形。位置 i の要素を v に置き換え、v を返します。末尾より先に書くと、Ruby と同じく間が nil で埋まります。型付き Array では v の型が検査され(静的に type、実行時は TypeError)、nil は要素になれないので末尾より先への書き込みは IndexError です。負の添字が先頭より前なら IndexError。
xs = Any[1]
xs[3] = 2
p(xs) # => [1, nil, nil, 2]
xs[-1] = 5
p(xs) # => [1, nil, nil, 5]
p(Array.[]=(xs, 0, 0)) # => 0
```ruby error is = Integer[1] is[3] = 2 # !> IndexError: Array.[]=: index 3 is past the end of Integer[] (length 1); the gap would be nil
## at
`Array.at(x, Integer)`
位置 `i` の要素(`a[i]` と同じ。負の値は末尾から)。範囲外は nil で、「外した nil」としてレベル 3 だけが報告します。
```ruby
xs = Any[3, 1, 2]
p(Array.at(xs, 0)) # => 3
p(Array.at(xs, -1)) # => 2
p(Array.at(xs, 9)) # => nil
23.6 fetch
Array.fetch(x, Integer, [Any])
位置 i の要素を返します。範囲外のときは、default があればそれを、無ければ IndexError を上げます。結果が nil になることはないので(default が nil でなければ)、--strict で添字の nil 検査を避けたいときに使います。結果の型は要素型(default があればその型との和)です。
xs = Any[3, 1, 2]
p(Array.fetch(xs, 1)) # => 1
p(Array.fetch(xs, -1)) # => 2
p(Array.fetch(xs, 9, 0)) # => 0
```ruby error Array.fetch(Any[3, 1, 2], 9) # !> IndexError: Array.fetch: index 9 outside of array bounds: -3…3
## first, last
`Array.first(x, [Integer])`
`Array.last(x, [Integer])`
`first(a)` は先頭、`last(a)` は末尾の要素で、空の Array では nil(「外した nil」。レベル 3 だけが報告します)。`first(a, n)`、`last(a, n)` は先頭・末尾から n 個を新しい Array で返します(要素が足りなければあるだけ。nil にはなりません)。負の n は `ArgumentError`。
```ruby
xs = Any[3, 1, 2]
p(Array.first(xs)) # => 3
p(Array.last(xs)) # => 2
p(Array.first(xs, 2)) # => [3, 1]
p(Array.last(xs, 5)) # => [3, 1, 2]
p(Array.first(Any[])) # => nil
p(Array.last(Any[], 2)) # => []
```ruby error Array.first(Any[1], -1) # !> ArgumentError: Array.first: negative size -1
## slice
`Array.slice(x, Integer|Range, [Integer])`
`a[i]`、`a[i, n]`、`a[range]` と同じ結果を返します: `slice(a, i)` は要素か nil、`slice(a, i, n)` と `slice(a, range)` は新しい Array か nil(開始位置が末尾を越えたとき)。`a[i]` と同じく、この nil は「外した nil」で、未検査で使うのは `--strict=3` の `index-nil` の問題です(レベル 2 では報告されません)。例外にしたいときは `Array.fetch` を使います。
```ruby
xs = Any[3, 1, 2]
p(Array.slice(xs, 1)) # => 1
p(Array.slice(xs, 1, 5)) # => [1, 2]
p(Array.slice(xs, 1..)) # => [1, 2]
p(Array.slice(xs, 9)) # => nil
y = Array.slice(xs, 0)
p(y + 1) if y # => 4
23.7 dig
Array.dig(x, Integer, *Any)
位置 i の要素か nil(a[i] と同じ)。添字を 2 つ以上与えると、Ruby の dig のように入れ子を順に掘ります: 2 つ目以降の添字は 1 つ前で得た値に当て(Array なら Integer の位置、Hash ならそのキー)、途中で nil になればそのまま nil です。途中の値は Array、Tuple、Hash のどれかでなければならず、他の型(Integer、String、Record など)は静的に type の問題、検査を通さずに実行すれば TypeError(Integer cannot be dug into (not an Array, a Tuple, or a Hash))です。Array や Tuple に Integer 以外の添字を当てると TypeError。nil は「外した nil」で、レベル 3 だけが報告します。
xs = Any[Any[1, 2], Any[3]]
p(Array.dig(xs, 0)) # => [1, 2]
p(Array.dig(xs, 5)) # => nil
p(Array.dig(xs, 0, 1)) # => 2
p(Array.dig(xs, 1, 5)) # => nil
p(Array.dig(xs, 5, 0)) # => nil
p(Array.dig(Any[Hash.of(a: 1)], 0, :a)) # => 1
p(Array.dig(Any[[1, "x"]], 0, 1)) # => "x"
p(Array.dig(Any[[1, "x"]], 0, 5)) # => nil
```ruby error p(Array.dig(Any[Any[1, 2]], 0, 1, 2)) # !> Array.dig: the value must be Array|Hash|Tuple, but is Integer
```ruby error
p(Array.dig(Any[Any[1]], 0, "k")) # !> TypeError: Array.dig: an index into Array must be Integer, got String
23.8 values_at
Array.values_at(x, *Integer)
与えた位置の要素を並べた新しい Array。範囲外の位置は nil になります(要素型は要素型と nil の和)。
xs = Any[3, 1, 2]
p(Array.values_at(xs, 0, 2, 7)) # => [3, 2, nil]
23.9 fetch_values
Array.fetch_values(x, *Integer)
与えた位置の要素を並べた新しい Array。範囲外の位置があれば IndexError(nil にはなりません)。
p(Array.fetch_values(Any[3, 1, 2], 0, 2)) # => [3, 2]
```ruby error Array.fetch_values(Any[3, 1, 2], 0, 9) # !> IndexError: Array.fetch_values: index 9 outside of array bounds: -3…3
## push, append
`Array.push(x, *Any)`
`Array.append(x, *Any)`
末尾に値を追加して(何個でも)、主語を返します。その場で変更します。型付き Array では各値の型を検査します(静的に `type`、実行時は `TypeError`)。検査器は追加された値の型をその Array の要素型に加えます。Ruby の `<<` はありません。
```ruby
xs = Any[1]
p(Array.push(xs, 2, 3)) # => [1, 2, 3]
p(Array.append(xs, 4)) # => [1, 2, 3, 4]
```ruby error Array.push(Integer[1, 2], “x”) # !> Array.push: an element must be Integer, but is String
## unshift, prepend
`Array.unshift(x, *Any)`
`Array.prepend(x, *Any)`
先頭に値を追加して(何個でも、与えた順のまま)、主語を返します。その場で変更します。型付き Array では型を検査します。
```ruby
xs = Any[3]
p(Array.unshift(xs, 1, 2)) # => [1, 2, 3]
p(Array.prepend(xs, 0)) # => [0, 1, 2, 3]
23.10 insert
Array.insert(x, Integer, *Any)
位置 i の前に値を差し込み(何個でも)、主語を返します。i が長さと等しければ末尾に追加します。負の i は末尾から数えます(-1 なら末尾に追加、-size - 1 なら先頭)。i が長さより大きいとき、Ruby は間を nil で埋めますが、Sake では IndexError です(型付きでない Array でも。nil を黙って作らないためです)。-size - 1 より小さい負の i(先頭より前)も IndexError。型付き Array では差し込む値の型を検査します(静的に type、実行時は TypeError)。
xs = Any[1, 2, 3]
p(Array.insert(xs, 1, 9, 9)) # => [1, 9, 9, 2, 3]
p(Array.insert(xs, -2, 7)) # => [1, 9, 9, 2, 7, 3]
p(Array.insert(xs, 6, 8)) # => [1, 9, 9, 2, 7, 3, 8]
```ruby error Array.insert(Any[1], 3, 2) # !> IndexError: Array.insert: index 3 is past the end of the Array (length 1); the gap would be nil
```ruby error
Array.insert(Any[1, 2], -4, 0) # !> IndexError: Array.insert: index -4 is before the start of the Array (length 2)
```ruby error Array.insert(Integer[1], 0, “a”) # !> Array.insert: an element must be Integer, but is String
## concat
`Array.concat(x, Array)`
もう 1 つの Array の要素をすべて末尾に追加して、主語を返します。その場で変更します(`+` は新しい Array を作ります)。型付き Array では要素の型を検査します。
```ruby
xs = Any[1]
p(Array.concat(xs, Any[2, 3])) # => [1, 2, 3]
```ruby error Array.concat(Integer[1], Any[“a”]) # !> Array.concat: an element must be Integer, but is String
## fill
`Array.fill(x, Any)`
すべての要素を `v` に置き換え、主語を返します(長さは変わりません)。Ruby の範囲やブロックの形はありません。型付き Array では `v` の型を検査します(静的に `type`、検査を通らずに実行すれば `TypeError`)。
```ruby
xs = Any[1, 2, 3]
p(Array.fill(xs, 0)) # => [0, 0, 0]
```ruby error Array.fill(Integer[1], “a”) # !> Array.fill: an element must be Integer, but is String
## replace
`Array.replace(x, Array)`
主語の内容をもう 1 つの Array の要素で置き換え、主語を返します(`a = other` と違い、同じ Array を共有している場所すべてに見えます)。型付き Array では要素の型を検査します(静的に `type`、実行時は `TypeError`)。
```ruby
xs = Any[1, 2, 3]
ys = xs
p(Array.replace(xs, Any[9])) # => [9]
p(ys) # => [9]
```ruby error Array.replace(Integer[1], Any[“a”]) # !> Array.replace: an element must be Integer, but is String
## pop
`Array.pop(x)`
末尾の要素を取り除いて返します。空の Array では nil(「外した nil」。レベル 3 だけが報告します)。Ruby の `pop(n)` はありません。
```ruby
xs = Any[1, 2]
p(Array.pop(xs)) # => 2
p(xs) # => [1]
p(Array.pop(Any[])) # => nil
23.11 shift
Array.shift(x, [Integer])
shift(a) は先頭の要素を取り除いて返し、空の Array では nil(「外した nil」。レベル 3 だけが報告します)。shift(a, n) は先頭から n 個を取り除いて新しい Array で返します(足りなければあるだけ。nil にはなりません)。負の n は ArgumentError。
xs = Any[1, 2, 3, 4]
p(Array.shift(xs)) # => 1
p(Array.shift(xs, 2)) # => [2, 3]
p(xs) # => [4]
p(Array.shift(Any[])) # => nil
p(Array.shift(Any[], 2)) # => []
23.12 delete
Array.delete(x, Any)
v と == で等しい要素をすべて取り除き、v を返します。1 つも無ければ nil(レベル 2 の nil の問題)。
xs = Any[1, 2, 1, 3]
p(Array.delete(xs, 1)) # => 1
p(xs) # => [2, 3]
p(Array.delete(xs, 42)) # => nil
23.13 delete_at
Array.delete_at(x, Integer)
位置 i の要素を取り除いて返します。範囲外なら nil(「外した nil」。レベル 3 だけが報告します)。
xs = Any[1, 2, 3]
p(Array.delete_at(xs, 1)) # => 2
p(xs) # => [1, 3]
p(Array.delete_at(xs, 99)) # => nil
23.14 slice!
Array.slice!(x, Integer|Range, [Integer])
slice と同じものを返し、同時にそれを主語から取り除きます: slice!(a, i) は要素か nil、slice!(a, i, n) と slice!(a, range) は新しい Array か nil。nil は「外した nil」で、レベル 3 だけが報告します。
xs = Any[1, 2, 3, 4]
p(Array.slice!(xs, 1, 2)) # => [2, 3]
p(xs) # => [1, 4]
p(Array.slice!(xs, 0)) # => 1
p(Array.slice!(xs, 5)) # => nil
p(Array.slice!(xs, 0..0)) # => [4]
p(xs) # => []
23.15 clear
Array.clear(x)
すべての要素を取り除き、主語(空になった Array)を返します。
xs = Any[1, 2]
p(Array.clear(xs)) # => []
p(xs) # => []
23.16 each, each_entry
Array.each(x) { }
Array.each_entry(x) { }
各要素を順にブロックに渡し、主語を返します。ブロックは必須です(Ruby の Enumerator を返す形はありません)。break v でその場で終わり、v が結果になります。each_entry は each と同じです。
xs = Any[1, 2, 3]
r = Array.each(xs) { |x| puts(x) }
# => 1
# => 2
# => 3
p(r) # => [1, 2, 3]
p(Array.each(xs) { |x| break x if x == 2 }) # => 2
23.17 each_with_index
Array.each_with_index(x) [{ }]
ブロックに要素と添字(0 から)を渡し、主語を返します。ブロック無しでは Ruby の Enumerator ではなく、[要素, 添字] の Tuple を並べた新しい Array を返します。
xs = Any["a", "b"]
Array.each_with_index(xs) { |s, i| puts("#{i}:#{s}") }
# => 0:a
# => 1:b
p(Array.each_with_index(xs)) # => [["a", 0], ["b", 1]]
23.18 each_index
Array.each_index(x) { }
添字 0 から長さ-1 までを順にブロックに渡し、主語を返します。
xs = Any["a", "b"]
Array.each_index(xs) { |i| puts(i) }
# => 0
# => 1
23.19 reverse_each
Array.reverse_each(x) { }
末尾から順に各要素をブロックに渡し、主語を返します。
Array.reverse_each(Any[1, 2, 3]) { |x| puts(x) }
# => 3
# => 2
# => 1
23.20 each_slice, each_cons
Array.each_slice(x, Integer) [{ }]
Array.each_cons(x, Integer) [{ }]
each_slice(a, n) は要素を n 個ずつに切った Array を、each_cons(a, n) は連続する n 個の窓を順にブロックに渡し、主語を返します。ブロック無しでは Ruby の Enumerator ではなく、それらの Array を並べた新しい Array を返します(each_cons で n が長さを越えると [])。n は 1 以上でなければならず、0 や負の n は ArgumentError です。
xs = Any[1, 2, 3]
p(Array.each_slice(xs, 2)) # => [[1, 2], [3]]
p(Array.each_cons(xs, 2)) # => [[1, 2], [2, 3]]
Array.each_slice(xs, 2) { |s| p(s) }
# => [1, 2]
# => [3]
p(Array.each_cons(xs, 2) { |s| s }) # => [1, 2, 3]
p(Array.each_cons(xs, 5)) # => []
```ruby error Array.each_slice(Any[1], 0) # !> ArgumentError: Array.each_slice: invalid slice size
## each_with_object
`Array.each_with_object(x, Any) { }`
各要素と `memo` をブロックに渡し(この順)、最後に `memo` を返します。`memo` の型が結果の型です。`reduce` と違い、ブロックの値は使いません(`memo` をその場で変更して溜めます)。
```ruby
xs = Any[1, 2, 3]
p(Array.each_with_object(xs, Any[]) { |x, acc| Array.push(acc, x * 2) }) # => [2, 4, 6]
23.21 cycle
Array.cycle(x, Integer) { }
全要素を n 回繰り返してブロックに渡し、nil を返します。Ruby の無限に繰り返す形(引数無し)はなく、n は必須です。n が 0 以下なら何もしません。
p(Array.cycle(Any[1, 2], 2) { |x| puts(x) })
# => 1
# => 2
# => 1
# => 2
# => nil
23.22 map, collect
Array.map(x) { }
Array.collect(x) { }
各要素にブロックを適用した結果を並べた新しい Array。要素の型はブロックの結果の型で、要素型の宣言はありません(型付き Array から作っても、何でも追加できます)。
xs = Any[1, 2, 3]
p(Array.map(xs) { |x| x * 2 }) # => [2, 4, 6]
p(Array.collect(xs) { |x| Integer.to_s(x) }) # => ["1", "2", "3"]
p(xs) # => [1, 2, 3]
23.23 map!, collect!
Array.map!(x) { }
Array.collect!(x) { }
各要素をブロックの値で置き換え、主語を返します。その場で変更します。検査器は 1 つの Array に 1 つの要素型を与えるので、元と違う型を入れると、以後その Array は両方の型を持つものとして扱われ、片方にしか合わない操作が type の問題になります。同じ型への写像に使い、型を変えるなら map(新しい Array)を使います。型付き Array では結果の型を検査します(静的に type、実行時は TypeError)。
xs = Any[1, 2, 3]
p(Array.map!(xs) { |x| x * 2 }) # => [2, 4, 6]
p(Array.collect!(xs) { |x| x + 1 }) # => [3, 5, 7]
p(xs) # => [3, 5, 7]
```ruby error xs = Any[1, 2, 3] Array.map!(xs) { |x| Integer.to_s(x) } p(Array.sum(xs)) # !> Array.sum: an element must be Integer|Float|Rational|Complex, but can be String
## flat_map
`Array.flat_map(x) { }`
各要素にブロックを適用し、その結果の並びをつなげた新しい Array。ブロックは Array か Tuple を返さなければならず、他の値は実行時に `TypeError` です。Ruby のように Array 以外の値をそのまま並べることはありません。
```ruby
p(Array.flat_map(Any[1, 2]) { |x| Any[x, x * 10] }) # => [1, 10, 2, 20]
p(Array.flat_map(Any[1, 2]) { |x| [x, x] }) # => [1, 1, 2, 2]
```ruby error Array.flat_map(Any[1]) { |x| x } # !> TypeError: Array.flat_map: the block must return an Array or a Tuple, got Integer
## filter_map
`Array.filter_map(x) { }`
各要素にブロックを適用し、nil と false 以外の結果を並べた新しい Array(`map` と `compact` を合わせたもの。要素型から nil は除かれます)。
```ruby
xs = Any[1, 2, 3, 4]
p(Array.filter_map(xs) { |x| x * 10 if x > 2 }) # => [30, 40]
p(Array.filter_map(xs) { |x| x > 2 }) # => [true, true]
23.24 select, filter, find_all
Array.select(x) { }
Array.filter(x) { }
Array.find_all(x) { }
ブロックが真(nil と false 以外)を返した要素を並べた新しい Array。
xs = Any[1, 2, 3, 4]
p(Array.select(xs) { |x| x > 2 }) # => [3, 4]
p(Array.filter(xs) { |x| x > 2 }) # => [3, 4]
p(Array.find_all(xs) { |x| x > 2 }) # => [3, 4]
23.25 select!, filter!, keep_if
Array.select!(x) { }
Array.filter!(x) { }
Array.keep_if(x) { }
ブロックが真を返した要素だけを残します(その場で変更)。keep_if は常に主語を返します。select!、filter! は 1 つも取り除かなかったとき nil を返し(Ruby と同じ)、その結果を未検査で使うのはレベル 2 の nil の問題です。
xs = Any[1, 2, 3]
p(Array.select!(xs) { |x| x > 1 }) # => [2, 3]
p(Array.select!(xs) { |x| x > 1 }) # => nil
p(Array.keep_if(xs) { |x| x > 1 }) # => [2, 3]
p(Array.keep_if(xs) { |x| x > 2 }) # => [3]
p(xs) # => [3]
23.26 reject
Array.reject(x) { }
ブロックが真を返した要素を除いた新しい Array。
p(Array.reject(Any[1, 2, 3, 4]) { |x| x > 2 }) # => [1, 2]
23.27 reject!, delete_if
Array.reject!(x) { }
Array.delete_if(x) { }
ブロックが真を返した要素を取り除きます(その場で変更)。delete_if は常に主語を返します。reject! は 1 つも取り除かなかったとき nil を返し(Ruby と同じ)、その結果を未検査で使うのはレベル 2 の nil の問題です。
xs = Any[1, 2, 3]
p(Array.reject!(xs) { |x| x > 2 }) # => [1, 2]
p(Array.reject!(xs) { |x| x > 2 }) # => nil
p(Array.delete_if(xs) { |x| x > 5 }) # => [1, 2]
p(Array.delete_if(xs) { |x| x > 1 }) # => [1]
23.28 partition
Array.partition(x) { }
ブロックが真を返した要素の Array と、残りの Array の 2 要素 Tuple [selected, rest] を返します。多重代入で受けます。
evens, odds = Array.partition(Any[1, 2, 3, 4]) { |x| Integer.even?(x) }
p(evens) # => [2, 4]
p(odds) # => [1, 3]
23.29 group_by
Array.group_by(x) { }
ブロックの値をキーとし、そのキーになった要素の Array を値とする Hash を返します(最初に現れた順)。キーは Hash のキーになれる値でなければなりません(Regexp などは TypeError。値と型)。
p(Array.group_by(Any[1, 2, 3, 4]) { |x| x % 2 }) # => {1 => [1, 3], 0 => [2, 4]}
23.30 chunk_while, slice_when
Array.chunk_while(x) { }
Array.slice_when(x) { }
隣り合う 2 要素 (a, b) をブロックに渡して並びを切り分け、切り分けた Array を並べた新しい Array を返します。chunk_while はブロックが真の間を同じ塊にまとめ、slice_when はブロックが真のところで切ります。
xs = Any[1, 2, 4, 5, 7]
p(Array.chunk_while(xs) { |a, b| b == a + 1 }) # => [[1, 2], [4, 5], [7]]
p(Array.slice_when(xs) { |a, b| b != a + 1 }) # => [[1, 2], [4, 5], [7]]
23.31 take, drop
Array.take(x, Integer)
Array.drop(x, Integer)
take(a, n) は先頭 n 個、drop(a, n) は先頭 n 個を除いた残りを新しい Array で返します(足りなければあるだけ)。負の n は ArgumentError。
xs = Any[1, 2, 3, 4]
p(Array.take(xs, 2)) # => [1, 2]
p(Array.drop(xs, 2)) # => [3, 4]
p(Array.take(xs, 9)) # => [1, 2, 3, 4]
23.32 take_while, drop_while
Array.take_while(x) { }
Array.drop_while(x) { }
先頭からブロックが真を返す間の要素を取る(take_while)、または捨てて残りを返す(drop_while)新しい Array。
xs = Any[1, 2, 3, 4]
p(Array.take_while(xs) { |x| x < 3 }) # => [1, 2]
p(Array.drop_while(xs) { |x| x < 3 }) # => [3, 4]
23.33 include?
Array.include?(x, Any)
v と == で等しい要素があるとき true。クラスのインスタンスはその型の等しさで比べます(演算子と添字)。
p(Array.include?(Any[1, 2], 2)) # => true
p(Array.include?(Any[1, 2], nil)) # => false
p(Array.include?(Any[[1, 2]], [1, 2])) # => true
23.34 index, rindex
Array.index(x, Any)
Array.rindex(x, Any)
v と == で等しい最初(index)・最後(rindex)の要素の位置(Integer)。無ければ nil で、レベル 2 の nil の問題です。Ruby のブロック形はなく、条件で探すなら find_index。
xs = Any[3, 1, 4, 1]
p(Array.index(xs, 1)) # => 1
p(Array.rindex(xs, 1)) # => 3
p(Array.index(xs, 9)) # => nil
```ruby error i = Array.index(Any[1, 2], 2) p(i + 1) # !> the operands may be nil
## find_index
`Array.find_index(x) { }`
ブロックが真を返す最初の要素の位置(Integer)。無ければ nil(レベル 2 の `nil` の問題)。
```ruby
p(Array.find_index(Any[3, 1, 4]) { |x| x > 3 }) # => 2
p(Array.find_index(Any[3, 1, 4]) { |x| x > 9 }) # => nil
23.35 find, detect
Array.find(x) { }
Array.detect(x) { }
ブロックが真を返す最初の要素。無ければ nil で、レベル 2 の nil の問題です(空の Array でも nil です。要素が無いことと見つからないことを区別しません)。
xs = Any[3, 1, 4]
p(Array.find(xs) { |x| x > 3 }) # => 4
p(Array.detect(xs) { |x| x > 30 }) # => nil
```ruby error y = Array.find(Any[3, 1, 4]) { |x| x > 1 } p(y + 1) # !> the operands may be nil
## rfind
`Array.rfind(x) { }`
ブロックが真を返す最後の要素。無ければ nil(レベル 2 の `nil` の問題)。Ruby 4.0 の `Array#rfind` に対応し、それより前の Ruby の上では使えません。
```ruby
p(Array.rfind(Any[3, 1, 4, 1, 5]) { |x| x < 4 }) # => 1
23.36 bsearch, bsearch_index
Array.bsearch(x) { }
Array.bsearch_index(x) { }
ソート済みの Array を二分探索し、要素(bsearch)またはその位置(bsearch_index)を返します。Ruby と同じ 2 つのモードがあります: ブロックが true/false を返すなら true になる最初の要素(find-minimum)、Integer(<=> の結果)を返すなら 0 になる要素(find-any)。見つからなければ nil(レベル 2 の nil の問題)。ソートされていない Array では結果は定まりません。
xs = Any[1, 3, 5, 7]
p(Array.bsearch(xs) { |x| x >= 4 }) # => 5
p(Array.bsearch_index(xs) { |x| x >= 4 }) # => 2
p(Array.bsearch(xs) { |x| 3 <=> x }) # => 3
p(Array.bsearch(xs) { |x| x >= 40 }) # => nil
23.37 assoc, rassoc
Array.assoc(x, Any)
Array.rassoc(x, Any)
要素が Tuple や Array の並び(組の表)から、1 番目の項目が k に等しい(assoc)、2 番目の項目が v に等しい(rassoc)最初の要素を返します。無ければ nil(レベル 2 の nil の問題)。Ruby の assoc は Array の要素だけを見ますが、Sake の組は普通 Tuple なので Tuple も見ます。
pairs = Any[[1, "a"], [2, "b"]]
p(Array.assoc(pairs, 2)) # => [2, "b"]
p(Array.rassoc(pairs, "a")) # => [1, "a"]
p(Array.assoc(pairs, 9)) # => nil
23.38 count
Array.count(x, [Any]) [{ }]
count(a) は要素の個数、count(a, v) は v と == で等しい要素の個数、count(a) { |x| ... } はブロックが真を返す要素の個数(Integer)。
xs = Any[3, 1, 4, 1]
p(Array.count(xs)) # => 4
p(Array.count(xs, 1)) # => 2
p(Array.count(xs) { |x| x > 1 }) # => 2
23.39 any?, all?, none?, one?
Array.any?(x) { }
Array.all?(x) { }
Array.none?(x) { }
Array.one?(x) { }
ブロックが真を返す要素が、1 つ以上ある(any?)、すべてである(all?)、1 つも無い(none?)、ちょうど 1 つである(one?)とき true。ブロックは必須です(Ruby のブロック無しの形やパターン引数はありません)。空の Array では all? と none? が true、any? と one? が false です。
xs = Any[3, 1, 4]
p(Array.any?(xs) { |x| x > 3 }) # => true
p(Array.all?(xs) { |x| x > 0 }) # => true
p(Array.none?(xs) { |x| x > 4 }) # => true
p(Array.one?(xs) { |x| x > 3 }) # => true
p(Array.all?(Any[]) { |x| x > 0 }) # => true
```ruby error Array.any?(Any[1]) # !> Array.any? requires a block
## sum
`Array.sum(x, [Any]) [{ }]`
要素の和。ブロックがあれば各要素をブロックの値に写してから足します。`init`(既定 0)から足し始め、空の Array では `init` そのものが結果です。要素と `init` は数(Integer、Float、Rational、Complex)か、`Arithmetic` を include して `+` を定義したクラスの値でなければならず、それ以外は静的に `type` の問題、実行時は `TypeError`(String の連結には `join` を使います)。Float の和が欲しいときは `sum(a, 0.0)` と書くと、空でも Float になります。
```ruby
p(Array.sum(Any[1, 2, 3])) # => 6
p(Array.sum(Any[])) # => 0
p(Array.sum(Any[], 0.0)) # => 0.0
p(Array.sum(Any[1, 2.5])) # => 3.5
p(Array.sum(Any[1, 2], 10)) # => 13
p(Array.sum(Any["a", "bb"]) { |s| String.size(s) }) # => 3
```ruby error Array.sum(Any[“a”, “b”]) # !> Array.sum: an element must be Integer|Float|Rational|Complex, but is String
## reduce, inject
`Array.reduce(x, Any) { }`
`Array.inject(x, Any) { }`
`init` から始めて、累積値と各要素をブロックに渡し(この順)、その値を次の累積値にして、最後の累積値を返します。Ruby と違い `init` は必須です(省くと引数の個数の静的エラー)。空の Array では `init` が結果です。Ruby の Symbol を渡す形(`inject(:+)`)はありません。結果の型は `init` の型とブロックの結果の型の和です。
```ruby
p(Array.reduce(Any[1, 2, 3], 0) { |acc, x| acc + x }) # => 6
p(Array.inject(Any[1, 2, 3], 1) { |acc, x| acc * x }) # => 6
p(Array.reduce(Any[], 0) { |acc, x| acc + x }) # => 0
p(Array.reduce(Any["a", "b"], "") { |acc, x| acc + x }) # => "ab"
```ruby error Array.reduce(Any[1, 2]) { |acc, x| acc + x } # !> wrong number of arguments for Array.reduce (given 1, expected 2)
## min, max
`Array.min(x)`
`Array.max(x)`
最小・最大の要素。空の Array では nil(「外した nil」。レベル 3 だけが報告します)。要素は互いに比べられなければなりません: 比べられない型の組(Integer と String など)が要素型にあれば静的に `type` の問題(`elements compared in order may be (Integer, String), which cannot be compared`)、要素が nil になり得れば `nil` の問題です。検査器に見えない比較の失敗(`Float.NAN` など)は実行時に `ArgumentError`。Integer と Float は混ぜて比べられます。クラスのインスタンスはその型の `<=>`(`include Comparable`)で比べます。Ruby の `min(n)` やブロック形はありません。
```ruby
xs = Any[3, 1, 4, 1, 5]
p(Array.min(xs)) # => 1
p(Array.max(xs)) # => 5
p(Array.max(Any[2, 1.5])) # => 2
p(Array.min(Any[])) # => nil
```ruby error Array.min(Any[1, “a”]) # !> Array.min: elements compared in order may be (Integer, String), which cannot be compared
```ruby error
Array.max(Any[1.0, Float.NAN]) # !> ArgumentError: Array.max: cannot compare elements of types Float
23.40 minmax
Array.minmax(x)
最小と最大の 2 要素 Tuple [min, max]。空の Array では [nil, nil] で、各位置の型は要素型と nil の和です(min、max と同じ「外した nil」で、レベル 3 だけが報告します)。比較の規則は min、max と同じで、比べられない型の組は静的に type の問題、実行時の失敗は ArgumentError。
p(Array.minmax(Any[3, 1, 4])) # => [1, 4]
p(Array.minmax(Any[])) # => [nil, nil]
lo, hi = Array.minmax(Any[3, 1, 4])
p(lo + hi) # => 5
23.41 min_by, max_by
Array.min_by(x) { }
Array.max_by(x) { }
ブロックの値が最小・最大になる要素。空の Array では nil(「外した nil」。min、max と同じくレベル 3 だけが報告します)。ブロックの値どうしが比べられなければならず、比べられない型の組は静的に type の問題、検査器に見えない失敗は実行時に ArgumentError。
ws = Any["bb", "a", "ccc"]
p(Array.min_by(ws) { |s| String.size(s) }) # => "a"
p(Array.max_by(ws) { |s| String.size(s) }) # => "ccc"
p(Array.min_by(Any[]) { |s| s }) # => nil
```ruby error Array.min_by(Any[1, “a”]) { |x| x } # !> Array.min_by: elements compared in order may be (Integer, String), which cannot be compared
## minmax_by
`Array.minmax_by(x) { }`
ブロックの値が最小になる要素と最大になる要素の 2 要素 Tuple。空の Array では `[nil, nil]`(各位置は「外した nil」で、レベル 3 だけが報告します)。比較の規則は `min_by`、`max_by` と同じです。
```ruby
lo, hi = Array.minmax_by(Any["bb", "a", "ccc"]) { |s| String.size(s) }
p([lo, hi]) # => ["a", "ccc"]
23.42 tally
Array.tally(x)
各要素をキー、その出現回数(Integer)を値とする Hash を返します(最初に現れた順)。要素は Hash のキーになれる値でなければなりません(TypeError)。
p(Array.tally(Any["a", "b", "a"])) # => {"a" => 2, "b" => 1}
23.43 sort, sort!
Array.sort(x)
Array.sort!(x)
要素を昇順に並べた新しい Array(sort)、または主語をその場で並べ替えて返します(sort!)。Ruby の比較ブロックは取れず(静的エラー)、別の順で並べるには sort_by を使います。要素は互いに比べられなければなりません: 比べられない型の組が要素型にあれば静的に type の問題、要素が nil になり得れば nil の問題(nil を除くには compact)。検査器に見えない比較の失敗(Float.NAN など)は実行時に ArgumentError。Integer と Float は混ぜて比べられ、Tuple は要素ごとに、クラスのインスタンスはその型の <=> で比べます。
xs = Any[3, 1, 2]
p(Array.sort(xs)) # => [1, 2, 3]
p(xs) # => [3, 1, 2]
p(Array.sort!(xs)) # => [1, 2, 3]
p(xs) # => [1, 2, 3]
p(Array.sort(Any[[2, "a"], [1, "b"]])) # => [[1, "b"], [2, "a"]]
```ruby error Array.sort(Any[1, 2]) { |a, b| b <=> a } # !> Array.sort does not take a block
```ruby error
Array.sort(Any[1, "a"]) # !> Array.sort: elements compared in order may be (Integer, String), which cannot be compared
```ruby error Array.sort(Any[1, nil]) # !> Array.sort: argument elements may be nil
```ruby error
Array.sort(Any[1.0, Float.NAN]) # !> ArgumentError: Array.sort: cannot compare elements of types Float
23.44 sort_by, sort_by!
Array.sort_by(x) { }
Array.sort_by!(x) { }
ブロックの値(キー)の昇順に並べた新しい Array(sort_by)、または主語をその場で並べ替えて返します(sort_by!)。キーどうしが比べられなければならず、比べられない型の組は静的に type の問題、nil になり得るキーは nil の問題、検査器に見えない失敗は実行時に ArgumentError。降順は負のキーで、複数のキーは Tuple [k1, k2] で表します(Tuple は辞書順)。
ws = Any["bb", "a", "ccc"]
p(Array.sort_by(ws) { |s| -String.size(s) }) # => ["ccc", "bb", "a"]
p(Array.sort_by(Any["bb", "a", "c"]) { |s| [String.size(s), s] }) # => ["a", "c", "bb"]
xs = Any[3, 1, 2]
p(Array.sort_by!(xs) { |x| -x }) # => [3, 2, 1]
p(xs) # => [3, 2, 1]
23.45 reverse, reverse!
Array.reverse(x)
Array.reverse!(x)
要素を逆順にした新しい Array(reverse)、または主語をその場で逆順にして返します(reverse!)。
xs = Any[1, 2, 3]
p(Array.reverse(xs)) # => [3, 2, 1]
p(xs) # => [1, 2, 3]
p(Array.reverse!(xs)) # => [3, 2, 1]
p(xs) # => [3, 2, 1]
23.46 rotate, rotate!
Array.rotate(x, [Integer])
Array.rotate!(x, [Integer])
先頭の n 個(既定 1)を末尾に回した新しい Array(rotate)、または主語をその場で回して返します(rotate!)。負の n は逆方向に回します。
xs = Any[1, 2, 3]
p(Array.rotate(xs)) # => [2, 3, 1]
p(Array.rotate(xs, 2)) # => [3, 1, 2]
p(Array.rotate(xs, -1)) # => [3, 1, 2]
p(Array.rotate!(xs)) # => [2, 3, 1]
p(xs) # => [2, 3, 1]
23.47 shuffle, shuffle!
Array.shuffle(x)
Array.shuffle!(x)
要素を無作為に並べ替えた新しい Array(shuffle)、または主語をその場で並べ替えて返します(shuffle!)。
xs = Any[1, 2, 3]
p(Array.size(Array.shuffle(xs))) # => 3
p(Array.sort(Array.shuffle!(xs))) # => [1, 2, 3]
23.48 sample
Array.sample(x)
無作為に選んだ 1 要素。空の Array では nil(「外した nil」。レベル 3 だけが報告します)。Ruby の sample(n) はありません。
p(Array.sample(Any[7])) # => 7
p(Array.sample(Any[])) # => nil
23.49 uniq, uniq!
Array.uniq(x)
Array.uniq!(x)
重複する要素(== で等しいもの。最初のものを残す)を除いた新しい Array(uniq)、または主語からその場で除いて返します(uniq!)。uniq! は 1 つも除かなかったとき nil を返し(Ruby と同じ)、その結果を未検査で使うのはレベル 2 の nil の問題です。Ruby のブロック形はありません。
p(Array.uniq(Any[1, 2, 1, 3, 2])) # => [1, 2, 3]
xs = Any[1, 1]
p(Array.uniq!(xs)) # => [1]
p(Array.uniq!(xs)) # => nil
```ruby error p(Array.size(Array.uniq!(Any[1, 1]))) # !> Array.size: argument 1 may be nil
## compact, compact!
`Array.compact(x)`
`Array.compact!(x)`
nil の要素を除いた新しい Array(`compact`。要素型から nil が除かれます)、または主語からその場で除いて返します(`compact!`)。`compact!` は nil が 1 つも無かったとき nil を返し、その結果を未検査で使うのはレベル 2 の `nil` の問題です。
```ruby
p(Array.compact(Any[1, nil, 2])) # => [1, 2]
xs = Any[1, nil]
p(Array.compact!(xs)) # => [1]
p(Array.compact!(xs)) # => nil
23.50 flatten, flatten!
Array.flatten(x)
Array.flatten!(x)
入れ子の Array をすべて展開して平らにした新しい Array(flatten)、または主語をその場で平らにして返します(flatten!)。展開するのは Array だけで、Tuple は 1 つの値なので開きません(Ruby の flatten は開くので、Tuple が要素に来ると静的に type の問題として知らせます。開くには Array.flat_map(xs) { |t| Tuple.to_a(t) })。flatten! は入れ子が無かったとき nil を返し、その結果を未検査で使うのはレベル 2 の nil の問題です。Ruby の深さ引数はありません。
p(Array.flatten(Any[1, Any[2, Any[3]], 4])) # => [1, 2, 3, 4]
xs = Any[1, Any[2]]
p(Array.flatten!(xs)) # => [1, 2]
p(Array.flatten!(xs)) # => nil
```ruby error p(Array.flatten(Any[[1, 2], [3, 4]])) # !> which flatten leaves whole
## zip
`Array.zip(x, *Array)`
主語の各要素と、他の Array の同じ位置の要素を組にした Tuple を並べた新しい Array。長さは主語の長さで、他の Array が短ければ nil で埋まり(その位置の型は要素型と nil の和)、長ければ余りは捨てます。Ruby と違い組は Array ではなく Tuple です。
```ruby
p(Array.zip(Any[1, 2, 3], Any["a", "b"])) # => [[1, "a"], [2, "b"], [3, nil]]
p(Array.zip(Any[1, 2], Any["a", "b"], Any[:x, :y])) # => [[1, "a", :x], [2, "b", :y]]
23.51 product
Array.product(x, Array)
主語の要素と他の Array の要素のすべての組み合わせ [a, b](Tuple)を並べた新しい Array。Ruby と違い、他の Array は 1 つだけで、組は Tuple です。
p(Array.product(Any[1, 2], Any["a", "b"])) # => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]
p(Array.product(Any[1], Any[])) # => []
23.52 transpose
Array.transpose(x)
Array の Array を行列と見て、行と列を入れ替えた新しい Array を返します。行は Array でも Tuple でもよく(zip の結果のような Tuple の並びも転置できます)、結果の行は Array です。行はすべて同じ長さでなければならず、長さが違えば IndexError。Array でも Tuple でもない要素は TypeError。
p(Array.transpose(Any[Any[1, 2], Any[3, 4]])) # => [[1, 3], [2, 4]]
p(Array.transpose(Array.zip(Any[1, 2], Any["a", "b"]))) # => [[1, 2], ["a", "b"]]
```ruby error Array.transpose(Any[Any[1, 2], Any[3]]) # !> IndexError: Array.transpose: element size differs (1 should be 2)
## combination, repeated_combination
`Array.combination(x, Integer)`
`Array.repeated_combination(x, Integer)`
要素から n 個を選ぶ組み合わせ(`combination`。同じ要素は 1 度まで)、または重複を許す組み合わせ(`repeated_combination`)を Array で並べた新しい Array を返します(Ruby の Enumerator ではなく Array)。n が長さを越えると `combination` は `[]`。
```ruby
p(Array.combination(Any[1, 2, 3], 2)) # => [[1, 2], [1, 3], [2, 3]]
p(Array.repeated_combination(Any[1, 2], 2)) # => [[1, 1], [1, 2], [2, 2]]
p(Array.combination(Any[1, 2], 5)) # => []
23.53 permutation, repeated_permutation
Array.permutation(x, [Integer])
Array.repeated_permutation(x, Integer)
要素から n 個を選んで並べる順列を Array で並べた新しい Array を返します。permutation(a) の n の既定は全要素です。repeated_permutation は同じ要素を繰り返して選べます(n は必須)。
p(Array.permutation(Any[1, 2, 3], 2)) # => [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
p(Array.permutation(Any[1, 2])) # => [[1, 2], [2, 1]]
p(Array.repeated_permutation(Any[1, 2], 2)) # => [[1, 1], [1, 2], [2, 1], [2, 2]]
23.54 union, intersection, difference
Array.union(x, *Array)
Array.intersection(x, *Array)
Array.difference(x, *Array)
集合のように扱った結果の新しい Array を返します(順序は主語の順、== で比べます)。union は主語と他の Array すべての要素を重複を除いて並べ(要素型は全部の和)、intersection は他の Array すべてに含まれる要素だけを重複を除いて、difference は他の Array のどれにも含まれない要素を(重複はそのまま)残します。他の Array は何個でも(0 個でも)与えられます。
a = Any[1, 2, 3]
b = Any[2, 3, 4]
p(Array.union(a, b)) # => [1, 2, 3, 4]
p(Array.union(Any[1, 1])) # => [1]
p(Array.intersection(a, b)) # => [2, 3]
p(Array.intersection(a, b, Any[3])) # => [3]
p(Array.difference(a, b)) # => [1]
p(Array.difference(Any[1, 2, 2, 3], Any[2], Any[3])) # => [1]
23.55 intersect?
Array.intersect?(x, Array)
2 つの Array に共通する要素が 1 つでもあるとき true。
p(Array.intersect?(Any[1, 2], Any[2, 3])) # => true
p(Array.intersect?(Any[1, 2], Any[9])) # => false
23.56 join
Array.join(x, [String])
各要素を to_s で文字列にして、sep(既定 "")で区切ってつなげた String。to_s は puts と同じ規則で、nil は空、Array と Tuple は inspect の形、クラスのインスタンスはその型の to_s です(値と型)。
p(Array.join(Any[1, 2, 3], ", ")) # => "1, 2, 3"
p(Array.join(Any[1, "a", nil, :s, [1, 2]])) # => "1as[1, 2]"
p(Array.join(Any[])) # => ""
23.57 to_h
Array.to_h(x)
[key, value] の Tuple の並びから Hash を作ります。要素はすべて 2 要素の Tuple でなければならず(Array は不可)、他の値は TypeError。同じキーが複数あれば後のものが残ります。キーは Hash のキーになれる値でなければなりません。Ruby のブロック形はありません。
p(Array.to_h(Any[[:a, 1], [:b, 2]])) # => {a: 1, b: 2}
p(Array.to_h(Any[[1, "a"], [1, "b"]])) # => {1 => "b"}
p(Array.to_h(Any[])) # => {}
```ruby error Array.to_h(Any[1, 2]) # !> TypeError: Array.to_h: Array.to_h needs [key, value] Tuples, got Integer
## to_set
`Array.to_set(x)`
要素を集めた新しい Set(重複は 1 つに)。要素は Set の要素になれる値でなければなりません(`TypeError`)。
```ruby
p(Array.to_set(Any[1, 2, 1])) # => Set[1, 2]
23.58 pack
Array.pack(x, String)
Ruby の Array#pack。要素を書式 fmt に従ってバイト列(String)にします。不正な書式は ArgumentError、書式に合わない要素("C*" に String など)は TypeError。逆は String.unpack。
p(Array.pack(Any[65, 66], "C*")) # => "AB"
p(Array.pack(Any[1, 2], "n*")) # => "\x00\x01\x00\x02"
```ruby error Array.pack(Any[“a”], “C”) # !> TypeError: Array.pack: no implicit conversion of String into Integer
## +, -, *
`Array.+(x, Any)`
`Array.-(x, Any)`
`Array.*(x, Any)`
`a + b` は 2 つの Array をつなげた新しい Array(要素型は和。`concat` はその場で変更します)。`a - b` は `b` に含まれる要素(`==` で等しいもの)をすべて除いた新しい Array(`difference` と同じ)。`a * n` は `a` を n 回繰り返した新しい Array で、負の n は `ArgumentError`。右側は `+`、`-` では Array、`*` では Integer でなければならず、Tuple や String(Ruby の `a * ","` は `join`)は静的に `type` の問題です。
```ruby
a = Any[1, 2, 3]
b = Any[2, 3, 4]
p(a + b) # => [1, 2, 3, 2, 3, 4]
p(a - b) # => [1]
p(Any[1, 2, 2] - Any[2]) # => [1]
p(a * 2) # => [1, 2, 3, 1, 2, 3]
p(Array.+(a, Any[9])) # => [1, 2, 3, 9]
p(Array.*(a, 0)) # => []
```ruby error p(Any[1] + [2]) # !> which the left operand’s type does not support
## ==, !=
`Array.==(x, Any)`
`Array.!=(x, Any)`
2 つの Array が同じ長さで、各位置の要素が `==` で等しいときに true(`!=` はその否定)。右側が Array 以外の値(Tuple や nil)なら等しくありません。`[1, 2]` は Tuple なので `Any[1, 2]` とは等しくありません。
```ruby
a = Any[1, 2, 3]
p(a == Any[1, 2, 3]) # => true
p(a != Any[1, 2]) # => true
p(a == [1, 2, 3]) # => false
p(a == nil) # => false
p(Array.==(a, a)) # => true
23.59 <, <=, >, >=
Array.<(x, Any)
Array.<=(x, Any)
Array.>(x, Any)
Array.>=(x, Any)
2 つの Array を辞書順に比べます: 先頭から順に要素を比べ、最初に違った位置で決まり、すべて等しければ短い方が小さい。要素は互いに比べられる型でなければならず、比べられない組は静的に type の問題(which cannot be compared)、実行時は ArgumentError。右側は Array でなければなりません(Tuple は type の問題)。
p(Any[1, 2] < Any[1, 3]) # => true
p(Any[1, 2] < Any[1, 2, 0]) # => true
p(Any[2] > Any[1, 9]) # => true
p(Any[1, 2] <= Any[1, 2]) # => true
p(Array.>=(Any[1], Any[1])) # => true
```ruby error p(Any[1, “a”] < Any[1, 2]) # !> which cannot be compared
## <=>
`Array.<=>(x, Any)`
辞書順の比較の結果を -1、0、1 で返します。比較の規則は `<` と同じです。比べられない組は静的に `type` の問題で、検査を通らずに実行すれば(`--strict=0`)nil になります(`<` などは `ArgumentError`)。
```ruby
a = Any[1, 2, 3]
p(a <=> Any[1, 2, 4]) # => -1
p(a <=> a) # => 0
p(Array.<=>(a, Any[1])) # => 1
24. Enum
Enum は Ruby の Enumerable を短い名前にした module で、prelude(sakelib/prelude.sake。どのプログラムも最初に読む)が Sake で定義しています。Enum.map(x) { } などの関数は mixin 関数で、第 1 引数の型でディスパッチします(プログラムの構造の「module の関数の呼び方」)。Array・Hash・Set・Range は Enum を include していて、自分の操作で答えます: Enum.map(xs) は xs が Array なら Array.map(xs)、Enum.each(h) { |k, v| } は Hash.each です。組み込み型には何も写されません。それ以外の型(Integer、Tuple、String)の値を渡すのは実行前に type の問題、実行時は TypeError です。Tuple は Enum を include しません(長さと位置ごとの型が決まっているので、添字と分解で扱います)。
自分のクラスは include Enum と def each(c) = ... yield(v) ... を書けば参加します。Enum の他の関数はすべて each の上に書かれているので、クラスに写され(include)、C.map(c) { }、C.count(c)、Enum.first(c) のどれでも呼べます。クラス自身が同名の関数を定義していればそれが勝ちます。
class Deck
include Enum
attr_reader cards
def each(d) = Array.each(@cards) { |c| yield(c) }
end
d = Deck.new(Any["A", "K", "Q", "J"])
p(Deck.map(d) { |c| String.downcase(c) }) # => ["a", "k", "q", "j"]
p(Enum.count(d)) # => 4
p(Enum.select(Any[3, 1, 2]) { |v| v > 1 }) # => [3, 2]
```ruby error p(Enum.map(5) { |v| v }) # !> Enum.map dispatches on its first argument, which is Integer; the types that include Enum are Array, Hash, Set, Range
結果が nil になりうる関数(`find`、`first`、`min_by`、`max_by`)は、自分のクラスの写しでは `nil | T` を返し、検査しないで使うと level 2 で報告されます。組み込み型にディスパッチしたときは、その型の操作の規則に従います(`Array.first` の「外れの nil」は level 3)。以下の各節は Enum 自身の定義を述べ、組み込み型では同名の操作([Array](Array.md)、[Hash](Hash.md)、[Set](Set.md)、[Range](Range.md))が走ります。
## each
`Enum.each(x)`
必須の関数です。Enum を include するクラスが `def each(c)` を定義し、各要素に `yield(v)` します(Hash のように 2 つ渡してもよい)。`Enum.each(x) { }` は x の型の `each` にディスパッチします。定義していないクラスは、呼び出しが届く場所で `type` の問題、実行時は `NotImplementedError` です。
```ruby
Enum.each(Hash.of(a: 1, b: 2)) { |k, v| puts("#{k}: #{v}") }
# => a: 1
# => b: 2
24.1 map
Enum.map(x) { }
各要素をブロックの値に写した Array を返します。
p(Enum.map(1..3) { |v| v * v }) # => [1, 4, 9]
p(Enum.map(Set.of(1, 2)) { |v| -v }) # => [-1, -2]
24.2 select, filter
Enum.select(x) { }
Enum.filter(x) { }
ブロックが真を返した要素の Array を返します。
p(Enum.select(Any[1, 2, 3, 4]) { |v| v % 2 == 0 }) # => [2, 4]
p(Enum.filter(1..6) { |v| v > 4 }) # => [5, 6]
24.3 reject
Enum.reject(x) { }
ブロックが偽を返した要素の Array を返します。
p(Enum.reject(Any[1, 2, 3, 4]) { |v| v % 2 == 0 }) # => [1, 3]
24.4 filter_map
Enum.filter_map(x) { }
各要素にブロックを適用し、真の結果だけを集めた Array を返します(nil と false は落ちます)。
p(Enum.filter_map(Any["1", "x", "3"]) { |s| String.to_i(s) if String.match?(s, /\A\d+\z/) }) # => [1, 3]
24.5 flat_map
Enum.flat_map(x) { }
ブロックが返す Array(か Tuple)を 1 段つなげた Array を返します。
p(Enum.flat_map(Any[1, 2]) { |v| Any[v, v * 10] }) # => [1, 10, 2, 20]
24.6 find, detect
Enum.find(x) { }
Enum.detect(x) { }
ブロックが真を返す最初の要素を返します。無ければ nil(自分のクラスでは level 2、Array.find などでは level 2)。
p(Enum.find(Any[3, 8, 5]) { |v| v > 4 }) # => 8
p(Enum.detect(1..3) { |v| v > 9 }) # => nil
24.7 reduce, inject
Enum.reduce(x, init) { }
Enum.inject(x, init) { }
init から始めて、各要素でブロックを yield(acc, v) と呼び、最後の累積値を返します。Ruby と違い、初期値は省略できません(要素型だけから結果の型が決まらないため。Array の reduce と同じ)。
p(Enum.reduce(Any[1, 2, 3], 0) { |acc, v| acc + v }) # => 6
p(Enum.inject(1..4, 1) { |acc, v| acc * v }) # => 24
24.8 sum
Enum.sum(x, [init]) [{ }]
要素の和(ブロックがあればブロックの値の和)を返します。init(既定 0)から足し始めます。組み込み型では要素は数か Arithmetic を include する型の値でなければなりません(Array)。
p(Enum.sum(1..4)) # => 10
p(Enum.sum(Any[1, 2, 3]) { |v| v * 10 }) # => 60
24.9 count
Enum.count(x) [{ }]
要素の数、ブロックがあればブロックが真を返した要素の数を返します。
p(Enum.count(Set.of(1, 2, 3))) # => 3
p(Enum.count(1..10) { |v| v % 3 == 0 }) # => 3
24.10 include?
Enum.include?(x, v)
== で等しい要素があれば true。
p(Enum.include?(1..5, 3)) # => true
p(Enum.include?(Any["a"], "b")) # => false
24.11 any?, all?, none?, one?
Enum.any?(x) [{ }]
Enum.all?(x) [{ }]
Enum.none?(x) [{ }]
Enum.one?(x) [{ }]
ブロックが真を返す要素が 1 つ以上ある / すべて / 無い / ちょうど 1 つ、のとき true。ブロックが無ければ要素自身の真偽を見ます。
xs = Any[1, 2, 3]
p(Enum.any?(xs) { |v| v > 2 }) # => true
p(Enum.all?(xs) { |v| v > 2 }) # => false
p(Enum.none?(xs) { |v| v > 5 }) # => true
p(Enum.one?(xs) { |v| v > 2 }) # => true
24.12 min_by, max_by
Enum.min_by(x) { }
Enum.max_by(x) { }
ブロックの値が最小 / 最大の要素を返します。空なら nil。ブロックの値は互いに比べられる型でなければなりません(検査器が静的に見ます)。
words = Any["pear", "fig", "banana"]
p(Enum.min_by(words) { |w| String.length(w) }) # => "fig"
p(Enum.max_by(words) { |w| String.length(w) }) # => "banana"
24.13 sort_by
Enum.sort_by(x) { }
ブロックの値の昇順に並べた Array を返します。
p(Enum.sort_by(Any["bb", "a", "ccc"]) { |s| -String.length(s) }) # => ["ccc", "bb", "a"]
24.14 to_a
Enum.to_a(x)
要素を並べた新しい Array を返します。
p(Enum.to_a(1..3)) # => [1, 2, 3]
p(Enum.to_a(Hash.of(a: 1))) # => [[:a, 1]]
24.15 first
Enum.first(x, [n])
最初の要素(空なら nil)、n を与えれば最初の n 個の Array を返します。
p(Enum.first(Any[7, 8, 9])) # => 7
p(Enum.first(1..10, 3)) # => [1, 2, 3]
24.16 take, drop
Enum.take(x, n)
Enum.drop(x, n)
最初の n 個の Array / 最初の n 個を除いた Array を返します。
p(Enum.take(1..5, 2)) # => [1, 2]
p(Enum.drop(1..5, 2)) # => [3, 4, 5]
24.17 group_by
Enum.group_by(x) { }
ブロックの値をキーに、同じ値になった要素の Array を値にした Hash を返します。
p(Enum.group_by(1..6) { |v| v % 3 }) # => {1 => [1, 4], 2 => [2, 5], 0 => [3, 6]}
24.18 partition
Enum.partition(x) { }
ブロックが真を返した要素の Array と、偽を返した要素の Array の Tuple [yes, no] を返します。
yes, no = Enum.partition(1..6) { |v| v > 3 }
p(yes) # => [4, 5, 6]
p(no) # => [1, 2, 3]
24.19 each_with_object
Enum.each_with_object(x, memo) { }
各要素でブロックを yield(v, memo) と呼び、memo を返します。
p(Enum.each_with_object(Any[1, 2], Any[]) { |v, m| Array.unshift(m, v) }) # => [2, 1]
24.20 compact
Enum.compact(x)
nil を除いた要素の Array を返します。
p(Enum.compact(Any[1, nil, 2])) # => [1, 2]
25. Tuple
Tuple は長さが固定で、位置ごとに型が決まった並びです。リテラル [1, "a"] が Tuple を作ります(Ruby の [1, "a"] は Array ですが、Sake では Any[1, "a"] が Array です。値と型)。要素の個数と各位置の型は値の型の一部で、検査器は添字の範囲と位置の型を静的に見ます。伸ばす・縮めることはできません。可変長の並びには Array を使います。
多重代入 a, b = t と Tuple パターン in [Integer, String] が Tuple を分解します。関数の return a, b は Tuple を返します。
Tuple に使える演算子は ==、!=、<、<=、>、>=、<=>(2 つの Tuple を辞書順に比べる)と添字 t[i]、t[i] = v です。ここに挙げた Tuple.<(x, y) などは、その演算子を関数の形で呼ぶものです(演算子と添字)。
25.1 Tuple[]
Tuple[*Any]
Tuple の Array を作ります(Tuple そのものではありません)。Integer[] や String[] と同じ型付き Array で、要素はすべて Tuple でなければならず、Array.push などの書き込みも毎回検査されます。Tuple 以外の要素は静的に type の問題、実行時は TypeError です。空の Tuple の Array は Tuple[] です。
pairs = Tuple[[1, "a"], [2, "b"]]
Array.push(pairs, [3, "c"])
p(pairs) # => [[1, "a"], [2, "b"], [3, "c"]]
p(Array.size(Tuple[])) # => 0
```ruby error Array.push(Tuple[], 1) # !> Array.push: an element must be Tuple, but is Integer
## []
`Tuple.[](x, Any)`
`t[i]` の関数形。位置 `i`(Integer。負の値は末尾から)の要素を返します。戻り値の型はその位置の型です。Tuple の長さは型の一部なので、範囲外の添字は Array のように nil にならず、静的に `type` の問題(`the index is outside the Tuple`)、実行時は `IndexError` です。添字に Range は使えません(`TypeError`)。
```ruby
t = [1, "a", 2.5]
p(t[0]) # => 1
p(t[-1]) # => 2.5
p(Tuple.[](t, 1)) # => "a"
```ruby error t = [1, 2] p(t[5]) # !> the index is outside the Tuple
## []=
`Tuple.[]=(x, Any, Any)`
`t[i] = v` の関数形。位置 `i` の要素を `v` に置き換え、`v` を返します。その位置の型と違う型の値は静的に `type` の問題、実行時は `TypeError` です。範囲外の添字は `IndexError`。
```ruby
t = [1, "a"]
t[0] = 10
Tuple.[]=(t, 1, "b")
p(t) # => [10, "b"]
```ruby error t = [1, 2] t[0] = “x” # !> the value must be Integer, but is String
## length, size
`Tuple.length(x)`
`Tuple.size(x)`
要素の個数(Integer)。Tuple の長さは型から分かるので、検査器にとっては定数です。
```ruby
p(Tuple.size([1, "a", 2.5])) # => 3
p(Tuple.length([])) # => 0
25.2 max, min
Tuple.max(x)
Tuple.min(x)
要素のうち最大・最小のものを返します(Ruby の [a, b].max)。各位置の型は分かっているので、要素が互いに比べられるかは静的に見ます: 比べられない組(Integer と String など)は type の問題(elements compared in order may be (Integer, String), which cannot be compared)、nil になり得る位置は nil の問題です。検査器に見えない比較の失敗(Float.NAN など)は実行時に ArgumentError。Integer と Float は混ぜて比べられます。空の Tuple [] では nil です。要素が クラスのインスタンスなら、その型の <=>(include Comparable)で比べます。結果の型は各位置の型の和で、空でない Tuple では nil になりません。
p(Tuple.max([3, 1, 2])) # => 3
p(Tuple.min([3, 1, 2])) # => 1
p(Tuple.max([2, 1.5])) # => 2
p(Tuple.max([3, 1]) + 1) # => 4
p(Tuple.max([])) # => nil
```ruby error p(Tuple.max([1, “a”])) # !> Tuple.max: elements compared in order may be (Integer, String), which cannot be compared
```ruby error
p(Tuple.max([1.0, Float.NAN])) # !> ArgumentError: Tuple.max: cannot compare elements of types Float
25.3 minmax
Tuple.minmax(x)
最小と最大の 2 要素の Tuple [min, max] を返します。比較の規則は min、max と同じです(比べられない組は静的に type の問題)。
lo, hi = Tuple.minmax([3, 1, 2])
p([lo, hi]) # => [1, 3]
25.4 to_a
Tuple.to_a(x)
同じ要素を持つ新しい Array を返します。元の Tuple とは独立で、Array の方は伸ばせます。要素の型は Tuple の各位置の型の和になります。
t = [1, "a"]
a = Tuple.to_a(t)
Array.push(a, :x)
p(a) # => [1, "a", :x]
p(t) # => [1, "a"]
25.5 ==, !=
Tuple.==(x, Any)
Tuple.!=(x, Any)
2 つの Tuple が同じ長さで、各位置の要素が == で等しいときに true(!= はその否定)。右側が Tuple 以外の値(Array や nil)なら等しくありません。Any[1, 2] と [1, 2] は型が違うので等しくありません。
p([1, 2] == [1, 2]) # => true
p([1, 2] != [1, 2, 3]) # => true
p([1, 2] == Any[1, 2]) # => false
25.6 <, <=, >, >=
Tuple.<(x, Any)
Tuple.<=(x, Any)
Tuple.>(x, Any)
Tuple.>=(x, Any)
2 つの Tuple を辞書順に比べます: 先頭から順に要素を比べ、最初に違った位置で決まり、すべて等しければ短い方が小さい。各位置の組は互いに比べられる型でなければならず、比べられない組は静的に type の問題(elements compared in order may be (Integer, String))です。右側は Tuple でなければなりません。
p([1, 2] < [1, 3]) # => true
p([2] > [1, 9]) # => true
p([1] < [1, 0]) # => true
p(Tuple.<=([1, 2], [1, 2])) # => true
```ruby error p([1, 2] < [1, “a”]) # !> which cannot be compared
## <=>
`Tuple.<=>(x, Any)`
辞書順の比較の結果を -1、0、1 で返します。比較の規則は `<` と同じです。`Array.sort_by` のキーに Tuple を返すと、この順で並びます。
```ruby
p([1, 2] <=> [1, 2]) # => 0
p([1, 2] <=> [1, 2, 3]) # => -1
p(Array.sort_by(Any["bb", "a", "c"]) { |s| [String.size(s), s] }) # => ["a", "c", "bb"]
26. Range
Range は 2 つの端と、終端を含むかどうかの印の組です: 1..5 は 5 を含み、1...5 は含みません。端は Integer、Float、String、nil のいずれかです(値と型): 1.. は終端の無い Range、..5 は始端の無い Range です。終端の無い Range を行末に書くときは r = (3..) と括弧で囲みます。Ruby と同じく、3.. の直後の改行は次の行へ続いてしまうからです。型の違う端の組(1.."a")は実行時に ArgumentError。Range は値であり、変更する操作はありません。すべての結果は新しい値です。
検査器は端の型を Range とともに持ち(Range[Integer])、その型でどの操作が使えるかが決まります:
- 値を順に辿る操作(
each、map、to_a、select、find、countなど)には Integer か String で始まる Range が要ります。"a".."e"は Ruby のString#succで進みます。それ以外の型の Range(1.0..2.0など)は静的にtypeの問題(the Range's first value must be Integer|String, but is Float)、実行時はTypeErrorです。 step、sum、sizeは Integer で始まる Range だけを取ります。String の Range はtypeの問題です。- Range 全体を使う操作(
to_a、map、sum、last、min、maxなど)には有限の Range が要ります。終端の無い Range にはRangeError(cannot do this on an endless Range 1..)を投げます。途中で止まれる操作(each、find、take、first(r, n)、each_slice、bsearch)は終端の無い Range も受け取り、eachはbreakするまで走ります。 cover?、overlap?、begin、end、exclude_end?は端だけを見るので、Float や終端の無いものも含めてどんな Range でも使えます。include?とmember?も数の Range では同じですが、String の Range は Ruby と同じく辿ります。- 始端が終端より大きい Range(
5..1)には値がありません: 辿っても何も起きず、to_aは[]、minは nil です。first、last、begin、endは端をそのまま返します。
Range に使える演算子は == と != です(2 つの Range は端と exclude_end? が同じなら等しい)。Range での添字(a[1..3]、s[0...2])は Array と String の操作です(演算子と添字)。Range は Hash のキーや Set の要素にはできません(TypeError)。nil になりうる結果(first、last、min、max、begin、end、find、find_index、bsearch、min_by、max_by)の型は T | nil で、未検査のまま使うと --strict(レベル 2)が報告します。レベル 3 の「外れの nil」に当たるのは minmax と minmax_by の Tuple の要素だけです(a, b = tuple と同じ扱い)。
操作の多くは Ruby の Enumerable のメソッドです。Ruby と違い、署名に { } のある操作のブロックは必須(例外は count と sum で、ブロックは省略可能)、reduce と inject には初期値が要り、each や map などのブロックはちょうど 1 つの引数を取ります(each_with_index、each_with_object、chunk_while、slice_when は 2 つ)。引数の数が違うブロックは実行時に ArgumentError です。
26.1 begin, end
Range.begin(x)
Range.end(x)
書かれたままの 2 つの端です(Ruby の r.begin、r.end)。end は除外される終端でも書かれた値を返します(Range.end(1...5) は 5、Range.max(1...5) は 4)。結果の型は T | nil です: 終端の無い Range の end と始端の無い Range の begin は nil なので、算術に使う前の検査を --strict が求めます。
p(Range.begin(1..5)) # => 1
p(Range.end(1...5)) # => 5
p(Range.end(1..)) # => nil
p(Range.begin("a".."z")) # => "a"
```ruby error x = Range.begin(1..5) p(x + 1) # !> the operands may be nil
## exclude_end?
`Range.exclude_end?(x)`
3 つの点で書いた Range(`1...5`)なら true、2 つ(`1..5`、`1..`)なら false。
```ruby
p(Range.exclude_end?(1..5)) # => false
p(Range.exclude_end?(1...5)) # => true
p(Range.exclude_end?(1..)) # => false
26.2 ==, !=
Range.==(x, Any)
Range.!=(x, Any)
両端が == で等しく、exclude_end? も一致するときに 2 つの Range は等しい(!= はその否定)。1..5 と 1...5 は違います。右側が Range でない値(Integer、nil)とは等しくなく、関数形も演算子と同じです: (1..5) == 5 も Range.==(1..5, 5) も false。
p((1..5) == (1..5)) # => true
p((1..5) == (1...5)) # => false
p((1..5) != (1...6)) # => true
p((1..5) == 5) # => false
p(Range.==(1..5, 1..5)) # => true
p(Range.==(1..5, 5)) # => false
p(Range.!=(1..5, nil)) # => true
26.3 include?, cover?, member?
Range.include?(x, Any)
Range.cover?(x, Any)
Range.member?(x, Any)
値が Range にあれば true。cover? は Ruby の cover? です: 端と比べるだけで(begin <= v <= end、... なら < end)Range を辿らないので、終端の無い Range、始端の無い Range、Float の Range でも使え、Integer の Range に Float も見つかります。include? と member? は Ruby の include? です: 数の Range では cover? と同じですが、String の Range は String#succ で辿るので、"a".."z" に "m" は含まれ "mm" は含まれません(cover? は "a" <= "mm" <= "z" なので true)。端の無い String の Range は TypeError(cannot determine inclusion in beginless/endless ranges)。端と型の違う値(1..5 に "a"、nil)は false。値に Range を渡したとき、cover? は Ruby と同じく全体が内側にあるかを答え、include? と member? は false です。
p(Range.include?(1..5, 5)) # => true
p(Range.include?(1...5, 5)) # => false
p(Range.cover?(1..5, 2.5)) # => true
p(Range.member?(1..5, 0)) # => false
p(Range.include?(1.., 100)) # => true
p(Range.include?("a".."z", "m")) # => true
p(Range.include?("a".."z", "mm")) # => false
p(Range.cover?("a".."z", "mm")) # => true
p(Range.include?(1..5, "a")) # => false
p(Range.cover?(1..5, 2..3)) # => true
p(Range.include?(1..5, 2..3)) # => false
```ruby error p(Range.include?(“a”.., “b”)) # !> TypeError: Range.include?: cannot determine inclusion in beginless/endless ranges
## overlap?
`Range.overlap?(x, Range)`
2 つの Range が少なくとも 1 つの値を共有するとき true(Ruby の `overlap?`)。除外された終端は数えません(`1...5` と `5..8` は重なりません)。端の型は何でもよく、型の違う Range 同士は重なりません。
```ruby
p(Range.overlap?(1..5, 3..8)) # => true
p(Range.overlap?(1..5, 6..8)) # => false
p(Range.overlap?(1...5, 5..8)) # => false
p(Range.overlap?(1.., 100..200)) # => true
p(Range.overlap?(1.0..2.0, 1.5..3.0)) # => true
26.4 size
Range.size(x)
Range に含まれる Integer の個数(Integer)。Integer の Range だけに使えます: String の Range は type の問題(the Range's first value must be Integer, but is String)、Float になりうる Range(1..2.5)も同様です。終端の無い Range は RangeError。始端が終端より大きい Range の size は 0 です。String の Range には count を使います。
p(Range.size(1..5)) # => 5
p(Range.size(1...5)) # => 4
p(Range.size(5..1)) # => 0
```ruby error p(Range.size(1..)) # !> RangeError: Range.size: cannot do this on an endless Range 1..
## count
`Range.count(x) [{ }]`
ブロック無しなら Range を辿って数えた値の個数(Integer)、ブロック付きならブロックが真を返した値の個数。Integer と String の Range に使えます。有限の Range が要ります(`RangeError`)。Ruby の `count(v)` と違って値を渡す形は無く、`include?` を使います。
```ruby
p(Range.count("a".."c")) # => 3
p(Range.count(1..5) { |i| Integer.odd?(i) }) # => 3
26.5 each
Range.each(x) { }
各値を順にブロックへ渡し、Range を返します。Integer の Range は 1 ずつ、String の Range は String#succ で進みます("az".."bc" は "az"、"ba"、"bb"、"bc")。終端の無い Range は break するまで走り、break v なら v が結果です。ブロックの引数は 1 つ。Float の Range は type の問題です。
r = Range.each(1..3) { |i| puts(i) } # => 1
# => 2
# => 3
p(r) # => 1..3
Range.each("a".."c") { |s| print(s) } # => abc
puts("")
x = Range.each(1..) { |i| break i * 10 if i > 2 }
p(x) # => 30
```ruby error Range.each(1.0..2.0) { |f| p(f) } # !> the Range’s first value must be Integer|String, but is Float
## each_with_index
`Range.each_with_index(x) { }`
各値とその位置(0, 1, 2, ...)をブロックへ渡し、Range を返します。ブロックの引数は 2 つ。終端の無い Range には `break` が要ります。
```ruby
r = Range.each_with_index("a".."c") { |s, i| p([s, i]) } # => ["a", 0]
# => ["b", 1]
# => ["c", 2]
p(r) # => "a".."c"
26.6 each_entry
Range.each_entry(x) { }
each と同じです: Ruby の Enumerable#each_entry で、それに合わせて書かれたプログラムのために残してあります。Range を返します。
p(Range.each_entry(1..3) { |i| puts(i) }) # => 1
# => 2
# => 3
# => 1..3
26.7 reverse_each
Range.reverse_each(x) { }
各値を終端から始端へ向かってブロックへ渡し、Range を返します。有限の Range が要ります(RangeError)。
p(Range.reverse_each(1..3) { |i| puts(i) }) # => 3
# => 2
# => 1
# => 1..3
26.8 step
Range.step(x, Integer) { }
始端、始端 + 刻み、… と Range の中にある間ブロックへ渡し、Range を返します。Integer の Range だけに使えます(String や Float の Range は type)。終端の無い Range には break が要ります。負の刻みは下る Range を辿り(10..1 に -3 で 10, 7, 4, 1)、上る Range では何も渡しません。刻み 0 は ArgumentError(step can't be 0)です。
p(Range.step(1..10, 3) { |i| puts(i) }) # => 1
# => 4
# => 7
# => 10
# => 1..10
Range.step(1...9, 4) { |i| p(i) } # => 1
# => 5
Range.step(10..1, -3) { |i| p(i) } # => 10
# => 7
# => 4
# => 1
```ruby error Range.step(“a”..”z”, 2) { |s| p(s) } # !> the Range’s first value must be Integer, but is String
```ruby error
Range.step(1..5, 0) { |i| p(i) } # !> ArgumentError: Range.step: step can't be 0
26.9 each_slice
Range.each_slice(x, Integer) { }
値を n 個ずつの Array にしてブロックへ渡し(最後は足りなければ短い)、Range を返します。終端の無い Range には break が要ります。0 以下の大きさは ArgumentError。
p(Range.each_slice(1..7, 3) { |a| p(a) }) # => [1, 2, 3]
# => [4, 5, 6]
# => [7]
# => 1..7
26.10 each_cons
Range.each_cons(x, Integer) { }
連続する n 個の値の窓を Array としてブロックへ渡し、Range を返します。値が n 個に足りなければブロックは呼ばれません。0 以下の大きさは ArgumentError。
p(Range.each_cons(1..4, 2) { |a| p(a) }) # => [1, 2]
# => [2, 3]
# => [3, 4]
# => 1..4
26.11 each_with_object
Range.each_with_object(x, Any) { }
各値と渡したオブジェクトをブロックへ渡し、そのオブジェクトを返します。容器を埋めるのに使います。Ruby と同じく、ブロックが新しい値を返してもオブジェクトは変わりません(Range.each_with_object(1..3, 0) { |i, acc| acc + i } は 0 を返す。reduce を使う)。終端の無い Range には break が要ります。
a = Range.each_with_object(1..3, Any[]) { |i, acc| Array.push(acc, i * i) }
p(a) # => [1, 4, 9]
26.12 cycle
Range.cycle(x, Integer) { }
Range を n 回繰り返して辿り、nil を返します。n が 0 以下なら何もしません。有限の Range が要ります(RangeError)。引数無しの Ruby の cycle と違い、回数は必須です。
p(Range.cycle(1..2, 2) { |i| puts(i) }) # => 1
# => 2
# => 1
# => 2
# => nil
26.13 to_a, entries
Range.to_a(x)
Range.entries(x)
すべての値を持つ新しい Array。Integer か String の Range(それ以外は type)で、有限なもの(RangeError)が要ります。Array の要素の型は端の型です。
p(Range.to_a(1..5)) # => [1, 2, 3, 4, 5]
p(Range.to_a(1...5)) # => [1, 2, 3, 4]
p(Range.entries("a".."e")) # => ["a", "b", "c", "d", "e"]
p(Range.to_a(5..1)) # => []
```ruby error p(Range.to_a(1..)) # !> RangeError: Range.to_a: cannot do this on an endless Range 1..
## first
`Range.first(x, [Integer])`
個数無しなら Range の始端を、中身にかかわらず返します(`Range.first(5..1)` は 5、`Range.first(1...1)` は 1、`Range.first(1.0..2.0)` は 1.0)。型は `T | nil` で、算術の前の検査を `--strict` が求めます。始端の無い Range は nil を返すのではなく `RangeError`(`cannot get the first element of beginless range`)です。個数 n 付きなら先頭 n 個の値の新しい Array(Range が短ければそれだけ)。Range を辿るので Integer か String の Range が要ります(Float の Range は実行時に `TypeError`、`can't iterate from Float`)。終端の無い Range でも使えます。負の個数は `RangeError`(`negative array size (or size too big)`)。
```ruby
p(Range.first(1..5)) # => 1
p(Range.first(1..5, 2)) # => [1, 2]
p(Range.first(1..5, 10)) # => [1, 2, 3, 4, 5]
p(Range.first(1.., 3)) # => [1, 2, 3]
p(Range.first("a".."c", 2)) # => ["a", "b"]
```ruby error p(Range.first(..5)) # !> RangeError: Range.first: cannot get the first element of beginless range
```ruby error
p(Range.first(1.0..2.0, 2)) # !> TypeError: Range.first: can't iterate from Float
26.14 last
Range.last(x, [Integer])
個数無しなら書かれた終端を Range.end と同じく返します(なので Ruby と同じく Range.last(1...5) は 5)。個数 n 付きなら末尾 n 個の値の新しい Array で、除外された終端は入りません(Range.last(1...5, 2) は [3, 4])。有限の Range が要ります(終端の無い Range は個数無しでも RangeError)。個数無しの結果は T | nil。負の個数は RangeError。
p(Range.last(1..5)) # => 5
p(Range.last(1...5)) # => 5
p(Range.last(1..5, 2)) # => [4, 5]
p(Range.last(1...5, 2)) # => [3, 4]
p(Range.last("a".."c")) # => "c"
```ruby error p(Range.last(1..)) # !> RangeError: Range.last: cannot do this on an endless Range 1..
## take
`Range.take(x, Integer)`
先頭 n 個の値の新しい Array(足りなければ全部)。`first(r, n)` と同じです。終端の無い Range でも使えます。負の n は `ArgumentError`。
```ruby
p(Range.take(1..10, 3)) # => [1, 2, 3]
p(Range.take(1.., 3)) # => [1, 2, 3]
p(Range.take(1..3, 10)) # => [1, 2, 3]
26.15 drop
Range.drop(x, Integer)
先頭 n 個を除いた残りの値の新しい Array(残りが無ければ空)。有限の Range が要ります(RangeError)。負の n は ArgumentError。
p(Range.drop(1..5, 2)) # => [3, 4, 5]
p(Range.drop(1..5, 10)) # => []
26.16 take_while
Range.take_while(x) { }
始端から、ブロックが初めて偽を返した値の直前までの新しい Array。ブロックがいずれ偽を返すなら、終端の無い Range でも使えます。
p(Range.take_while(1..10) { |i| i < 4 }) # => [1, 2, 3]
p(Range.take_while(1..) { |i| i < 4 }) # => [1, 2, 3]
26.17 drop_while
Range.drop_while(x) { }
ブロックが初めて偽を返した値から終端までの新しい Array。終端の無い Range は拒否されませんが、結果が終わらないので、この操作は戻ってきません。
p(Range.drop_while(1..6) { |i| i < 4 }) # => [4, 5, 6]
26.18 map, collect
Range.map(x) { }
Range.collect(x) { }
値ごとのブロックの結果を集めた新しい Array。有限の Integer か String の Range が要ります。Array の要素の型はブロックの結果の型です。
n = 4
p(Range.map(1..n) { |i| i * i }) # => [1, 4, 9, 16]
p(Range.collect("a".."c") { |s| String.upcase(s) }) # => ["A", "B", "C"]
```ruby error p(Range.map(1..) { |i| i }) # !> RangeError: Range.map: cannot do this on an endless Range 1..
## flat_map, collect_concat
`Range.flat_map(x) { }`
`Range.collect_concat(x) { }`
ブロックが返した Array(または Tuple)を 1 段つなげた新しい Array。ブロックは Array か Tuple を返さなければならず、他の値は実行時に `TypeError`(`the block must return an Array or a Tuple, got Integer`)です。有限の Range が要ります。
```ruby
p(Range.flat_map(1..3) { |i| Any[i, i] }) # => [1, 1, 2, 2, 3, 3]
p(Range.flat_map(1..2) { |i| [i, i * 10] }) # => [1, 10, 2, 20]
```ruby error p(Range.flat_map(1..2) { |i| i }) # !> TypeError: Range.flat_map: the block must return an Array or a Tuple, got Integer
## select, filter, find_all
`Range.select(x) { }`
`Range.filter(x) { }`
`Range.find_all(x) { }`
ブロックが真を返した値の新しい Array。有限の Integer か String の Range が要ります。Array の要素の型は端の型です。
```ruby
p(Range.select(1..6) { |i| Integer.even?(i) }) # => [2, 4, 6]
p(Range.filter("a".."e") { |s| s > "c" }) # => ["d", "e"]
26.19 reject
Range.reject(x) { }
ブロックが偽を返した値の新しい Array。select の補集合です。有限の Range が要ります。
p(Range.reject(1..6) { |i| Integer.even?(i) }) # => [1, 3, 5]
26.20 filter_map
Range.filter_map(x) { }
ブロックの結果のうち nil と false を除いた新しい Array(Ruby の filter_map)。検査器は要素の型から nil を外すので、この Array は nil 検査なしで使えます。有限の Range が要ります。
p(Range.filter_map(1..6) { |i| Integer.even?(i) ? i * 10 : nil }) # => [20, 40, 60]
26.21 partition
Range.partition(x) { }
2 つの Array の Tuple: ブロックが真を返した値たち、それから残り。有限の Range が要ります。
evens, odds = Range.partition(1..6) { |i| Integer.even?(i) }
p(evens) # => [2, 4, 6]
p(odds) # => [1, 3, 5]
26.22 group_by
Range.group_by(x) { }
ブロックの結果ごとに、その結果を与えた値の Array を対応づけた Hash(初出の順)。有限の Range が要ります。キーの型はブロックの結果の型、値の型は端の型の Array です(無いキーの h[k] は他の Hash と同じく nil)。
h = Range.group_by(1..6) { |i| i % 3 }
p(h) # => {1 => [1, 4], 2 => [2, 5], 0 => [3, 6]}
p(h[1]) # => [1, 4]
26.23 chunk_while, slice_when
Range.chunk_while(x) { }
Range.slice_when(x) { }
値を連なりに切り分けた Array の Array。ブロックは隣り合う 2 つの値を受け取り、chunk_while はブロックが真の間同じ連なりに入れ、slice_when はブロックが真のところで新しい連なりを始めます。有限の Range が要ります。
p(Range.chunk_while(1..5) { |a, b| b != 3 }) # => [[1, 2], [3, 4, 5]]
p(Range.slice_when(1..6) { |a, b| Integer.even?(b) }) # => [[1], [2, 3], [4, 5], [6]]
26.24 slice_before, slice_after
Range.slice_before(x) { }
Range.slice_after(x) { }
ブロックが真を返した値の前(または後)で値を切り分けた Array の Array。有限の Range が要ります。
p(Range.slice_before(1..6) { |i| Integer.even?(i) }) # => [[1], [2, 3], [4, 5], [6]]
p(Range.slice_after(1..6) { |i| Integer.even?(i) }) # => [[1, 2], [3, 4], [5, 6]]
26.25 find, detect
Range.find(x) { }
Range.detect(x) { }
ブロックが初めて真を返した値。無ければ nil。結果は T | nil なので、使う前の検査を --strict が求めます。どれかの値がブロックを満たすなら終端の無い Range でも使えます。満たさなければ探索は終わりません。
p(Range.find(1..10) { |i| i * i > 10 }) # => 4
p(Range.detect(1..10) { |i| i > 100 }) # => nil
p(Range.find(1..) { |i| i * i > 10 }) # => 4
s = Range.find("a".."e") { |c| c > "b" }
if s
p(String.upcase(s)) # => "C"
end
26.26 find_index
Range.find_index(x) { }
ブロックが初めて真を返した値の位置(0, 1, 2, …)。無ければ nil(Integer | nil)。Ruby の find_index(v) と違い、ブロックが必須で値を渡す形はありません。見つかるなら終端の無い Range でも使えます。
p(Range.find_index(1..10) { |i| i * i > 10 }) # => 3
p(Range.find_index(1..10) { |i| i > 100 }) # => nil
26.27 bsearch
Range.bsearch(x) { }
Integer の Range の二分探索(Ruby と同じ): find-minimum モードでは、求める値とそれより上のすべての値でブロックが true を返すようにし、結果はそのような最小の値です。ブロックが一度も true にならなければ nil(T | nil)。終端の無い Range でも使えます。String の Range は実行時に TypeError(can't do binary search for String)。
p(Range.bsearch(1..100) { |i| i * i >= 50 }) # => 8
p(Range.bsearch(1..100) { |i| i > 1000 }) # => nil
p(Range.bsearch(1..) { |i| i * i >= 50 }) # => 8
```ruby error p(Range.bsearch(“a”..”z”) { |s| s >= “m” }) # !> TypeError: Range.bsearch: can’t do binary search for String
## all?, any?, none?, one?
`Range.all?(x) { }`
`Range.any?(x) { }`
`Range.none?(x) { }`
`Range.one?(x) { }`
ブロックがすべての値で真か、少なくとも 1 つで真か、どれでも偽か、ちょうど 1 つで真か。Ruby と違ってブロックは必須です(ブロック無しの `any?` やパターン引数はありません)。有限の Range が要ります(`RangeError`)。値の無い Range では `all?` と `none?` が true、`any?` と `one?` が false。
```ruby
p(Range.all?(1..5) { |i| i > 0 }) # => true
p(Range.any?(1..5) { |i| i > 4 }) # => true
p(Range.none?(1..5) { |i| i > 5 }) # => true
p(Range.one?(1..5) { |i| i == 3 }) # => true
p(Range.any?(5..1) { |i| true }) # => false
26.28 reduce, inject
Range.reduce(x, Any) { }
Range.inject(x, Any) { }
値を畳み込みます: ブロックは累積値と次の値を受け取り、その結果が新しい累積値になります。結果は最後の累積値です。初期値は必須で(初期値無しの Ruby の inject は wrong number of arguments の問題)、結果の型は初期値の型とブロックの結果の型の和です。シンボル形(reduce(:+))は無く、それには sum を使います。有限の Range が要ります。
p(Range.reduce(1..5, 0) { |acc, i| acc + i }) # => 15
p(Range.inject(1..5, 1) { |acc, i| acc * i }) # => 120
p(Range.reduce(1..3, "") { |acc, i| acc + Integer.to_s(i) }) # => "123"
p(Range.reduce(5..1, 7) { |acc, i| acc + i }) # => 7
```ruby error p(Range.inject(1..5) { |acc, i| acc + i }) # !> wrong number of arguments for Range.inject (given 1, expected 2)
## sum
`Range.sum(x, [Integer|Float|Rational|Complex]) [{ }]`
値(ブロック付きならブロックの結果)の合計に初期値(省略時 0)を足したもの。Integer の Range だけ(String の Range は `type`)で、有限なもの(`RangeError`)が要ります。結果の型は初期値とブロックに従います: Integer の値に Integer の初期値なら Integer、Float の初期値なら Float。Ruby の `Range#sum` と同じく、Integer の Range に Rational の初期値を与えると Float になり(`Range.sum(1..3, 2r)` は 8.0)、Complex の初期値は実行時に `RangeError`(`can't convert 1+2i into Float`)です。数でない結果を返すブロックは `type` の問題です。
```ruby
p(Range.sum(1..100)) # => 5050
p(Range.sum(1..10, 100)) # => 155
p(Range.sum(1..3) { |i| i * 2 }) # => 12
p(Range.sum(1..3, 0.5)) # => 6.5
p(Range.sum(1..3, 2r)) # => 8.0
p(Range.sum(1..0)) # => 0
```ruby error p(Range.sum(“a”..”c”)) # !> the Range’s first value must be Integer, but is String
```ruby error
p(Range.sum(1..3, Complex(1, 2))) # !> RangeError: Range.sum: can't convert 1+2i into Float
26.29 min, max
Range.min(x)
Range.max(x)
最小と最大の値。値の無い Range(5..1)では nil で、型は T | nil です。max は除外された終端を数えません(Range.max(1...5) は 4)。有限の Range が要ります(RangeError)。終端を含む Float の Range はどちらも受け取ります(Range.max(1.0..2.5) は 2.5。Ruby と同じ)。終端を除く Float の Range の max は TypeError(cannot exclude non Integer end value。その min は使えます)。
p(Range.min(1..5)) # => 1
p(Range.max(1...5)) # => 4
p(Range.max(5..1)) # => nil
p(Range.min("a".."c")) # => "a"
p(Range.max(1.0..2.5)) # => 2.5
p(Range.min(1.0...2.5)) # => 1.0
```ruby error x = Range.min(1..5) p(x + 1) # !> the operands may be nil
```ruby error
p(Range.max(1.0...2.5)) # !> TypeError: Range.max: cannot exclude non Integer end value
26.30 minmax
Range.minmax(x)
Tuple [min, max]。値の無い Range(5..1、2.5..1.0)では両方 nil で、これは外れの nil です(要素を確かめずに使うことは --strict=3 でだけ報告)。有限の Range が要ります(RangeError)。min、max と同じく終端を含む Float の Range も受け取り(Range.minmax(1.0..2.5) は [1.0, 2.5])、終端を除く Float の Range は TypeError(cannot exclude non Integer end value)です。
lo, hi = Range.minmax(1..5)
p([lo, hi]) # => [1, 5]
p(Range.minmax(5..1)) # => [nil, nil]
p(Range.minmax(1.0..2.5)) # => [1.0, 2.5]
p(Range.minmax("a".."c")) # => ["a", "c"]
```ruby error p(Range.minmax(1.0…2.5)) # !> TypeError: Range.minmax: cannot exclude non Integer end value
## min_by, max_by
`Range.min_by(x) { }`
`Range.max_by(x) { }`
ブロックの結果が最小(最大)になる値。値の無い Range では nil(`T | nil`)。有限の Range が要ります。
```ruby
p(Range.min_by(1..5) { |i| -i }) # => 5
p(Range.max_by("a".."e") { |s| String.ord(s) }) # => "e"
p(Range.min_by(5..1) { |i| i }) # => nil
26.31 minmax_by
Range.minmax_by(x) { }
ブロックの結果が最小になる値と最大になる値の Tuple。値の無い Range では [nil, nil](外れの nil で、--strict=3 でだけ報告)。有限の Range が要ります。
p(Range.minmax_by(1..5) { |i| -i }) # => [5, 1]
26.32 sort
Range.sort(x)
値を昇順に並べた新しい Array。Range では to_a と同じです。有限の Integer か String の Range が要ります。
p(Range.sort(1..3)) # => [1, 2, 3]
p(Range.sort(3..1)) # => []
26.33 sort_by
Range.sort_by(x) { }
ブロックの結果の順に値を並べた新しい Array。有限の Range が要ります。
p(Range.sort_by(1..5) { |i| -i }) # => [5, 4, 3, 2, 1]
p(Range.sort_by("a".."c") { |s| -String.ord(s) }) # => ["c", "b", "a"]
26.34 uniq
Range.uniq(x)
重複を除いた値の新しい Array。Range に重複は無いので to_a と同じです。有限の Integer か String の Range が要ります。
p(Range.uniq(1..3)) # => [1, 2, 3]
26.35 compact
Range.compact(x)
nil を除いた値の新しい Array。Range に nil は無いので to_a と同じです。有限の Integer か String の Range が要ります。
p(Range.compact(1..3)) # => [1, 2, 3]
26.36 tally
Range.tally(x)
各値をその出現回数に対応づけた Hash。Range では常に 1 です。有限の Integer か String の Range が要ります。
p(Range.tally("a".."c")) # => {"a" => 1, "b" => 1, "c" => 1}
26.37 to_set
Range.to_set(x)
値の新しい Set。有限の Integer か String の Range が要ります。
p(Range.to_set(1..3)) # => Set[1, 2, 3]
26.38 zip
Range.zip(x, *Array)
Tuple の新しい Array: i 番目の Tuple は Range の i 番目の値と、渡した各 Array の i 番目の要素を持ちます。短い Array のところは nil です(Tuple の型にそう出ます)。Array を渡さなければ各 Tuple は値 1 つです。有限の Integer か String の Range が要ります。
p(Range.zip(1..3, Any["a", "b", "c"])) # => [[1, "a"], [2, "b"], [3, "c"]]
p(Range.zip(1..3, Any[10, 20])) # => [[1, 10], [2, 20], [3, nil]]
p(Range.zip(1..2)) # => [[1], [2]]
27. Hash
Hash はキーから値への対応で、挿入順を保ちます。Ruby の {"a" => 1} のようなリテラルはなく、Hash.of("a" => 1, b: 2)、Hash.new、Hash.new(default) で構築します(値と型)。{k: v} は Record であって Hash ではありません({} も空の Record で、静的に拒否されます。Record)。Hash.of(name: v) と書いたキーは Symbol です。
キーに使えるのは Integer、Float、String、Symbol、true、false、nil、Time と、それらだけからなる Tuple、Record、Array、Hash、Set、クラスのインスタンスです。Regexp、Range、独自の等価性(== や Comparable)を定義したクラスの値はキーにできず、実行時に TypeError です。Tuple と Record のキーは格納時に複製されるので、元の値を後で書き換えてもキーは変わりません(Array などのキーは Ruby と同じく、書き換えると見つからなくなります)。"a" と :a、1 と 1.0 は別のキーです。
h[k] は見つからないと nil(Hash.new(default) で作った Hash なら default)を返します。この nil をそのまま使うことは --strict=3 でだけ index-nil として報告されます(level 2 は報告しません)。見つからないことを例外にしたいときは Hash.fetch を使います。Hash.dig、Hash.min_by、Hash.max_by の nil も外れの nil です(level 3)。Hash.delete、Hash.key、Hash.first、Hash.shift、Hash.find などの nil は level 2 で報告されます。
ブロックを取る操作は、各要素を [key, value] の Tuple 1 つとして渡します。|k, v| と書けば分解され、|kv| なら Tuple のまま受け取ります。each_with_object は |(k, v), memo|、reduce は |acc, (k, v)| と書きます。繰り返しの順序は挿入順です。to_a、sort_by、take、first などが返す組も [k, v] の Tuple です。
検査器は Hash に、作られた場所ごとにキーの型と値の型を 1 つずつ与えます。Hash.store や []= で別の型を入れると、その Hash の値の型は和になります(Hash.of("a" => 1) に "x" を入れると値は Integer | String で、fetch した値の + 1 は type の問題)。transform_keys!、transform_values! など要素の型を変える in-place 操作も同じです(下記)。
Hash に使える演算子は ==、!= と添字 h[k]、h[k] = v です。Hash.==(x, y) などは演算子の関数形です(演算子と添字)。
27.1 of
Hash.of(*Any)
Hash.of(k => v, ...) は新しい Hash を作ります。引数は key => value の組だけで(Hash.of(name: v) のラベル形はキーが Symbol)、組以外(Hash.of(1)、Hash.of({x: 1}))は静的なエラーです。Hash.new は空の Hash。同じキーを 2 度書くと後の値が残ります。キーにできない値は実行時に TypeError。
h = Hash.of("a" => 1, b: 2)
p(h) # => {"a" => 1, b: 2}
p(Hash.new) # => {}
p(Hash.of(1 => 2, 1 => 3)) # => {1 => 3}
p(Hash.of([1, 2] => "t", nil => "n")) # => {[1, 2] => "t", nil => "n"}
```ruby error
p(Hash.of(1)) # !> Hash.of takes key => value pairs, like Hash.of("a" => 1)
## Hash[]
`Hash[*Any]`
Hash の Array を作ります。どの T でも `T[...]` が T の Array を作るのと同じで、各要素は Hash でなければならず、書き込みのたびに検査されます。Hash そのものは `Hash.of(k => v, ...)` か `Hash.new` で作ります。
```ruby
hs = Hash[Hash.of(a: 1), Hash.new]
p(hs) # => [{a: 1}, {}]
27.2 new
Hash.new([Any])
空の Hash を作ります。Hash.new(default) は見つからないキーに対して h[k] が default を返す Hash です(Ruby と同じ)。default はそのオブジェクト 1 つを共有するので、Hash.new(Any[]) の Array を h["x"] 経由で変更するとすべてのキーで見えますし、Hash 自体には何も格納されません。Ruby のブロック形 Hash.new { |h, k| ... } はありません(静的なエラー)。Hash.fetch は default を見ません。
counts = Hash.new(0)
counts["x"] += 1
counts["x"] += 1
p(counts) # => {"x" => 2}
p(counts["none"]) # => 0
p(Hash.new["none"]) # => nil
d = Hash.new(Any[])
Array.push(d["a"], 1)
p(d["b"]) # => [1]
p(d) # => {}
```ruby error h = Hash.new { |hh, k| 0 } # !> Hash.new does not take a block
## default, set_default
`Hash.default(x)`
`Hash.set_default(x, Any)`
`default` は見つからないキーに `h[k]` が返す値(`Hash.new(default)` の default。無ければ nil)。`set_default(h, v)` はそれを `v` に変えて `v` を返します(Ruby の `h.default = v`)。検査器は `h[k]` の型に default の型を加えます。
```ruby
h = Hash.new(0)
p(Hash.default(h)) # => 0
p(Hash.default(Hash.new)) # => nil
Hash.set_default(h, 5)
p(h["q"]) # => 5
27.3 []
Hash.[](x, Any)
h[k] の関数形。キー k の値を返します。無ければ default(通常 nil)。この nil は --strict=3 でだけ index-nil として報告されます。キーの型は何でもよく、どんな値でも静的な問題にはなりません。Hash.new(default) の Hash では default の型が結果に加わるので、Hash.new(0) の h[k] + 1 は level 3 でも通ります。
h = Hash.of("a" => 1, [1, 2] => "t")
p(h["a"]) # => 1
p(h[[1, 2]]) # => "t"
p(h["zz"]) # => nil
p(Hash.[](h, "a")) # => 1
27.4 []=, store
Hash.[]=(x, Any, Any)
Hash.store(x, Any, Any)
h[k] = v とその関数形。キー k に値 v を結び付け、v を返します。新しいキーは末尾に加わり、既存のキーは値だけ変わって位置は動きません。キーにできない値は TypeError。検査器はこの Hash のキーの型・値の型に k、v の型を加えます。
h = Hash.of("a" => 1)
h["b"] = 2
p(Hash.store(h, "a", 10)) # => 10
p(h) # => {"a" => 10, "b" => 2}
```ruby error Hash.store(Hash.new, /x/, 1) # !> TypeError: Hash.store: Regexp cannot be a Hash key or Set element
## fetch
`Hash.fetch(x, Any, [Any])`
キー `k` の値を返します。無いときは、第 3 引数 `default` があればそれを返し、無ければ `KeyError`(Ruby と同じ。ブロック形はありません)。`Hash.new(default)` の default は使いません。結果の型は値の型と `default` の型の和で、値が nil でなければ nil になりえないので、`h[k]` の代わりに使えば `--strict=3` の `index-nil` も出ません。
```ruby
h = Hash.of("a" => 1)
p(Hash.fetch(h, "a")) # => 1
p(Hash.fetch(h, "zz", 0)) # => 0
p(Hash.fetch(h, "a") + 1) # => 2
```ruby error p(Hash.fetch(Hash.of(“a” => 1), “b”)) # !> KeyError: Hash.fetch: key not found: “b”
## dig
`Hash.dig(x, Any, *Any)`
Ruby の `dig` と同じく、キーを順に使って入れ子の容器を掘り進みます: `dig(h, k)` は `h[k]` で、以降のキーはそこまでに見つかった値への添字です(Hash ならキー、Array と Tuple なら位置)。結果は辿り着いた値で、途中でキーが見つからなければそこで nil です(`Hash.new(default)` で作った Hash での外れは `h[k]` と同じく default で、検査器も default の型を結果に加えます)。この nil は `h[k]` と同じ外れの nil で、そのまま使うことは `--strict=3` でだけ `index-nil` として報告されます(level 2 は報告しません)。検査器はキーを型の上で辿り、Hash・Array・Tuple でない値(Integer、String、Record)にキーを当てるのは静的に `type` の問題です(`the value must be Array|Hash|Tuple, but is Integer`)。辿り着いた容器に合わない種類のキー(Array に String)は実行時に `TypeError`(`an index into Array must be Integer, got String`)。
```ruby
h = Hash.of("a" => Hash.of("b" => Any[10, 20]))
p(Hash.dig(h, "a", "b", 1)) # => 20
p(Hash.dig(h, "a", "zz", 1)) # => nil
p(Hash.dig(h, "zz")) # => nil
t = Hash.of("t" => [1, "x"])
p(Hash.dig(t, "t", 1)) # => "x"
p(Hash.dig(t, "t", 5)) # => nil
p(Hash.dig(Hash.new(0), "q") + 1) # => 1
x = Hash.dig(h, "a", "b", 0)
if x
p(x + 1) # => 11
end
```ruby error p(Hash.dig(Hash.of(“a” => 1), “a”, “b”)) # !> Hash.dig: the value must be Array|Hash|Tuple, but is Integer
## fetch_values
`Hash.fetch_values(x, *Any)`
列挙したキーの値を順に並べた Array を返します。無いキーがあれば `KeyError`(default は見ません)。要素の型は値の型で、nil にはなりません。
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.fetch_values(h, "b", "a")) # => [2, 1]
```ruby error p(Hash.fetch_values(Hash.of(“a” => 1), “a”, “zz”)) # !> KeyError: Hash.fetch_values: key not found: “zz”
## values_at
`Hash.values_at(x, *Any)`
列挙したキーの値を順に並べた Array を返します。無いキーの位置は default(通常 nil)です。要素の型は `値 | nil` なので、要素をそのまま使うと level 2 で報告されます。
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.values_at(h, "a", "zz")) # => [1, nil]
p(Hash.values_at(Hash.new(9), "x")) # => [9]
27.5 key?, has_key?, include?, member?
Hash.key?(x, Any)
Hash.has_key?(x, Any)
Hash.include?(x, Any)
Hash.member?(x, Any)
キー k があれば true。値が nil でも true です(h[k] が nil でも区別できます)。4 つは同じ操作です。
h = Hash.of("a" => nil)
p(Hash.key?(h, "a")) # => true
p(Hash.include?(h, "b")) # => false
p(h["a"]) # => nil
27.6 value?, has_value?
Hash.value?(x, Any)
Hash.has_value?(x, Any)
v と == で等しい値があれば true。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.value?(h, 2)) # => true
p(Hash.has_value?(h, 3)) # => false
27.7 key
Hash.key(x, Any)
値が v と等しい最初のキーを返します。無ければ nil(level 2 で報告)。
h = Hash.of("a" => 1, "b" => 2, "c" => 2)
p(Hash.key(h, 2)) # => "b"
p(Hash.key(h, 9)) # => nil
27.8 assoc, rassoc
Hash.assoc(x, Any)
Hash.rassoc(x, Any)
assoc(h, k) はキー k の組 [k, v]、rassoc(h, v) は値が v の最初の組 [k, v] を Tuple で返します。無ければ nil(level 2 で報告)。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.assoc(h, "a")) # => ["a", 1]
p(Hash.rassoc(h, 2)) # => ["b", 2]
p(Hash.assoc(h, "zz")) # => nil
27.9 length, size
Hash.length(x)
Hash.size(x)
組の個数(Integer)。
p(Hash.size(Hash.of("a" => 1, "b" => 2))) # => 2
p(Hash.length(Hash.new)) # => 0
27.10 empty?
Hash.empty?(x)
組が 1 つも無ければ true。
p(Hash.empty?(Hash.new)) # => true
p(Hash.empty?(Hash.new(0))) # => true
p(Hash.empty?(Hash.of("a" => 1))) # => false
27.11 count
Hash.count(x) [{ }]
ブロックなしなら組の個数(size と同じ)、ブロックがあれば [k, v] を受けて真を返した組の個数(Integer)。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.count(h)) # => 3
p(Hash.count(h) { |k, v| v >= 2 }) # => 2
27.12 keys
Hash.keys(x)
キーを挿入順に並べた新しい Array。要素の型はキーの型です。型付き Array ではないので、他の型の値も push できます。
h = Hash.of("b" => 2, "a" => 1)
p(Hash.keys(h)) # => ["b", "a"]
p(Hash.keys(Hash.new)) # => []
27.13 values
Hash.values(x)
値を挿入順に並べた新しい Array。要素の型は値の型です。
h = Hash.of("b" => 2, "a" => 1)
p(Hash.values(h)) # => [2, 1]
27.14 to_a
Hash.to_a(x)
組を [k, v] の Tuple にして挿入順に並べた新しい Array。Ruby の to_a が返す内側の Array は、Sake では Tuple です。
h = Hash.of(1 => "x", 2 => "y")
p(Hash.to_a(h)) # => [[1, "x"], [2, "y"]]
Array.each(Hash.to_a(h)) { |k, v| p(k) } # => 1
# => 2
27.15 flatten
Hash.flatten(x)
キーと値を交互に並べた新しい Array [k1, v1, k2, v2, ...]。Ruby の flatten の深さ 1 と同じで、値の Array や Tuple は開きません(深さの引数はありません)。要素の型はキーの型と値の型の和です。
p(Hash.flatten(Hash.of(1 => "x", 2 => "y"))) # => [1, "x", 2, "y"]
p(Hash.flatten(Hash.of("a" => Any[1, 2]))) # => ["a", [1, 2]]
27.16 to_h
Hash.to_h(x)
その Hash 自身を返します(Ruby の Hash#to_h と同じで、複製ではありません)。ブロック形はありません。複製には Hash.dup。
h = Hash.of("a" => 1)
p(Hash.to_h(h)) # => {"a" => 1}
p(Kernel.equal?(Hash.to_h(h), h)) # => true
27.17 dup
Hash.dup(x)
同じ組を持つ新しい Hash(浅い複製。キーと値のオブジェクトは共有)。default も引き継ぎます。一方への store や delete は他方に影響しません。
h = Hash.new(0)
Hash.store(h, "a", 1)
h2 = Hash.dup(h)
Hash.store(h2, "b", 2)
p(h) # => {"a" => 1}
p(h2) # => {"a" => 1, "b" => 2}
p(h2["zz"]) # => 0
27.18 first
Hash.first(x)
最初に挿入された組 [k, v] を Tuple で返します。空なら nil(level 2 で報告)。Ruby の first(n) の形はなく、先頭 n 組は Hash.take。
p(Hash.first(Hash.of("a" => 1, "b" => 2))) # => ["a", 1]
p(Hash.first(Hash.new)) # => nil
27.19 take, drop
Hash.take(x, Integer)
Hash.drop(x, Integer)
take(h, n) は先頭 n 組、drop(h, n) は先頭 n 組を除いた残りを、[k, v] の Tuple の Array で返します。Hash は変わりません。n が組の数より大きくても構いません。負の n は ArgumentError。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.take(h, 2)) # => [["a", 1], ["b", 2]]
p(Hash.drop(h, 2)) # => [["c", 3]]
p(Hash.drop(h, 5)) # => []
```ruby error p(Hash.take(Hash.of(“a” => 1), -1)) # !> ArgumentError: Hash.take: attempt to take negative size
## each, each_pair
`Hash.each(x) { }`
`Hash.each_pair(x) { }`
各組についてブロックを挿入順に呼び、Hash 自身を返します。ブロックは `[k, v]` の Tuple を受け取ります(`|k, v|` で分解)。ブロックは必須です(ブロック無しで Enumerator を返す Ruby の形はありません)。
```ruby
h = Hash.of("a" => 1, "b" => 2)
Hash.each(h) { |k, v| p(k + Integer.to_s(v)) } # => "a1"
# => "b2"
Hash.each_pair(h) { |kv| p(kv) } # => ["a", 1]
# => ["b", 2]
r = Hash.each(h) { |k, v| nil }
p(Kernel.equal?(r, h)) # => true
27.20 each_key
Hash.each_key(x) { }
各キーについてブロックを呼び、Hash 自身を返します。ブロックはキーだけを受け取ります。
Hash.each_key(Hash.of("a" => 1, "b" => 2)) { |k| p(k) } # => "a"
# => "b"
27.21 each_value
Hash.each_value(x) { }
各値についてブロックを呼び、Hash 自身を返します。ブロックは値だけを受け取ります。
Hash.each_value(Hash.of("a" => 1, "b" => 2)) { |v| p(v * 10) } # => 10
# => 20
27.22 each_with_object
Hash.each_with_object(x, Any) { }
memo を引き回しながら各組についてブロックを呼び、最後に memo を返します。ブロックは [k, v] の Tuple と memo の 2 つを受け取るので、|(k, v), memo| と書きます。結果の型は memo の型です。
h = Hash.of("a" => 1, "b" => 2)
ks = Hash.each_with_object(h, Any[]) { |(k, v), acc| Array.push(acc, k) }
p(ks) # => ["a", "b"]
27.23 map
Hash.map(x) { }
各組について [k, v] を渡してブロックを呼び、その結果を並べた新しい Array を返します。Ruby と同じく結果は Hash ではなく Array です(Hash を得るなら transform_values や to_h)。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.map(h) { |k, v| k + Integer.to_s(v) }) # => ["a1", "b2"]
p(Hash.map(h) { |kv| kv }) # => [["a", 1], ["b", 2]]
27.24 flat_map
Hash.flat_map(x) { }
各組についてブロックを呼び、ブロックが返した Array をすべてつなげた新しい Array を返します。ブロックは Array か Tuple を返さなければならず(Tuple の要素も Array と同じくつなげられます)、スカラーを返すと実行時に TypeError(the block must return an Array or a Tuple, got Integer。Ruby は Array 以外をそのまま並べます)。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.flat_map(h) { |k, v| Any[k, v] }) # => ["a", 1, "b", 2]
p(Hash.flat_map(h) { |k, v| [k, v] }) # => ["a", 1, "b", 2]
```ruby error Hash.flat_map(Hash.of(“a” => 1)) { |k, v| v } # !> TypeError: Hash.flat_map: the block must return an Array or a Tuple, got Integer
## filter_map
`Hash.filter_map(x) { }`
各組についてブロックを呼び、nil と false 以外の結果を並べた新しい Array を返します。
```ruby
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.filter_map(h) { |k, v| v > 1 ? k : nil }) # => ["b", "c"]
27.25 select, filter
Hash.select(x) { }
Hash.filter(x) { }
ブロックが真を返した組だけを持つ新しい Hash を返します(Ruby と同じく結果は Hash)。元の Hash は変わりません。キー・値の型は元と同じです。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.select(h) { |k, v| v > 1 }) # => {"b" => 2, "c" => 3}
p(Hash.filter(h) { |k, v| k == "a" }) # => {"a" => 1}
p(h) # => {"a" => 1, "b" => 2, "c" => 3}
27.26 reject
Hash.reject(x) { }
ブロックが真を返した組を除いた新しい Hash を返します。元の Hash は変わりません。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.reject(h) { |k, v| v > 1 }) # => {"a" => 1}
27.27 partition
Hash.partition(x) { }
ブロックが真を返した組と偽を返した組を、それぞれ [k, v] の Tuple の Array にして、2 要素の Tuple [真の組, 偽の組] で返します。多重代入で受けます。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
odd, even = Hash.partition(h) { |k, v| Integer.odd?(v) }
p(odd) # => [["a", 1], ["c", 3]]
p(even) # => [["b", 2]]
27.28 group_by
Hash.group_by(x) { }
各組についてブロックを呼び、その結果をキー、同じ結果になった組 [k, v] の Tuple の Array を値とする新しい Hash を返します。ブロックの結果はキーにできる値でなければなりません。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
g = Hash.group_by(h) { |k, v| v % 2 }
p(g) # => {1 => [["a", 1], ["c", 3]], 0 => [["b", 2]]}
p(Hash.fetch(g, 0)) # => [["b", 2]]
27.29 find, detect
Hash.find(x) { }
Hash.detect(x) { }
ブロックが最初に真を返した組 [k, v] を Tuple で返します。無ければ nil で、level 2 では nil のまま多重代入や添字に使うと報告されるので、if で確かめます。
h = Hash.of("a" => 1, "b" => 2)
kv = Hash.find(h) { |k, v| v == 2 }
if kv
k, v = kv
p(k) # => "b"
end
p(Hash.detect(h) { |k, v| v == 9 }) # => nil
```ruby error kv = Hash.find(Hash.of(“a” => 1)) { |k, v| v == 1 } k, v = kv # !> multiple assignment: argument 1 may be nil
## min_by, max_by
`Hash.min_by(x) { }`
`Hash.max_by(x) { }`
ブロックの結果が最小・最大になる組 `[k, v]` を Tuple で返します。空なら nil で、これは外れの nil です(`Array.min_by` と同じく、そのまま使うことは `--strict=3` でだけ報告)。ブロックの結果同士は比べられなければなりません: 比べられない型(Integer と String)が結果の型に混ざるブロックは静的に `type` の問題として退けられ、nil になりうる結果は `nil` の問題です。検査器に見えないとき(`Float.NAN`)は実行時に `ArgumentError`。
```ruby
h = Hash.of("a" => 3, "b" => 1, "c" => 2)
p(Hash.min_by(h) { |k, v| v }) # => ["b", 1]
p(Hash.max_by(h) { |k, v| v }) # => ["a", 3]
p(Hash.max_by(Hash.new) { |k, v| v }) # => nil
```ruby error Hash.min_by(Hash.of(“a” => 1, “b” => “x”)) { |k, v| v } # !> Hash.min_by: elements compared in order may be (Integer, String), which cannot be compared
## sort_by
`Hash.sort_by(x) { }`
ブロックの結果の昇順に組を並べ、`[k, v]` の Tuple の Array で返します(Ruby と同じく Hash ではなく Array)。ブロックの結果同士は比べられなければならず、比べられない型が結果の型に混ざると `min_by` と同じく静的に退けられます(`type`)。降順にはブロックで符号を反転するか `Array.reverse` を使います。Tuple を返せば辞書順です([Tuple](Tuple.md))。
```ruby
h = Hash.of("a" => 3, "b" => 1, "c" => 2)
p(Hash.sort_by(h) { |k, v| v }) # => [["b", 1], ["c", 2], ["a", 3]]
p(Hash.sort_by(h) { |k, v| -v }) # => [["a", 3], ["c", 2], ["b", 1]]
```ruby error Hash.sort_by(Hash.of(“a” => 1, “b” => “x”)) { |k, v| v } # !> Hash.sort_by: elements compared in order may be (Integer, String), which cannot be compared
## any?, all?, none?
`Hash.any?(x) { }`
`Hash.all?(x) { }`
`Hash.none?(x) { }`
ブロックが真を返す組が 1 つでもあるか(`any?`)、すべての組で真か(`all?`)、1 つも無いか(`none?`)。ブロックは必須です(Ruby のブロック無しの形はなく、静的なエラー。空かどうかは `empty?`)。空の Hash では `any?` は false、`all?` と `none?` は true。
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.any?(h) { |k, v| v > 1 }) # => true
p(Hash.all?(h) { |k, v| v > 1 }) # => false
p(Hash.none?(h) { |k, v| v > 5 }) # => true
```ruby error p(Hash.any?(Hash.of(“a” => 1))) # !> Hash.any? requires a block
## one?
`Hash.one?(x) { }`
ブロックが真を返す組がちょうど 1 つなら true。ブロックは必須です。
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.one?(h) { |k, v| v == 2 }) # => true
p(Hash.one?(h) { |k, v| v > 0 }) # => false
27.30 sum
Hash.sum(x, [Integer|Float|Rational|Complex]) { }
各組についてブロックを呼び、その結果を init(省略時 0)に足し合わせます。ブロックは必須で(Ruby のブロック無しの形はありません)、init は数値に限ります(String を渡すのは静的に type の問題。文字列の連結は reduce)。数でない結果を返すブロックは実行時に TypeError(String can't be coerced into Integer)。結果の型は init とブロックの結果の数値型から決まります。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.sum(h) { |k, v| v }) # => 3
p(Hash.sum(h, 0.5) { |k, v| v }) # => 3.5
p(Hash.sum(Hash.new) { |k, v| 1 }) # => 0
```ruby error Hash.sum(Hash.of(“a” => “x”), “”) { |k, v| v } # !> argument 2 must be Integer|Float|Rational|Complex, but is String
## reduce, inject
`Hash.reduce(x, Any) { }`
`Hash.inject(x, Any) { }`
初期値 `init` から始めて、各組について `|acc, (k, v)|` でブロックを呼び、その結果を次の `acc` にして、最後の結果を返します。初期値は必須です(Ruby の省略形はありません)。結果の型は `init` とブロックの結果の型の和です。
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.reduce(h, 0) { |acc, (k, v)| acc + v }) # => 3
p(Hash.inject(h, "") { |acc, (k, v)| acc + k }) # => "ab"
27.31 transform_values
Hash.transform_values(x) { }
各値にブロックを適用した結果を値とする新しい Hash を返します。ブロックは値だけを受け取ります。キーはそのまま、元の Hash は変わりません。結果の値の型はブロックの結果の型なので、型を変える変換はこちらを使います(in-place の transform_values! と違って元の型と和になりません)。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.transform_values(h) { |v| Integer.to_s(v) }) # => {"a" => "1", "b" => "2"}
p(h) # => {"a" => 1, "b" => 2}
27.32 transform_keys
Hash.transform_keys(x) { }
各キーにブロックを適用した結果をキーとする新しい Hash を返します。ブロックはキーだけを受け取り、その結果はキーにできる値でなければなりません(TypeError)。2 つのキーが同じ結果になったときは後の組の値が残ります。元の Hash は変わりません。Ruby の Hash 引数の形(transform_keys(a: :b))はありません。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.transform_keys(h) { |k| String.to_sym(k) }) # => {a: 1, b: 2}
p(Hash.transform_keys(Hash.of("a" => 1, "A" => 2)) { |k| String.downcase(k) }) # => {"a" => 2}
27.33 transform_values!
Hash.transform_values!(x) { }
各値をブロックの結果に置き換え、Hash 自身を返します。検査器は Hash に 1 つの値の型を与えるので、型を変える変換(Integer から String)をするとその Hash の値の型は Integer | String の和になり、以後どちらか片方にしか合わない操作が type(partial)として報告されます。型を変えるなら新しい Hash を作る transform_values を使います。
h = Hash.of("a" => 1, "b" => 2)
r = Hash.transform_values!(h) { |v| v * 10 }
p(h) # => {"a" => 10, "b" => 20}
p(Kernel.equal?(r, h)) # => true
```ruby error h = Hash.of(“a” => 1) Hash.transform_values!(h) { |v| Integer.to_s(v) } # !> argument 1 must be Integer, but can be String
## transform_keys!
`Hash.transform_keys!(x) { }`
各キーをブロックの結果に置き換え、Hash 自身を返します。結果はキーにできる値でなければなりません(`TypeError`)。`transform_values!` と同じく、型を変える変換はキーの型を和にするので、`Array.each(Hash.keys(h))` などで片方の型の操作を使うと `type` の問題です。型を変えるなら `transform_keys`。
```ruby
h = Hash.of("a" => 1, "b" => 2)
Hash.transform_keys!(h) { |k| String.upcase(k) }
p(h) # => {"A" => 1, "B" => 2}
```ruby error h = Hash.of(“a” => 1) Hash.transform_keys!(h) { |k| String.to_sym(k) } # !> argument 1 must be String, but can be Symbol
## merge
`Hash.merge(x, Hash)`
2 つの Hash の組を合わせた新しい Hash を返します。同じキーは第 2 引数の値が勝ちます。どちらも変わりません。第 2 引数は Hash でなければならず、Record(`{a: 2}`)を渡すのは静的に `type` の問題です。Ruby のブロック形(衝突時の値を決める)はありません。結果のキー・値の型は両方の和です。
```ruby
a = Hash.of("a" => 1, "b" => 2)
b = Hash.of("b" => 20, "c" => 3)
p(Hash.merge(a, b)) # => {"a" => 1, "b" => 20, "c" => 3}
p(a) # => {"a" => 1, "b" => 2}
```ruby error p(Hash.merge(Hash.of(“a” => 1), {a: 2})) # !> Hash.merge: argument 2 must be Hash, but is {a: Integer}
## merge!, update
`Hash.merge!(x, Hash)`
`Hash.update(x, Hash)`
第 2 引数の組を第 1 引数の Hash に書き込み(同じキーは上書き)、第 1 引数自身を返します。検査器は第 1 引数のキー・値の型に第 2 引数の型を加えます。ブロック形はありません。
```ruby
h = Hash.of("a" => 1)
Hash.merge!(h, Hash.of("b" => 2))
p(Hash.update(h, Hash.of("a" => 10))) # => {"a" => 10, "b" => 2}
p(h) # => {"a" => 10, "b" => 2}
27.34 replace
Hash.replace(x, Hash)
第 1 引数の中身をすべて第 2 引数の組に置き換え、第 1 引数自身を返します。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.replace(h, Hash.of("z" => 26))) # => {"z" => 26}
p(h) # => {"z" => 26}
27.35 compact
Hash.compact(x)
値が nil の組を除いた新しい Hash を返します。結果の値の型から nil が取れます。
h = Hash.of("a" => 1, "b" => nil)
p(Hash.compact(h)) # => {"a" => 1}
p(h) # => {"a" => 1, "b" => nil}
27.36 compact!
Hash.compact!(x)
値が nil の組をその場で取り除き、Hash 自身を返します。1 つも取り除かなかったときは nil(Ruby と同じ)で、戻り値をそのまま使うと level 2 で報告されます。
h = Hash.of("a" => 1, "b" => nil)
p(Hash.compact!(h)) # => {"a" => 1}
p(Hash.compact!(h)) # => nil
```ruby error p(Hash.size(Hash.compact!(Hash.of(“a” => 1)))) # !> Hash.size: argument 1 may be nil
## slice
`Hash.slice(x, *Any)`
列挙したキーの組だけを持つ新しい Hash を返します。無いキーは無視され、順序は列挙した順です。
```ruby
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.slice(h, "c", "a", "zz")) # => {"c" => 3, "a" => 1}
27.37 except
Hash.except(x, *Any)
列挙したキーの組を除いた新しい Hash を返します。無いキーは無視されます。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.except(h, "a", "zz")) # => {"b" => 2, "c" => 3}
27.38 invert
Hash.invert(x)
値をキー、キーを値にした新しい Hash を返します。値はキーにできるものでなければならず(TypeError)、同じ値が複数あれば後の組が残ります。結果のキーの型と値の型は元の値の型とキーの型です。
p(Hash.invert(Hash.of("a" => 1, "b" => 2))) # => {1 => "a", 2 => "b"}
p(Hash.invert(Hash.of("a" => 1, "b" => 1))) # => {1 => "b"}
```ruby error Hash.invert(Hash.of(“a” => /x/)) # !> TypeError: Hash.invert: Regexp cannot be a Hash key or Set element
## delete
`Hash.delete(x, Any)`
キー `k` の組を取り除き、その値を返します。無ければ nil で、level 2 で報告されます(default は見ません)。Ruby のブロック形はありません。
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.delete(h, "a")) # => 1
p(Hash.delete(h, "zz")) # => nil
p(h) # => {"b" => 2}
```ruby error h = Hash.of(“a” => 1) p(Hash.delete(h, “a”) + 1) # !> the operands may be nil
## delete_if
`Hash.delete_if(x) { }`
ブロックが真を返した組をその場で取り除き、Hash 自身を返します(取り除かなくても nil にはなりません。`reject!` との違い)。
```ruby
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.delete_if(h) { |k, v| v > 1 }) # => {"a" => 1}
p(Hash.delete_if(h) { |k, v| false }) # => {"a" => 1}
27.39 keep_if
Hash.keep_if(x) { }
ブロックが真を返した組だけをその場で残し、Hash 自身を返します(変化が無くても nil にはなりません。select! との違い)。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.keep_if(h) { |k, v| v > 1 }) # => {"b" => 2, "c" => 3}
27.40 select!, filter!
Hash.select!(x) { }
Hash.filter!(x) { }
ブロックが真を返した組だけをその場で残し、Hash 自身を返します。1 つも取り除かなかったときは nil(Ruby と同じ)で、戻り値をそのまま使うと level 2 で報告されます。常に Hash を返す形は keep_if。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.select!(h) { |k, v| v > 1 }) # => {"b" => 2, "c" => 3}
p(Hash.filter!(h) { |k, v| v > 1 }) # => nil
p(h) # => {"b" => 2, "c" => 3}
27.41 reject!
Hash.reject!(x) { }
ブロックが真を返した組をその場で取り除き、Hash 自身を返します。1 つも取り除かなかったときは nil(level 2 で報告)。常に Hash を返す形は delete_if。
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.reject!(h) { |k, v| v > 2 }) # => {"a" => 1, "b" => 2}
p(Hash.reject!(h) { |k, v| v > 5 }) # => nil
27.42 shift
Hash.shift(x)
最初に挿入された組を取り除き、[k, v] の Tuple で返します。空なら nil(default は見ません。level 2 で報告)。
h = Hash.of("a" => 1, "b" => 2)
p(Hash.shift(h)) # => ["a", 1]
p(h) # => {"b" => 2}
Hash.shift(h)
p(Hash.shift(h)) # => nil
27.43 clear
Hash.clear(x)
すべての組を取り除き、Hash 自身(空)を返します。default はそのままです。
h = Hash.of("a" => 1)
p(Hash.clear(h)) # => {}
p(Hash.empty?(h)) # => true
27.44 ==, !=
Hash.==(x, Any)
Hash.!=(x, Any)
2 つの Hash が同じキーの集合を持ち、各キーの値が == で等しいときに true(!= はその否定)。挿入順は関係ありません。default も見ません。右側が Hash でない値(nil、Integer、Array)とは等しくなく、関数形も演算子と同じです: Hash.==(h, nil) は false。Record {a: 1} と Hash.of(a: 1) は型が違うので等しくありません。
a = Hash.of("a" => 1, "b" => 2)
p(a == Hash.of("b" => 2, "a" => 1)) # => true
p(a != Hash.of("a" => 1)) # => true
p(Hash.==(a, Hash.new)) # => false
p(Hash.==(a, nil)) # => false
p(Hash.!=(a, 1)) # => true
p(Hash.of(a: 1) == {a: 1}) # => false
28. Record
Record はフィールド名と値の組の集まりで、リテラル {x: 1, y: "a"} が作ります(値と型)。Ruby ではこの書き方は Hash ですが、Sake では Record であって Hash ではありません。Hash は Hash.of(x: 1) で作ります(Hash)。{}(空)と {"a" => 1}(=> 形)は静的に拒否されます。
Record の型はその (フィールド名, 型) の集合で、{x: Integer, y: String} のように表されます。同じ集合を持つ Record は同じ型です。フィールドの順序は型に関係なく、{y: 2, x: 1} は {x: 1, y: 2} と表示されます。クラスのインスタンスとは別物です(クラスは名前で、Record 型は構造で区別されます。クラス)。
フィールドはパターンで読みます: r => {x:, y: name} は局所変数 x にフィールド x、name にフィールド y を束縛します。一部のフィールドだけを挙げてもよく、case r in {x:} ... end も同じパターンです。無いフィールドを挙げると静的に type の問題(実行時なら KeyError)、Record でない値に当てても静的に type の問題(実行時なら TypeError)です。フィールドへの書き込み(r.x = v、r[:x] = v)はまだありません(どちらも静的に拒否。Indexable.[]= は Record を受け付けません)。
Record に使える演算子は ==、!= です(同じフィールドの集合を持ち、各値が等しいとき true)。添字 r[:x] はありません。
この章の 3 つの操作は、Record をデータとして扱う(任意の Record を歩く)ためのものです。引数の型は Any で、Record 以外を渡すと実行時に TypeError です。結果のフィールド順はフィールド名の順(表示と同じ)です。
28.1 keys
Record.keys(Any)
フィールド名を Symbol にした新しい Array を返します。順序はフィールド名の順で、リテラルに書いた順ではありません。
r = {x: 1, y: "a"}
p(Record.keys(r)) # => [:x, :y]
p(Record.keys({y: 2, x: 1})) # => [:x, :y]
```ruby error p(Record.keys(Hash.of(x: 1))) # !> TypeError: Record.keys: argument 1 must be a Record, got Hash
## values
`Record.values(Any)`
フィールドの値を `keys` と同じ順に並べた新しい Array を返します。Record とは独立で、Array の方は伸ばせます。要素の型は各フィールドの型の和です。
```ruby
r = {x: 1, y: "a"}
vs = Record.values(r)
p(vs) # => [1, "a"]
Array.push(vs, 3)
p(r) # => {x: 1, y: "a"}
28.2 to_h
Record.to_h(Any)
フィールド名を Symbol キーとする新しい Hash を返します。検査器にとってキーの型は Symbol、値の型は各フィールドの型の和なので、フィールドの型が違う Record では h[:x] の値をそのまま計算に使うと type の問題になります(Hash は値の型を 1 つしか持てないため)。フィールドの型がそろっていれば普通の Hash として使えます。入れ子の Record は Record のまま値になります。
r = {x: 1, y: 2}
h = Record.to_h(r)
p(h) # => {x: 1, y: 2}
p(Hash.fetch(h, :x) + Hash.fetch(h, :y)) # => 3
p(Record.to_h({name: "a", pos: {x: 1}})) # => {name: "a", pos: {x: 1}}
ruby error
h = Record.to_h({x: 1, y: "a"})
p(Hash.fetch(h, :x) + 1) # !> which the left operand's type does not support
29. Set
Set は重複の無い要素の集まりです。Set.of(1, 2) が Set を作り、Set.new(xs) は Array の要素から Set を作ります(Ruby の Set.new([1, 2]))。要素は Hash のキーと同じ規則で区別されます: Integer, Float, String, Symbol, true, false, nil, Time と、それらからなる Tuple, Record, Array, Hash, Set, クラスのインスタンスが要素になれ、Regexp, Range, 自分の等価(== か Comparable)を定義したクラスの値は実行時に TypeError です。同じ要素かどうかは Ruby の Hash と同じく hash と eql? で決まるので、1 と 1.0 は別の要素です。要素は追加した順に並び、反復もその順です。要素に使った String は複製が入るので、後で元の String を変えても Set は変わりません。
検査器は Set の要素の型を構築した場所ごとに追い、Set.add などの書き込みで広げます。ブロックを取る操作のブロックは、その要素の型の値を受け取ります。
Set に使える演算子は |(和)、&(積)、-(差)、==、!= です。^、+、<、<= などは使えず、部分集合の判定は Set.subset? などで行います。下の Set.|(x, y) などは演算子を関数の形で呼ぶものです(演算子と添字)。要素を取り出す多くの操作(select、map、sort、to_a など)は Set ではなく Array を返します。Ruby の Set#select が Set を返すのとは違います。
29.1 of
Set.of(*Any)
要素を並べて Set を作ります。重複した要素は 1 つになります。要素になれない値(Regexp, Range, 自分の等価を定義した クラスのインスタンス)は実行時に TypeError。Set.new は空の Set で、後から Set.add で要素を入れると要素の型がそれに広がります。
s = Set.of(1, 2, 2, 3)
p(s) # => Set[1, 2, 3]
p(Set.new) # => Set[]
p(Set.size(Set.of(1, 1.0))) # => 2
```ruby error p(Set.of(/a/)) # !> TypeError: Set.of: Regexp cannot be a Hash key or Set element
## new
`Set.new([Array])`
Array の要素から Set を作ります(Ruby の `Set.new([1, 2])`)。引数の無い `Set.new` は空の Set です。重複した要素は 1 つになり、要素になれない値は `TypeError`。
```ruby
p(Set.new(Any[3, 1, 3])) # => Set[3, 1]
p(Set.new) # => Set[]
29.2 Set[]
Set[*Any]
Set の Array を作ります。どの T でも T[...] が T の Array を作るのと同じで、各要素は Set でなければならず、書き込みのたびに検査されます。
ss = Set[Set.of(1), Set.new]
p(ss) # => [Set[1], Set[]]
29.3 length, size
Set.length(x)
Set.size(x)
要素の個数(Integer)。
p(Set.size(Set.of(1, 2, 3))) # => 3
p(Set.length(Set.new)) # => 0
29.4 empty?
Set.empty?(x)
要素が無いとき true。
p(Set.empty?(Set.new)) # => true
p(Set.empty?(Set.of(1))) # => false
29.5 include?, member?
Set.include?(x, Any)
Set.member?(x, Any)
値が要素にあるとき true。要素の比較は Hash のキーと同じ(hash と eql?)なので、1 の入った Set に 1.0 はありません。Set の要素の型と違う型の値を聞いても静的な問題にはならず、false です。
s = Set.of(1, 2)
p(Set.include?(s, 1)) # => true
p(Set.include?(s, 1.0)) # => false
p(Set.member?(s, "a")) # => false
p(Set.include?(Set.of([1, 2]), [1, 2])) # => true
29.6 add
Set.add(x, Any)
値を要素に加え、Set 自身を返します(その場で変更)。すでにある値なら何も変わりません。要素になれない値は実行時に TypeError。加えた値の型は、その Set の要素の型に加わります。
s = Set.of(1)
p(Set.add(s, 2)) # => Set[1, 2]
p(Set.add(s, 2)) # => Set[1, 2]
p(s) # => Set[1, 2]
```ruby error Set.add(Set.of(1), 1..2) # !> TypeError: Set.add: Range cannot be a Hash key or Set element
## add?
`Set.add?(x, Any)`
`add` と同じですが、その値がすでにあったときは **nil** を返します(新しく加えたときは Set 自身)。戻り値は Set か nil なので、`--strict`(レベル 2)は確かめずに使うと報告します。「初めて見た値か」の判定に使います。
```ruby
s = Set.of(1)
p(Set.add?(s, 2)) # => Set[1, 2]
p(Set.add?(s, 2)) # => nil
```ruby error s = Set.of(1) x = Set.add?(s, 2) p(Set.size(x)) # !> argument 1 may be nil
## delete
`Set.delete(x, Any)`
値を要素から取り除き、Set 自身を返します(その場で変更)。無い値なら何も変わりません。Ruby と同じです。
```ruby
s = Set.of(1, 2, 3)
p(Set.delete(s, 2)) # => Set[1, 3]
p(Set.delete(s, 99)) # => Set[1, 3]
29.7 delete?
Set.delete?(x, Any)
delete と同じですが、その値が無かったときは nil を返します(取り除いたときは Set 自身)。戻り値は --strict(レベル 2)で確かめずに使うと報告されます。
s = Set.of(1, 2)
p(Set.delete?(s, 2)) # => Set[1]
p(Set.delete?(s, 2)) # => nil
29.8 merge
Set.merge(x, Set)
もう一方の Set の要素をすべて加え、Set 自身を返します(その場で変更)。引数は Set でなければなりません(Array は静的に type の問題。Ruby の merge は任意の列挙を取ります)。新しい Set が欲しいときは | か union を使います。
s = Set.of(1, 2)
p(Set.merge(s, Set.of(2, 3))) # => Set[1, 2, 3]
p(s) # => Set[1, 2, 3]
```ruby error Set.merge(Set.of(1), Any[2]) # !> argument 2 must be Set, but is Array
## subtract
`Set.subtract(x, Any)`
引数に含まれる要素をすべて取り除き、Set 自身を返します(その場で変更)。引数は Set、Array、Tuple、Range のいずれかです。検査器は調べず(署名は `Any`)、それ以外は実行時に `TypeError`(`argument 2 must be Set, Array, Tuple, or Range, got Integer`)。新しい Set が欲しいときは `-` か `difference` を使います。
```ruby
s = Set.of(1, 2, 3, 4, 5)
p(Set.subtract(s, Set.of(1))) # => Set[2, 3, 4, 5]
p(Set.subtract(s, Any[2])) # => Set[3, 4, 5]
p(Set.subtract(s, [3])) # => Set[4, 5]
p(Set.subtract(s, 4..4)) # => Set[5]
p(s) # => Set[5]
```ruby error Set.subtract(Set.of(1, 2), 1) # !> TypeError: Set.subtract: argument 2 must be Set, Array, Tuple, or Range, got Integer
## replace
`Set.replace(x, Set)`
要素をすべてもう一方の Set の要素に置き換え、Set 自身を返します(その場で変更)。もう一方の要素の型がこの Set の要素の型に加わります。
```ruby
s = Set.of(1, 2)
p(Set.replace(s, Set.of(7, 8))) # => Set[7, 8]
p(s) # => Set[7, 8]
29.9 clear
Set.clear(x)
要素をすべて取り除き、空になった Set 自身を返します。
s = Set.of(1, 2)
p(Set.clear(s)) # => Set[]
p(Set.empty?(s)) # => true
29.10 |, union
Set.|(x, Any)
Set.union(x, Set)
和集合: 両方の要素をすべて持つ 新しい Set。a | b の関数形が Set.|。右側は Set でなければならず、Array などは静的に type の問題です。結果の要素の型は両方の要素の型の和です。
a = Set.of(1, 2)
b = Set.of(2, 3)
p(a | b) # => Set[1, 2, 3]
p(Set.union(a, b)) # => Set[1, 2, 3]
p(a) # => Set[1, 2]
```ruby error p(Set.of(1) | Any[2]) # !> Bitwise.|: the operands are (Set@L1[Integer], Array@L1[Integer])
## &, intersection
`Set.&(x, Any)`
`Set.intersection(x, Set)`
積集合: 両方にある要素だけの **新しい** Set。`a & b` の関数形が `Set.&`。右側は Set でなければなりません。
```ruby
a = Set.of(1, 2, 3)
b = Set.of(2, 3, 4)
p(a & b) # => Set[2, 3]
p(Set.intersection(a, b)) # => Set[2, 3]
29.11 -, difference
Set.-(x, Any)
Set.difference(x, Set)
差集合: 左にあって右に無い要素の 新しい Set。a - b の関数形が Set.-。右側は Set でなければなりません。
a = Set.of(1, 2, 3)
b = Set.of(2)
p(a - b) # => Set[1, 3]
p(Set.difference(a, b)) # => Set[1, 3]
p(a) # => Set[1, 2, 3]
29.12 ==, !=
Set.==(x, Any)
Set.!=(x, Any)
2 つの Set が同じ要素を持つとき true(順序は関係ありません。!= はその否定)。右側が Set 以外(Array や nil)なら等しくありません。
p(Set.of(1, 2) == Set.of(2, 1)) # => true
p(Set.of(1, 2) == Set.of(1)) # => false
p(Set.of(1) != Set.of(1)) # => false
p(Set.of(1, 2) == Any[1, 2]) # => false
29.13 subset?, superset?
Set.subset?(x, Set)
Set.superset?(x, Set)
subset? は x のすべての要素が引数の Set にあるとき true、superset? はその逆です。等しい Set 同士も true。引数は Set でなければなりません(Array は静的に type の問題)。Ruby の <=、>= 演算子は Set には使えないので、これらの関数を使います。
a = Set.of(1, 2)
b = Set.of(1, 2, 3)
p(Set.subset?(a, b)) # => true
p(Set.subset?(a, a)) # => true
p(Set.superset?(b, a)) # => true
p(Set.superset?(a, b)) # => false
```ruby error p(Set.of(1) <= Set.of(1, 2)) # !> Comparable.<=: the operands are (Set@L1[Integer], Set@L1[Integer])
## proper_subset?, proper_superset?
`Set.proper_subset?(x, Set)`
`Set.proper_superset?(x, Set)`
真部分集合の判定: `subset?`、`superset?` と同じですが、等しい Set 同士は false です(Ruby の `<`、`>`)。
```ruby
a = Set.of(1, 2)
b = Set.of(1, 2, 3)
p(Set.proper_subset?(a, b)) # => true
p(Set.proper_subset?(a, a)) # => false
p(Set.proper_superset?(b, a)) # => true
29.14 disjoint?, intersect?
Set.disjoint?(x, Set)
Set.intersect?(x, Set)
intersect? は共通の要素が 1 つでもあるとき true、disjoint? はその否定です。
p(Set.intersect?(Set.of(1, 2), Set.of(2, 3))) # => true
p(Set.disjoint?(Set.of(1, 2), Set.of(2, 3))) # => false
p(Set.disjoint?(Set.of(1), Set.of(9))) # => true
29.15 each, each_entry
Set.each(x) { }
Set.each_entry(x) { }
要素を加えた順にブロックへ 1 つずつ渡し、Set 自身を返します。ブロックは必須です(Ruby のようにブロック無しで Enumerator を得ることはできません)。
s = Set.of(3, 1, 2)
Set.each(s) { |x| puts(x) }
# => 3
# => 1
# => 2
p(Set.each_entry(s) { |x| x }) # => Set[3, 1, 2]
29.16 each_with_index
Set.each_with_index(x) { }
要素とその位置(0 から)をブロックへ渡し、Set 自身を返します。
Set.each_with_index(Set.of("a", "b")) { |x, i| puts("#{i}: #{x}") }
# => 0: a
# => 1: b
29.17 each_with_object
Set.each_with_object(x, Any) { }
要素と第 2 引数の値をブロックへ渡して回り、第 2 引数の値を返します。累積用の Array や Hash を渡す形で使います。
a = Set.each_with_object(Set.of(1, 2), Any[]) { |x, acc| Array.push(acc, x * 2) }
p(a) # => [2, 4]
29.18 reverse_each
Set.reverse_each(x) { }
要素を加えた順の逆にブロックへ渡し、Set 自身を返します。
Set.reverse_each(Set.of(1, 2, 3)) { |x| puts(x) }
# => 3
# => 2
# => 1
29.19 each_slice, each_cons
Set.each_slice(x, Integer) { }
Set.each_cons(x, Integer) { }
each_slice は要素を n 個ずつの Array に切ってブロックへ渡し(最後は短くてよい)、each_cons は連続する n 個の Array を 1 つずつずらして渡します。どちらも Set 自身を返します。n が 0 以下なら ArgumentError。
s = Set.of(1, 2, 3, 4)
Set.each_slice(s, 3) { |a| p(a) }
# => [1, 2, 3]
# => [4]
Set.each_cons(s, 3) { |a| p(a) }
# => [1, 2, 3]
# => [2, 3, 4]
```ruby error Set.each_slice(Set.of(1), 0) { |a| p(a) } # !> ArgumentError: Set.each_slice: invalid slice size
## cycle
`Set.cycle(x, Integer) { }`
要素の列を n 回繰り返してブロックへ渡し、**nil** を返します。回数は必須です(Ruby の無限の `cycle` はありません)。n が 0 以下なら何もしません。
```ruby
Set.cycle(Set.of(1, 2), 2) { |x| print(x) }
puts("")
# => 1212
p(Set.cycle(Set.of(1), 0) { |x| p(x) }) # => nil
29.20 map, collect
Set.map(x) { }
Set.collect(x) { }
各要素にブロックを適用した結果の Array(Set ではありません。要素の順に並び、重複もそのまま)。要素の型はブロックの結果の型です。
p(Set.map(Set.of(1, 2, 3)) { |x| x * 10 }) # => [10, 20, 30]
p(Set.collect(Set.of(1, 2, 3)) { |x| x % 2 }) # => [1, 0, 1]
29.21 map!, collect!
Set.map!(x) { }
Set.collect!(x) { }
各要素をブロックの結果に置き換え、Set 自身を返します(その場で変更)。結果が重なれば要素は減ります。ブロックの結果の型が要素の型に加わります。要素になれない値を返すと実行時に TypeError。
s = Set.of(1, 2, 3)
p(Set.map!(s) { |x| x % 2 }) # => Set[1, 0]
p(s) # => Set[1, 0]
29.22 flat_map, collect_concat
Set.flat_map(x) { }
Set.collect_concat(x) { }
ブロックは各要素に対して Array か Tuple を返し、それらをつなげた 1 つの Array が結果です。ブロックがどちらでもない値を返すと実行時に TypeError(the block must return an Array or a Tuple, got Integer。Ruby のように値をそのまま並べはしません)。
p(Set.flat_map(Set.of(1, 2)) { |x| Any[x, x * 10] }) # => [1, 10, 2, 20]
p(Set.flat_map(Set.of(1, 2)) { |x| [x, x * 10] }) # => [1, 10, 2, 20]
```ruby error Set.flat_map(Set.of(1)) { |x| x } # !> TypeError: Set.flat_map: the block must return an Array or a Tuple, got Integer
## select, filter, find_all
`Set.select(x) { }`
`Set.filter(x) { }`
`Set.find_all(x) { }`
ブロックが真を返した要素の **Array**(Ruby の `Set#select` は Set を返しますが、Sake では Array です)。Set が欲しいときは `select!` でその場で絞るか、`Set.of(*...)` ではなく `Array.to_set` を使います。
```ruby
s = Set.of(1, 2, 3, 4)
p(Set.select(s) { |x| Integer.even?(x) }) # => [2, 4]
p(Set.filter(s) { |x| x > 3 }) # => [4]
p(Set.find_all(s) { |x| x < 0 }) # => []
29.23 reject
Set.reject(x) { }
ブロックが偽を返した要素の Array(select の逆)。
p(Set.reject(Set.of(1, 2, 3, 4)) { |x| Integer.even?(x) }) # => [1, 3]
29.24 select!, filter!, reject!
Set.select!(x) { }
Set.filter!(x) { }
Set.reject!(x) { }
select!、filter! はブロックが偽の要素を、reject! はブロックが真の要素をその場で取り除きます。何か取り除いたときは Set 自身、何も変わらなかったときは nil を返します(Ruby と同じ)。戻り値は --strict(レベル 2)で確かめずに使うと報告されます。常に Set を返す形は keep_if、delete_if です。
s = Set.of(1, 2, 3, 4)
p(Set.select!(s) { |x| x > 1 }) # => Set[2, 3, 4]
p(Set.select!(s) { |x| x > 1 }) # => nil
p(Set.reject!(s) { |x| x > 3 }) # => Set[2, 3]
p(Set.filter!(s) { |x| x < 9 }) # => nil
p(s) # => Set[2, 3]
29.25 keep_if, delete_if
Set.keep_if(x) { }
Set.delete_if(x) { }
keep_if はブロックが真の要素だけを残し、delete_if はブロックが真の要素を取り除きます(その場で変更)。どちらも常に Set 自身を返します。
s = Set.of(1, 2, 3, 4)
p(Set.keep_if(s) { |x| x > 1 }) # => Set[2, 3, 4]
p(Set.delete_if(s) { |x| x > 3 }) # => Set[2, 3]
p(Set.delete_if(s) { |x| x > 9 }) # => Set[2, 3]
29.26 filter_map
Set.filter_map(x) { }
各要素にブロックを適用し、nil と false でない結果だけを集めた Array。結果の要素の型からは nil が除かれます。
p(Set.filter_map(Set.of(1, 2, 3, 4)) { |x| Integer.even?(x) ? x * 10 : nil }) # => [20, 40]
29.27 partition
Set.partition(x) { }
ブロックが真の要素の Array と偽の要素の Array の Tuple [yes, no]。
ev, od = Set.partition(Set.of(1, 2, 3, 4)) { |x| Integer.even?(x) }
p(ev) # => [2, 4]
p(od) # => [1, 3]
29.28 group_by
Set.group_by(x) { }
ブロックの結果をキー、その結果になった要素の Array を値とする Hash。キーになれない値を返すと実行時に TypeError。
p(Set.group_by(Set.of(1, 2, 3, 4)) { |x| x % 2 }) # => {1 => [1, 3], 0 => [2, 4]}
29.29 classify
Set.classify(x) { }
group_by と同じ分け方で、値が Array ではなく Set の Hash を返します(Ruby の Set#classify)。
p(Set.classify(Set.of(1, 2, 3, 4)) { |x| x % 2 }) # => {1 => Set[1, 3], 0 => Set[2, 4]}
29.30 tally
Set.tally(x)
要素をキー、その個数を値とする Hash。Set の要素は重複しないので値はすべて 1 です(Array から来た操作で、形をそろえるためにあります)。
p(Set.tally(Set.of("a", "b"))) # => {"a" => 1, "b" => 1}
29.31 chunk_while, slice_when
Set.chunk_while(x) { }
Set.slice_when(x) { }
隣り合う 2 要素をブロックに渡し、chunk_while はブロックが真の間を 1 つの区間に、slice_when はブロックが真のところで区切ります。結果は Array の Array です。
s = Set.of(1, 2, 4, 5, 7)
p(Set.chunk_while(s) { |a, b| b == a + 1 }) # => [[1, 2], [4, 5], [7]]
p(Set.slice_when(s) { |a, b| b > a + 1 }) # => [[1, 2], [4, 5], [7]]
29.32 slice_before, slice_after
Set.slice_before(x) { }
Set.slice_after(x) { }
ブロックが真を返す要素の直前(slice_before)または直後(slice_after)で区切った、Array の Array。
s = Set.of(1, 2, 3, 4)
p(Set.slice_before(s) { |x| x == 3 }) # => [[1, 2], [3, 4]]
p(Set.slice_after(s) { |x| x == 2 }) # => [[1, 2], [3, 4]]
29.33 find, detect
Set.find(x) { }
Set.detect(x) { }
ブロックが真を返した最初の要素。どの要素も真にならなければ nil で、--strict(レベル 2)は確かめずに使うと報告します。
s = Set.of(1, 2, 3)
p(Set.find(s) { |x| x > 1 }) # => 2
p(Set.detect(s) { |x| x > 9 }) # => nil
```ruby error x = Set.find(Set.of(1)) { |v| v > 0 } p(x + 1) # !> the operands may be nil
## find_index
`Set.find_index(x) { }`
ブロックが真を返した最初の要素の位置(Integer、加えた順で 0 から)。無ければ **nil**(`--strict` レベル 2 で報告)。
```ruby
s = Set.of("a", "b", "c")
p(Set.find_index(s) { |x| x == "c" }) # => 2
p(Set.find_index(s) { |x| x == "z" }) # => nil
29.34 any?, all?, none?, one?
Set.any?(x) { }
Set.all?(x) { }
Set.none?(x) { }
Set.one?(x) { }
ブロックが真を返す要素が、1 つ以上ある・すべてである・1 つも無い・ちょうど 1 つあるとき true。ブロックは必須です(Ruby のブロック無しの形はありません。空かどうかは empty?)。
s = Set.of(1, 2, 3)
p(Set.any?(s) { |x| x > 2 }) # => true
p(Set.all?(s) { |x| x > 2 }) # => false
p(Set.none?(s) { |x| x > 9 }) # => true
p(Set.one?(s) { |x| x == 1 }) # => true
```ruby error p(Set.any?(Set.of(1))) # !> Set.any? requires a block
## count
`Set.count(x) [{ }]`
ブロック無しなら要素の個数、ブロック付きならブロックが真を返した要素の個数(Integer)。
```ruby
s = Set.of(1, 2, 3)
p(Set.count(s)) # => 3
p(Set.count(s) { |x| x > 1 }) # => 2
29.35 sum
Set.sum(x, [Integer|Float|Rational|Complex])
要素の和。初期値(省略時 0)に要素を順に足します。要素と初期値は数でなければならず、初期値に String などを渡すのは静的に type の問題、数でない要素は実行時に TypeError(String can't be coerced into Integer)です(Ruby の sum("") で String をつなぐ形はありません。join を使います)。空の Set では初期値。
p(Set.sum(Set.of(1, 2, 3))) # => 6
p(Set.sum(Set.of(1, 2), 0.5)) # => 3.5
p(Set.sum(Set.new)) # => 0
```ruby error p(Set.sum(Set.of(“a”, “b”), “”)) # !> argument 2 must be Integer|Float|Rational|Complex, but is String
## reduce, inject
`Set.reduce(x, Any) { }`
`Set.inject(x, Any) { }`
初期値から始め、ブロックに累積値と要素を渡して畳み込みます。初期値は **必須** です(Ruby の、最初の要素を初期値にする形はありません)。結果の型は初期値とブロックの結果の型から決まります。
```ruby
p(Set.reduce(Set.of(1, 2, 3), 0) { |acc, x| acc + x }) # => 6
p(Set.inject(Set.of(1, 2, 3), 1) { |acc, x| acc * x }) # => 6
```ruby error p(Set.reduce(Set.of(1)) { |a, b| a + b }) # !> wrong number of arguments for Set.reduce (given 1, expected 2)
## min, max
`Set.min(x)`
`Set.max(x)`
最小・最大の要素。要素は互いに比べられなければなりません: 比べられない型が要素の型に混ざる Set(`Set.of(1, "a")`)は検査器が静的に `type` の問題として退け、nil になりうる要素は `nil` の問題です。検査器に見えないとき(`Set.of(1.0, Float.NAN)`)は実行時に `ArgumentError`(`cannot compare the elements`)。空の Set では **nil** で、`Array.min` と同じ外れの nil です: 確かめずに使うことは `--strict=3` でだけ報告されます。
```ruby
s = Set.of(3, 1, 2)
p(Set.min(s)) # => 1
p(Set.max(s)) # => 3
p(Set.max(Set.new)) # => nil
```ruby error p(Set.min(Set.of(1, “a”))) # !> Set.min: elements compared in order may be (Integer, String), which cannot be compared
```ruby error
p(Set.min(Set.of(1.0, Float.NAN))) # !> ArgumentError: Set.min: cannot compare the elements
29.36 minmax
Set.minmax(x)
最小と最大の Tuple [min, max]。空の Set では [nil, nil] で、これは外れの nil です(要素を確かめずに使うことは --strict=3 でだけ報告)。比較の規則は静的な検査を含めて min、max と同じ。
lo, hi = Set.minmax(Set.of(3, 1, 2))
p([lo, hi]) # => [1, 3]
p(Set.minmax(Set.new)) # => [nil, nil]
29.37 min_by, max_by
Set.min_by(x) { }
Set.max_by(x) { }
ブロックの結果が最小・最大になる要素。ブロックの結果同士は比べられなければならず、比べられない型が結果の型に混ざるブロックは min と同じく静的に退けられます(type)。空の Set では nil で、外れの nil です(--strict=3 でだけ報告)。
s = Set.of("bb", "a", "ccc")
p(Set.min_by(s) { |x| String.size(x) }) # => "a"
p(Set.max_by(s) { |x| String.size(x) }) # => "ccc"
p(Set.max_by(Set.new) { |x| x }) # => nil
```ruby error p(Set.min_by(Set.of(1, 2)) { |x| x == 1 ? 1 : “a” }) # !> Set.min_by: elements compared in order may be (Integer, String), which cannot be compared
## minmax_by
`Set.minmax_by(x) { }`
ブロックの結果で見た最小と最大の要素の Tuple。空の Set では `[nil, nil]`(外れの nil、レベル 3)。ブロックの結果の検査は `min_by` と同じ。
```ruby
p(Set.minmax_by(Set.of(1, 2, 3)) { |x| -x }) # => [3, 1]
29.38 sort
Set.sort(x)
要素を昇順に並べた Array。要素は互いに比べられなければなりません: 比べられない型が要素の型に混ざる Set は静的に退けられ(type)、nil になりうる要素は nil の問題です。検査器に見えないとき(Set.of(1.0, Float.NAN))は実行時に ArgumentError(cannot compare the elements)。
p(Set.sort(Set.of(3, 1, 2))) # => [1, 2, 3]
```ruby error p(Set.sort(Set.of(1, “a”))) # !> Set.sort: elements compared in order may be (Integer, String), which cannot be compared
## sort_by
`Set.sort_by(x) { }`
ブロックの結果の昇順に要素を並べた Array。Tuple を返すと辞書順で並びます。ブロックの結果同士は比べられなければならず、比べられない型が結果の型に混ざると静的に退けられます(`type`)。
```ruby
p(Set.sort_by(Set.of("bb", "a", "ccc")) { |x| String.size(x) }) # => ["a", "bb", "ccc"]
p(Set.sort_by(Set.of(1, 2, 3)) { |x| -x }) # => [3, 2, 1]
```ruby error p(Set.sort_by(Set.of(1, 2)) { |x| x == 1 ? 1 : “a” }) # !> Set.sort_by: elements compared in order may be (Integer, String), which cannot be compared
## first
`Set.first(x)`
最初に加えた要素。空の Set では **nil** で、`Array.first` と同じ外れの nil です: 確かめずに使うことは `--strict=3` でだけ報告されます。個数を指定する形はありません(`take` を使います)。
```ruby
p(Set.first(Set.of(3, 1))) # => 3
p(Set.first(Set.new)) # => nil
29.39 take, drop
Set.take(x, Integer)
Set.drop(x, Integer)
先頭の n 要素の Array、先頭の n 要素を除いた残りの Array。n が負なら ArgumentError。
s = Set.of(1, 2, 3, 4)
p(Set.take(s, 2)) # => [1, 2]
p(Set.drop(s, 2)) # => [3, 4]
p(Set.take(s, 9)) # => [1, 2, 3, 4]
```ruby error p(Set.take(Set.of(1), -1)) # !> ArgumentError: Set.take: attempt to take negative size
## take_while, drop_while
`Set.take_while(x) { }`
`Set.drop_while(x) { }`
先頭からブロックが真の間の要素の Array、その残りの Array。
```ruby
s = Set.of(1, 2, 3, 1)
p(Set.take_while(s) { |x| x < 3 }) # => [1, 2]
p(Set.drop_while(s) { |x| x < 3 }) # => [3]
29.40 to_a, entries
Set.to_a(x)
Set.entries(x)
要素を加えた順に並べた新しい Array。Array を変えても Set は変わりません。
s = Set.of(3, 1)
a = Set.to_a(s)
Array.push(a, 9)
p(a) # => [3, 1, 9]
p(s) # => Set[3, 1]
p(Set.entries(s)) # => [3, 1]
29.41 to_set
Set.to_set(x)
Set 自身を返します(複製ではありません)。Array の Array.to_set と形をそろえるための操作です。
s = Set.of(1)
Set.add(Set.to_set(s), 2)
p(s) # => Set[1, 2]
29.42 uniq
Set.uniq(x)
要素の Array。Set に重複は無いので to_a と同じです。
p(Set.uniq(Set.of(1, 2))) # => [1, 2]
29.43 compact
Set.compact(x)
nil を除いた要素の Array。
p(Set.compact(Set.of(1, nil, 2))) # => [1, 2]
29.44 flatten
Set.flatten(x)
要素の Set を再帰的に開いた 新しい Set(Ruby の Set#flatten)。Array の要素は開きません。
p(Set.flatten(Set.of(1, Set.of(2, Set.of(3))))) # => Set[1, 2, 3]
p(Set.flatten(Set.of(1, 2))) # => Set[1, 2]
29.45 zip
Set.zip(x, *Array)
要素と各 Array の同じ位置の要素を組にした Tuple の Array。Array が短ければ nil が入ります。引数は Array でなければなりません(Set は静的に type の問題)。
p(Set.zip(Set.of(1, 2, 3), Any["a", "b"])) # => [[1, "a"], [2, "b"], [3, nil]]
p(Set.zip(Set.of(1, 2))) # => [[1], [2]]
29.46 join
Set.join(x, [String])
要素を to_s でつなげた String。区切り(省略時 ““)を間に入れます。要素の Array は中まで開いてつなげます。
s = Set.of(1, 2, 3)
p(Set.join(s)) # => "123"
p(Set.join(s, ", ")) # => "1, 2, 3"
30. Time
Time は 1 つの時刻です: 1970-01-01 00:00:00 UTC からの秒数(有理数の精度)と、表示に使う UTC からのオフセットを持ちます。リテラルは無く、Time.now、Time.at(秒)、Time.new(年, 月, 日, ...) で作ります(値と型)。Ruby の Time と同じ値ですが、Ruby の t.utc、t.localtime が t 自身を書き換えるのに対し、Sake の操作はすべて新しい Time を返し、主語を変えません。Time に型付き Array Time[] は無く、Time の並びには Any[...] を使います。
ゾーン。 in: キーワードと getlocal・new のゾーン引数は固定の UTC オフセットです: "+09:00"・"-05:00"、"UTC"・"Z"、軍用の 1 文字 "A".."I", "K".."Z"、または秒数の Integer。"Asia/Tokyo" のような地域名は受け付けません(ArgumentError)。in: を省くと実行環境のローカルゾーンになるので、出力が環境に依らない例はすべて in: を付けています。"UTC"・"Z" で作った Time は UTC モード(utc? が true、to_s の末尾が UTC)で、"+00:00" はオフセット 0 の普通の Time です(utc? は false、末尾は +0000)。
Time に使える演算子は +、-(右が秒数の Integer・Float・Rational。Time - Time は秒数の Float)、==、!=、<、<=、>、>=、<=>(右は Time)です。下の Time.+(x, y) などはその関数形です(演算子と添字)。Time.strftime(t, fmt) と Time.iso8601(t) が文字列にします。String からの読み取り(Ruby の Time.parse、Time.iso8601(s))は組み込みにはありません。
30.1 now
Time.now([in: String|Integer])
今の時刻(Time)。in: を与えるとそのオフセットで表示される Time、省くとローカルゾーンです。不正なゾーンは ArgumentError。
t = Time.now(in: "UTC")
p(Time.utc?(t)) # => true
p(Time.year(t) >= 2026) # => true
p(Time.utc_offset(Time.now(in: "+09:00"))) # => 32400
30.2 at
Time.at(Integer|Float|Rational|Time, [in: String|Integer])
1970-01-01 00:00:00 UTC から 秒 後の Time(Ruby の Time.at)。Integer、Float、Rational のどれでもよく、Rational なら精度が保たれます。Time を渡すと同じ時刻の Time です(in: が無ければゾーンも同じ)。in: でゾーンを選びます。不正なゾーンは ArgumentError。
t = Time.at(0, in: "UTC")
p(t) # => 1970-01-01 00:00:00 UTC
p(Time.at(1.5, in: "UTC")) # => 1970-01-01 00:00:01.5 UTC
p(Time.subsec(Time.at(1r/3, in: "UTC"))) # => (1/3)
p(Time.at(t, in: "+09:00")) # => 1970-01-01 09:00:00 +0900
p(Time.at(t) == t) # => true
```ruby error Time.at(0, in: “Asia/Tokyo”) # !> ArgumentError: Time.at: “+HH:MM”, “-HH:MM”, “UTC” or “A”..”I”,”K”..”Z” expected for utc_offset: Asia/Tokyo
## new
`Time.new(Integer, [Integer], [Integer], [Integer], [Integer], [Integer|Float|Rational], [String|Integer], [in: String|Integer])`
年・月・日・時・分・秒・ゾーンから Time を作ります(Ruby の `Time.new(y, m, d, h, min, s, zone)`)。年は必須で、残りは省けます(月と日は 1、時・分・秒は 0 が既定)。秒は Float か Rational で小数部を持てます。ゾーンは 7 番目の引数か `in:` のどちらか一方で(両方は `ArgumentError`)、無ければローカルゾーンです。月が 1..12、時が 0..23 などの範囲を外れると `ArgumentError`(`mon out of range`)。Ruby の引数無しの `Time.new`(今の時刻)はありません。`Time.now` を使います。
```ruby
t = Time.new(2024, 2, 29, 12, 30, 45, "+09:00")
puts(Time.to_s(t)) # => 2024-02-29 12:30:45 +0900
puts(Time.to_s(Time.new(2024, in: "UTC"))) # => 2024-01-01 00:00:00 UTC
puts(Time.to_s(Time.new(2024, 3, 4, 5, in: "UTC"))) # => 2024-03-04 05:00:00 UTC
p(Time.subsec(Time.new(2024, 1, 1, 0, 0, 7.5, in: "Z"))) # => (1/2)
puts(Time.to_s(Time.new(2024, 3, 4, 5, 6, 7, 3600))) # => 2024-03-04 05:06:07 +0100
```ruby error Time.new(2024, 13, 1, in: “UTC”) # !> ArgumentError: Time.new: mon out of range
## year, month, mon, day, mday, hour, min, sec
`Time.year(x)`
`Time.month(x)`
`Time.mon(x)`
`Time.day(x)`
`Time.mday(x)`
`Time.hour(x)`
`Time.min(x)`
`Time.sec(x)`
時刻の各部分(Integer)を、その Time のオフソットで読みます: 年、月(1..12。`mon` は別名)、日(1..31。`mday` は別名)、時(0..23)、分(0..59)、秒(0..60。小数部は含みません。`subsec`)。
```ruby
t = Time.new(2024, 2, 29, 12, 30, 45.5, in: "+09:00")
p([Time.year(t), Time.month(t), Time.day(t)]) # => [2024, 2, 29]
p([Time.hour(t), Time.min(t), Time.sec(t)]) # => [12, 30, 45]
p(Time.mon(t) == Time.month(t)) # => true
p(Time.hour(Time.utc(t))) # => 3
30.3 wday, yday
Time.wday(x)
Time.yday(x)
曜日(Integer。日曜が 0、土曜が 6)と、年の中の日(1 月 1 日が 1、閏年の 12 月 31 日が 366)。
t = Time.new(2024, 2, 29, in: "UTC")
p(Time.wday(t)) # => 4
p(Time.yday(t)) # => 60
p(Time.wday(Time.at(0, in: "UTC"))) # => 4
30.4 sunday?, monday?, tuesday?, wednesday?, thursday?, friday?, saturday?
Time.sunday?(x)
Time.monday?(x)
Time.tuesday?(x)
Time.wednesday?(x)
Time.thursday?(x)
Time.friday?(x)
Time.saturday?(x)
その曜日なら true。Time.wday(t) == 4 と Time.thursday?(t) は同じです。
t = Time.new(2024, 2, 29, in: "UTC")
p(Time.thursday?(t)) # => true
p(Time.friday?(t)) # => false
p(Time.sunday?(t + 3 * 86400)) # => true
30.5 nsec, usec, subsec, tv_sec, tv_nsec, tv_usec
Time.nsec(x)
Time.usec(x)
Time.subsec(x)
Time.tv_sec(x)
Time.tv_nsec(x)
Time.tv_usec(x)
秒より細かい部分。nsec(tv_nsec は別名)はナノ秒(Integer、0..999999999)、usec(tv_usec は別名)はマイクロ秒(Integer、0..999999)、subsec は秒の小数部をそのまま(Integer の 0、または Rational)。tv_sec は to_i と同じ、1970 年からの整数秒です。
t = Time.at(1700000000.123456789r, in: "UTC")
p(Time.nsec(t)) # => 123456789
p(Time.usec(t)) # => 123456
p(Time.subsec(Time.at(1.5, in: "UTC"))) # => (1/2)
p(Time.subsec(Time.at(2, in: "UTC"))) # => 0
p(Time.tv_sec(t)) # => 1700000000
p(Time.tv_nsec(t) == Time.nsec(t)) # => true
30.6 to_i, to_f, to_r
Time.to_i(x)
Time.to_f(x)
Time.to_r(x)
1970-01-01 00:00:00 UTC からの秒数を、Integer(小数部は切り捨て)、Float、Rational(正確)で返します。ゾーンには依りません。Time.at の逆です。
t = Time.at(1.5, in: "UTC")
p(Time.to_i(t)) # => 1
p(Time.to_f(t)) # => 1.5
p(Time.to_r(t)) # => (3/2)
p(Time.to_i(Time.at(100, in: "+09:00"))) # => 100
30.7 to_a
Time.to_a(x)
Ruby の t.to_a: [sec, min, hour, day, month, year, wday, yday, isdst, zone] の 10 要素の Tuple。最後の zone は Time.zone と同じで、UTC なら "UTC"、in: "+09:00" のような固定オフセットの Time では nil です。この要素の型は String | nil なので、確かめずに String として使うと --strict(レベル 2)が nil の問題として止めます。他の 9 つは Integer と Boolean です。
p(Time.to_a(Time.at(0, in: "UTC"))) # => [0, 0, 0, 1, 1, 1970, 4, 1, false, "UTC"]
p(Time.to_a(Time.at(0, in: "+09:00"))) # => [0, 0, 9, 1, 1, 1970, 4, 1, false, nil]
sec, min, hour, day, month, year, wday, yday, isdst, zone = Time.to_a(Time.at(0, in: "UTC"))
p(zone ? String.size(zone) : 0) # => 3
```ruby error zone = Time.to_a(Time.at(0, in: “+09:00”))[9] p(String.size(zone)) # !> String.size: argument 1 may be nil
## to_s
`Time.to_s(x)`
`"2024-02-29 12:30:45 +0900"` の形の String(Ruby の `t.to_s`)。UTC モードの Time は末尾が `UTC`。小数部は書きません(`p(t)` と `inspect` は書きます)。`puts(t)` と `"#{t}"` もこの形です。
```ruby
t = Time.new(2024, 2, 29, 12, 30, 45.5, in: "+09:00")
puts(Time.to_s(t)) # => 2024-02-29 12:30:45 +0900
puts(Time.utc(t)) # => 2024-02-29 03:30:45 UTC
p(Time.at(0, in: "+00:00")) # => 1970-01-01 00:00:00 +0000
30.8 strftime
Time.strftime(x, String)
Ruby の strftime の書式で String を作ります: %Y 年、%m 月、%d 日、%H・%M・%S 時分秒、%F(%Y-%m-%d)、%T(%H:%M:%S)、%z(+0900)、%:z(+09:00)、%Z(ゾーン名。固定オフセットの Time では空)、%A・%a 曜日名、%B・%b 月名、%j 年の中の日、%s 1970 年からの秒、%L ミリ秒、%N ナノ秒(%3N で桁数)、%-d などの - は 0 埋めなし、%% は %。未知の指示子はそのまま残ります。
t = Time.new(2024, 2, 29, 12, 30, 45, in: "+09:00")
puts(Time.strftime(t, "%Y-%m-%d %H:%M:%S %z")) # => 2024-02-29 12:30:45 +0900
puts(Time.strftime(t, "%F %T %:z")) # => 2024-02-29 12:30:45 +09:00
puts(Time.strftime(t, "%A %-d %B, day %j")) # => Thursday 29 February, day 060
puts(Time.strftime(t, "%s|%L|%Z|%%")) # => 1709177445|000||%
puts(Time.strftime(Time.utc(t), "%Z %z")) # => UTC +0000
30.9 iso8601
Time.iso8601(x, [Integer])
ISO 8601 の形 2024-02-29T12:30:45+09:00 の String(Ruby の t.iso8601、xmlschema)。UTC モードの Time は末尾が Z。引数 n を与えると小数部を n 桁書きます(既定 0)。読み取り(Ruby のクラスメソッド Time.iso8601(s))ではありません。
t = Time.new(2024, 2, 29, 12, 30, 45.5, in: "+09:00")
puts(Time.iso8601(t)) # => 2024-02-29T12:30:45+09:00
puts(Time.iso8601(t, 3)) # => 2024-02-29T12:30:45.500+09:00
puts(Time.iso8601(Time.utc(t))) # => 2024-02-29T03:30:45Z
30.10 asctime, ctime
Time.asctime(x)
Time.ctime(x)
C の asctime の形 "Thu Feb 29 12:30:45 2024"(Ruby の t.asctime、t.ctime。同じもの)。ゾーンは書きません。
t = Time.new(2024, 2, 29, 12, 30, 45, in: "+09:00")
puts(Time.asctime(t)) # => Thu Feb 29 12:30:45 2024
p(Time.ctime(t) == Time.asctime(t)) # => true
30.11 utc, getutc, gmtime, getgm, gm
Time.utc(Time|Integer, [Integer], [Integer], [Integer], [Integer], [Integer|Float|Rational])
Time.getutc(x)
Time.gmtime(x)
Time.getgm(x)
Time.gm(Integer, [Integer], [Integer], [Integer], [Integer], [Integer|Float|Rational])
Time を渡すと、同じ時刻を UTC モードで表す新しい Time(Ruby の t.getutc)。4 つは同じもので、Ruby の t.utc・t.gmtime のように主語を書き換えることはありません。結果は utc? が true、zone が "UTC"、元の Time と == です。年(と、new と同じくその後の部分)を渡すと、Time.utc と Time.gm はその時刻を UTC で作ります(Ruby のクラスメソッド Time.utc(y, m, d, h, min, s) と Time.gm)。Time にさらに引数を付けると ArgumentError です。
t = Time.new(2024, 2, 29, 12, 30, 45, in: "+09:00")
u = Time.utc(t)
puts(Time.to_s(u)) # => 2024-02-29 03:30:45 UTC
puts(Time.to_s(t)) # => 2024-02-29 12:30:45 +0900
p(Time.utc?(u)) # => true
puts(Time.utc(2024, 2, 29, 3, 30, 45)) # => 2024-02-29 03:30:45 UTC
puts(Time.gm(2024)) # => 2024-01-01 00:00:00 UTC
p(u == t) # => true
p(Time.getutc(t) == Time.gmtime(t) && Time.getgm(t) == u) # => true
30.12 localtime, getlocal
Time.localtime(x, [String|Integer])
Time.getlocal(x, [String|Integer])
同じ時刻を別のオフセットで表す新しい Time(Ruby の t.getlocal(zone))。ゾーンは "+09:00"、"UTC"、秒数の Integer など(上記)。省くと実行環境のローカルゾーンです。2 つは同じもので、Ruby の t.localtime のように主語を書き換えることはありません。"UTC" を渡すと UTC モードになります。
t = Time.at(1700000000, in: "+09:00")
puts(Time.to_s(Time.getlocal(t, "-05:00"))) # => 2023-11-14 17:13:20 -0500
puts(Time.to_s(Time.localtime(t, 3600))) # => 2023-11-14 23:13:20 +0100
p(Time.utc?(Time.getlocal(t, "UTC"))) # => true
p(Time.getlocal(t) == t) # => true
puts(Time.to_s(t)) # => 2023-11-15 07:13:20 +0900
30.13 utc?, gmt?
Time.utc?(x)
Time.gmt?(x)
UTC モードの Time なら true("UTC"・"Z" で作ったもの、Time.utc の結果)。オフセット 0 でも "+00:00" で作った Time は false です。gmt? は別名。
p(Time.utc?(Time.at(0, in: "UTC"))) # => true
p(Time.utc?(Time.at(0, in: "+00:00"))) # => false
p(Time.gmt?(Time.utc(Time.at(0, in: "+09:00")))) # => true
30.14 zone
Time.zone(x)
ゾーンの名前(String)か nil。UTC モードなら "UTC"、ローカルゾーンの Time ならその略称("JST" など、環境による)、"+09:00" のような固定オフセットで作った Time には名前が無いので nil です。戻り値は String | nil で、検査なしに String の操作に渡すと --strict(レベル 2)が止めます。オフセットが要るなら utc_offset を使います。
p(Time.zone(Time.at(0, in: "UTC"))) # => "UTC"
p(Time.zone(Time.at(0, in: "+09:00"))) # => nil
30.15 utc_offset, gmt_offset, gmtoff
Time.utc_offset(x)
Time.gmt_offset(x)
Time.gmtoff(x)
UTC からのオフセット(秒数の Integer。+09:00 なら 32400、UTC なら 0)。3 つは別名です。
t = Time.at(0, in: "+09:00")
p(Time.utc_offset(t)) # => 32400
p(Time.gmt_offset(Time.at(0, in: "-05:00"))) # => -18000
p(Time.gmtoff(Time.utc(t))) # => 0
30.16 dst?, isdst
Time.dst?(x)
Time.isdst(x)
夏時間の中なら true(Ruby の t.dst?)。固定オフセットと UTC の Time では常に false で、true になりうるのはローカルゾーンの Time だけです。isdst は別名。
p(Time.dst?(Time.at(0, in: "UTC"))) # => false
p(Time.isdst(Time.at(0, in: "+09:00"))) # => false
30.17 round, floor, ceil
Time.round(x, [Integer])
Time.floor(x, [Integer])
Time.ceil(x, [Integer])
秒の小数部を n 桁(既定 0)に丸めた新しい Time(Ruby の t.round(n))。round は四捨五入、floor は切り捨て、ceil は切り上げ。負の n は ArgumentError。
t = Time.at(1700000000.123456789r, in: "UTC")
puts(Time.iso8601(Time.round(t, 2), 9)) # => 2023-11-14T22:13:20.120000000Z
puts(Time.iso8601(Time.floor(t, 1), 9)) # => 2023-11-14T22:13:20.100000000Z
puts(Time.iso8601(Time.ceil(t), 9)) # => 2023-11-14T22:13:21.000000000Z
p(Time.round(Time.at(1.5, in: "UTC")) == Time.at(2, in: "UTC")) # => true
30.18 +, -
Time.+(x, Any)
Time.-(x, Any)
t + 秒 と t - 秒 は秒数(Integer、Float、Rational)だけ後・前の新しい Time で、ゾーンは t のものです。t1 - t2 は 2 つの Time の差の秒数を Float で返します。Time + Time と、秒数でない右辺は静的に type の問題です。日や月の単位は無いので、1 日は 86400 と書きます。
a = Time.at(100, in: "UTC")
p(a + 5) # => 1970-01-01 00:01:45 UTC
p(a - 1.5) # => 1970-01-01 00:01:38.5 UTC
p((a + 5) - a) # => 5.0
p(a - (a + 5)) # => -5.0
p(Time.-(a, 1r/4) == Time.at(99.75, in: "UTC")) # => true
d = Time.new(2024, 3, 10, in: "UTC") - Time.new(2024, 1, 1, in: "UTC")
p(Integer(d / 86400)) # => 69
```ruby error a = Time.at(0, in: “UTC”) p(a + a) # !> the operands are (Time, Time), which the left operand’s type does not support
## ==, !=
`Time.==(x, Any)`
`Time.!=(x, Any)`
同じ時刻なら true(`!=` はその否定)。ゾーンは比べないので、`Time.at(100, in: "UTC")` と `Time.at(100, in: "+09:00")` は等しいです。右が Time でない値(nil、Integer など)なら `==` は false、`!=` は true で、演算子の形 `t == v` も関数の形 `Time.==(t, v)` も同じです。関数の形はどんな値も受け取り、例外にはなりません。
```ruby
a = Time.at(100, in: "UTC")
p(a == Time.at(100, in: "+09:00")) # => true
p(a != a + 1) # => true
p(a == nil) # => false
p(Time.==(a, Time.at(100.0, in: "UTC"))) # => true
p(Time.==(a, 100)) # => false
p(Time.!=(a, 100)) # => true
30.19 <, <=, >, >=
Time.<(x, Any)
Time.<=(x, Any)
Time.>(x, Any)
Time.>=(x, Any)
時刻の前後で比べます(早い方が小さい)。右は Time でなければならず、Integer などは関数の形 Time.<(t, 100) でも静的に type の問題です(false を返すだけの == とは違います)。
a = Time.at(100, in: "UTC")
b = a + 5
p(a < b) # => true
p(a <= a) # => true
p(b > a && b >= a) # => true
p(Time.<(b, a)) # => false
```ruby error a = Time.at(100, in: “UTC”) p(a < 100) # !> the operands are (Time, Integer), which the left operand’s type does not support
## <=>
`Time.<=>(x, Any)`
前後の比較の結果を -1、0、1 で返します(Integer。Time どうしは常に比べられるので nil にはなりません)。`Array.sort` と `Array.sort_by` のキーに Time を使うと時刻順に並びます。
```ruby
a = Time.at(100, in: "UTC")
p(a <=> a + 1) # => -1
p(a <=> a) # => 0
p(Time.<=>(a + 1, a)) # => 1
xs = Array.sort(Any[a + 9, a, a + 3])
p(Array.map(xs) { |t| Time.to_i(t) }) # => [100, 103, 109]
31. IO
IO は開いたファイルとプログラムの標準ストリームの型です。値は操作から来ます: IO.stdin、IO.stdout、IO.stderr が標準ストリームを、File.open(path, mode) がファイルを与えます(Sake に $stdout や STDOUT はありません。値と型)。ファイルとストリームの型は 1 つなので、IO を受け取る関数はどちらにも書けます。IO のリテラルはありません。
Ruby の io.puts(x) は IO.puts(io, x) と書きます。Ruby の IO のメソッドのうち、ここに挙げたものだけがあります。読み書きの失敗(閉じた IO、その向きに開いていない IO、OS のエラー)は IOError です(Ruby の Errno::ENOENT などもまとめて IOError。例外)。
IO に使える演算子は ==、!= だけです(同じストリームか同じ File.open の結果のとき等しい)。引数の無い操作 IO.stdout() は IO.stdout とも書けます。
31.1 stdin, stdout, stderr
IO.stdin()
IO.stdout()
IO.stderr()
プログラムの標準入力・標準出力・標準エラー出力を IO として返します。何度呼んでも同じ値で、IO.stdout == IO.stdout は true です。Kernel.puts と Kernel.p は IO.stdout に書き、Kernel.gets は IO.stdin から読みます。IO.stderr への書き込みは先に stdout を flush するので、2 つのストリームの行の順序が保たれます。標準ストリームを IO.close で閉じることはできますが、役に立つ場面はほとんどありません: 閉じた後はそこへの書き込み(p、puts、IO.write)がすべて IOError(closed stream)になります。rescue でき、IO.stderr はそのまま使えます。
out = IO.stdout
IO.puts(out, "hello") # => hello
p(IO.stdout == out) # => true
p(IO.stdout == IO.stderr) # => false
p(IO.stdin) # => #<IO:<STDIN>>
```ruby error IO.close(IO.stdout) puts(“x”) # !> IOError: Kernel.puts: closed stream
## puts
`IO.puts(x, *Any)`
各引数を `Kernel.puts` と同じ形で `x` に書き、nil を返します。引数ごとに 1 行(末尾に改行が無ければ加える)。Array と Tuple は平らにして要素ごとに 1 行、空の Array は空行、引数が無ければ空行 1 つ。文字列以外は `Kernel.to_s` の形です(nil は空行、Symbol は名前)。閉じた IO や読み出し専用の IO には `IOError`。
```ruby
out = IO.stdout
IO.puts(out, "a", 1) # => a
# => 1
IO.puts(out, [1, Any[2, 3]]) # => 1
# => 2
# => 3
IO.puts(out, "x\n") # => x
p(IO.puts(out, :s)) # => s
# => nil
31.2 print
IO.print(x, *Any)
各引数の to_s を改行を挟まずに x に書き、nil を返します(Ruby の io.print)。Array は [1, 2] の形、nil は空文字列です。
out = IO.stdout
IO.print(out, "a", 1, :s, "\n") # => a1s
IO.print(out, Any[1, 2], "\n") # => [1, 2]
31.3 write
IO.write(x, String)
文字列をそのまま x に書き、書いたバイト数(Integer)を返します。改行は加えません。引数は String でなければならず、他の型は静的に type の問題です(puts や print と違い、変換しません)。
p(IO.write(IO.stdout, "ab\n")) # => ab
# => 3
p(IO.write(IO.stdout, "日本\n")) # => 日本
# => 7
```ruby error IO.write(IO.stdout, 1) # !> IO.write: argument 2 must be String, but is Integer
## flush
`IO.flush(x)`
バッファに溜まった出力を書き出し、`x` を返します。`File.open` で開いたファイルへの `IO.write` や `IO.puts` はバッファされ、`flush` か `close` でファイルに届きます。
```ruby
f = File.open("log.txt", "w")
IO.puts(f, "a")
p(File.read("log.txt")) # => ""
IO.flush(f)
p(File.read("log.txt")) # => "a\n"
IO.close(f)
31.4 gets
IO.gets(x)
次の 1 行を改行を含めて返し(Ruby の io.gets)、終端では nil です。結果は String | nil なので、--strict(レベル 2)はそのまま使うことを nil の問題として報告します: if line、while (line = IO.gets(io)) のように確かめてから使います。書き込み専用の IO では IOError。
io = IO.stdin
line = IO.gets(io)
if line
p(String.to_i(line)) # => 3
end
while (l = IO.gets(io))
p(l) # => "1 2\n"
end
p(IO.gets(io)) # => nil
```ruby error line = IO.gets(IO.stdin) p(String.upcase(line)) # !> argument 1 may be nil
## read
`IO.read(x, [Integer])`
`IO.read(io)` は現在位置から終端までをすべて読んで String を返します(終端ではすでに空文字列 `""`。nil にはなりません)。`IO.read(io, n)` は最大 `n` バイト(文字ではなくバイト)を読み、終端に達していれば nil を返します(`n` が 0 のときは常に `""`)。この 2 引数の形だけが `String | nil` で、レベル 2 で検査されます。読み出しに開いていない IO は `IOError`。
```ruby
io = IO.stdin
p(IO.read(io, 2)) # => "3\n"
p(IO.read(io)) # => "1 2\n"
p(IO.read(io)) # => ""
p(IO.read(io, 1)) # => nil
31.5 getc
IO.getc(x)
次の 1 文字を String で返し、終端では nil です(String | nil、レベル 2)。
io = IO.stdin
p(IO.getc(io)) # => "3"
p(IO.getc(io)) # => "\n"
IO.read(io)
p(IO.getc(io)) # => nil
31.6 readlines
IO.readlines(x)
現在位置から終端までを行に分けて、String の Array で返します(各行は改行を含みます)。終端では空の Array です。
p(IO.readlines(IO.stdin)) # => ["3\n", "1 2\n"]
p(IO.readlines(IO.stdin)) # => []
31.7 each_line
IO.each_line(x) { }
現在位置から終端まで、1 行ずつ(改行を含む String)ブロックに渡し、x を返します。ブロックの値は使いません。Ruby の io.each_line と同じで、ブロックは必須です。
r = IO.each_line(IO.stdin) { |l| p(String.chomp(l)) } # => "3"
# => "1 2"
p(r == IO.stdin) # => true
31.8 eof?
IO.eof?(x)
読み出し位置が終端にあれば true。書き込み専用の IO や閉じた IO では IOError。
io = IO.stdin
p(IO.eof?(io)) # => false
IO.read(io)
p(IO.eof?(io)) # => true
31.9 seek
IO.seek(x, Integer, [Integer|Symbol])
読み書きの位置を動かし、0 を返します(Ruby の io.seek)。第 2 引数はオフセット、第 3 引数は基準で、省略すると先頭(0)。0/:SET は先頭から、1/:CUR は現在位置から、2/:END は末尾から(オフセットは負)。Symbol はこの 3 つだけで、他の Symbol は ArgumentError です。seek できないストリーム(パイプの IO.stdin など)は IOError。
File.write("a.txt", "hello\n")
f = File.open("a.txt")
IO.seek(f, 2)
p(IO.read(f, 2)) # => "ll"
IO.seek(f, -2, :END)
p(IO.read(f)) # => "o\n"
p(IO.seek(f, 1, :SET)) # => 0
p(IO.getc(f)) # => "e"
IO.close(f)
```ruby error File.write(“a.txt”, “hello\n”) f = File.open(“a.txt”) IO.seek(f, 1, :FOO) # !> ArgumentError: IO.seek: unknown whence: :FOO (0/1/2 or :SET/:CUR/:END)
## pos
`IO.pos(x)`
現在の位置(先頭からのバイト数、Integer)。seek できないストリームでは `IOError`。
```ruby
File.write("a.txt", "日本\n")
f = File.open("a.txt")
p(IO.pos(f)) # => 0
IO.getc(f)
p(IO.pos(f)) # => 3
IO.close(f)
31.10 rewind
IO.rewind(x)
位置を先頭に戻し、0 を返します。もう一度読み直すときに使います。
File.write("a.txt", "one\n")
f = File.open("a.txt")
p(IO.gets(f)) # => "one\n"
p(IO.rewind(f)) # => 0
p(IO.gets(f)) # => "one\n"
IO.close(f)
31.11 size
IO.size(x)
開いたファイルの大きさ(バイト数、Integer)。まだ flush していない書き込みも含みます。ファイルでない IO(IO.stdin などのストリーム)は IOError(not a file)です。
f = File.open("a.txt", "w")
IO.write(f, "abc")
p(IO.size(f)) # => 3
IO.close(f)
```ruby error IO.size(IO.stdin) # !> IOError: IO.size: not a file
## truncate
`IO.truncate(x, Integer)`
ファイルを `n` バイトに切り詰め、0 を返します。書き込みに開いていないファイルは `IOError`。
```ruby
File.write("a.txt", "hello")
r = File.open("a.txt", "r+") { |f| IO.truncate(f, 3) }
p(r) # => 0
p(File.read("a.txt")) # => "hel"
```ruby error File.write(“a.txt”, “hello”) f = File.open(“a.txt”) IO.truncate(f, 1) # !> IOError: IO.truncate: not opened for writing
## close
`IO.close(x)`
IO を閉じて nil を返します。バッファの出力は書き出されます。閉じた IO をもう一度閉じても何も起きません(Ruby と同じ)。閉じた IO への読み書きは `IOError`。`File.open` のブロック形はブロックの後に自動で閉じるので、`close` は手で開いたときだけ必要です。
```ruby
f = File.open("a.txt", "w")
IO.write(f, "x")
p(IO.close(f)) # => nil
p(IO.close(f)) # => nil
p(File.read("a.txt")) # => "x"
```ruby error f = File.open(“a.txt”, “w”) IO.close(f) IO.write(f, “x”) # !> IOError: IO.write: closed stream
## closed?
`IO.closed?(x)`
閉じていれば true。`File.open` のブロックに渡された IO は、ブロックの後では閉じています。
```ruby
kept = IO.stdout
File.open("a.txt", "w") { |f| kept = f }
p(IO.closed?(kept)) # => true
p(IO.closed?(IO.stdout)) # => false
31.12 tty?
IO.tty?(x)
端末に繋がっていれば true(Ruby の io.tty?)。ファイルやパイプでは false。検査器の中では標準入出力もパイプなので false です。以下の端末の操作(winsize、raw、noecho、getch)はこれが true のときだけ使えます。
f = File.open("a.txt", "w")
p(IO.tty?(f)) # => false
IO.close(f)
31.13 winsize
IO.winsize(x)
x が繋がっている端末の大きさを Tuple [rows, columns](Integer, Integer)で返します(Ruby の io/console の io.winsize)。rows, cols = IO.winsize(IO.stdout) と多重代入で受けます。端末でない IO(ファイル、パイプ、検査器の中の標準ストリーム)は IOError(not a terminal)。端末では:
rows, cols = IO.winsize(IO.stdout)
p([rows, cols]) # 80x24 の端末なら [24, 80]
検査器の中では stdin がパイプなので、示せるのはエラーだけです:
begin
IO.winsize(IO.stdin)
rescue IOError => e
p(Exception.message(e)) # => "not a terminal"
end
31.14 raw, noecho
IO.raw(x) { }
IO.noecho(x) { }
x の端末を raw モード(raw: キーが行の終わりを待たずに 1 つずつ届き、エコーされない)またはエコー無し(noecho: 行単位の読み出しのまま、打った文字が表示されない)にしてブロックを走らせ、終わると(例外で抜けても)端末を元に戻し、ブロックの値を返します(Ruby の io.raw { }、io.noecho { })。結果の型はブロックの型です。ブロックは必須で、引数は受け取りません。端末でない IO は IOError(not a terminal)。端末では、パスワードを表示せずに読む・Enter を待たずに 1 キー読む、と使います:
pw = IO.noecho(IO.stdin) { IO.gets(IO.stdin) } # secret と Enter を打つと "secret\n"
k = IO.raw(IO.stdin) { IO.getch(IO.stdin) } # x を押した瞬間に "x"
begin
IO.noecho(IO.stdin) { IO.gets(IO.stdin) }
rescue IOError => e
p(Exception.message(e)) # => "not a terminal"
end
31.15 getch
IO.getch(x)
x の端末からキーを 1 つ読み、Enter を待たず・エコーせずに String で返します(Ruby の io.getch)。結果は String | nil(入力の終端で nil)なので、--strict(レベル 2)はそのまま使うことを nil の問題として報告します。端末でない IO は IOError(not a terminal)。パイプやファイルから 1 文字読むには IO.getc を使います。端末では:
k = IO.getch(IO.stdin) # y を押した瞬間に "y"
begin
IO.getch(IO.stdin)
rescue IOError => e
p(Exception.message(e)) # => "not a terminal"
end
31.16 ==, !=
IO.==(x, Any)
IO.!=(x, Any)
2 つの IO 値が同じストリーム、または同じ File.open の結果なら等しい(!= はその否定)。同じパスを 2 回開いた IO は別の値です。IO 以外の値とは等しくありません。
f = File.open("a.txt", "w")
g = File.open("a.txt", "w")
p(f == f) # => true
p(f == g) # => false
p(IO.stdout != IO.stdin) # => true
IO.close(f)
IO.close(g)
32. File
File はファイルをパス(String)で扱う操作の集まりです。File という型の値はありません: File.open が返すのは IO(IO)で、他の操作はパスを受け取ってその場で読み書き・問い合わせをします。Ruby の File.read(path) などのクラスメソッドと同じ名前で、インスタンスメソッド(file.read)に当たるものは IO の操作です(値と型、組み込み)。
システムコールの失敗(無いパス、権限、ディレクトリを読む、など。Ruby では Errno::ENOENT などの例外)はすべて IOError で、メッセージは Ruby のものです(例外)。パスの文字列処理(basename、join、expand_path など)はファイルを見ません。exist? などの問い合わせは失敗せず false を返します。
File に演算子はありません。例は空のディレクトリで実行され、そこにファイルを作ります。
32.1 open
File.open(String, [String], [Integer]) [{ }]
ファイルを開いて IO を返します。第 2 引数はモードで省略すると "r"(Ruby と同じ: "r" 読む、"w" 書く(空にする)、"a" 追記、"r+" 読み書き、"w+"、"a+"、"b" を付けるとバイナリ)。第 3 引数は新しく作るファイルの許可ビット(0o600 など。既にあるファイルには影響しません)。ブロックを付けると IO をブロックに渡し、ブロックの後にファイルを閉じてブロックの値を返します(Ruby と同じ)。ブロック無しなら IO.close で自分で閉じます。無いファイルや権限の無いファイルは IOError、知らないモードは ArgumentError。
f = File.open("a.txt", "w")
IO.puts(f, "one")
IO.close(f)
n = File.open("a.txt", "a") { |io| IO.write(io, "two\n") }
p(n) # => 4
p(File.open("a.txt") { |io| IO.readlines(io) }) # => ["one\n", "two\n"]
File.open("p.txt", "w", 0o600) { |io| nil }
p(File.exist?("p.txt")) # => true
```ruby error File.open(“nope.txt”) # !> IOError: File.open: No such file or directory
## read
`File.read(String)`
ファイル全体を String で返します。無いファイルやディレクトリは `IOError`。
```ruby
File.write("a.txt", "hello\n")
p(File.read("a.txt")) # => "hello\n"
```ruby error File.read(“nope.txt”) # !> IOError: File.read: No such file or directory
## readlines
`File.readlines(String)`
ファイル全体を行に分けた String の Array(各行は改行を含みます)。空のファイルは空の Array。
```ruby
File.write("a.txt", "a\nb\n")
p(File.readlines("a.txt")) # => ["a\n", "b\n"]
32.2 write
File.write(String, String)
ファイルを作る(あれば中身を置き換える)か書き、書いたバイト数(Integer)を返します。追記はしません(追記は File.open(path, "a"))。第 2 引数は String だけで、他の型は静的に type の問題です。
p(File.write("a.txt", "日本")) # => 6
p(File.write("a.txt", "x")) # => 1
p(File.read("a.txt")) # => "x"
```ruby error File.write(“a.txt”, 1) # !> File.write: argument 2 must be String, but is Integer
## exist?
`File.exist?(String)`
パスにファイルかディレクトリがあれば true。失敗しません。
```ruby
File.write("a.txt", "")
p(File.exist?("a.txt")) # => true
p(File.exist?(".")) # => true
p(File.exist?("nope")) # => false
32.3 file?, directory?, symlink?
File.file?(String)
File.directory?(String)
File.symlink?(String)
通常のファイル・ディレクトリ・シンボリックリンクなら true。file? と directory? はリンクをたどって先を見ます。無いパスは false。
File.write("a.txt", "")
File.symlink("a.txt", "l1.txt")
p(File.file?("a.txt")) # => true
p(File.file?(".")) # => false
p(File.directory?(".")) # => true
p(File.symlink?("l1.txt")) # => true
p(File.symlink?("a.txt")) # => false
p(File.file?("nope")) # => false
32.4 zero?, empty?
File.zero?(String)
File.empty?(String)
ファイルがあって大きさが 0 なら true(2 つは同じ。Ruby と同じ)。無いパスは false。
File.write("e.txt", "")
File.write("a.txt", "x")
p(File.zero?("e.txt")) # => true
p(File.empty?("a.txt")) # => false
p(File.zero?("nope")) # => false
32.5 readable?, writable?, executable?
File.readable?(String)
File.writable?(String)
File.executable?(String)
このプロセスがそのパスを読める・書ける・実行できるなら true。無いパスは false。
File.write("a.txt", "")
p(File.readable?("a.txt")) # => true
p(File.writable?("a.txt")) # => true
p(File.executable?("a.txt")) # => false
File.chmod(0o755, "a.txt")
p(File.executable?("a.txt")) # => true
32.6 size
File.size(String)
ファイルの大きさ(バイト数、Integer)。無いパスは IOError。
File.write("a.txt", "日本")
p(File.size("a.txt")) # => 6
```ruby error File.size(“nope”) # !> IOError: File.size: No such file or directory
## mtime, atime
`File.mtime(String)`
`File.atime(String)`
最後に変更された・読まれた時刻を Time で返します。無いパスは `IOError`。
```ruby
File.write("a.txt", "")
File.utime(Time.at(0), Time.at(60), "a.txt")
p(Time.to_i(File.atime("a.txt"))) # => 0
p(Time.to_i(File.mtime("a.txt"))) # => 60
p(File.mtime("a.txt") <= Time.now) # => true
32.7 ftype
File.ftype(String)
種類を String で返します: "file"、"directory"、"link"(リンクそのもの。たどりません)、"characterSpecial" など Ruby と同じ。無いパスは IOError。
File.write("a.txt", "")
File.symlink("a.txt", "l2.txt")
p(File.ftype("a.txt")) # => "file"
p(File.ftype(".")) # => "directory"
p(File.ftype("l2.txt")) # => "link"
32.8 identical?
File.identical?(String, String)
2 つのパスが同じファイルを指すなら true(リンクをたどります。ハードリンクも同じ)。どちらかが無ければ false。
File.write("a.txt", "")
File.write("b.txt", "")
File.symlink("a.txt", "l3.txt")
p(File.identical?("a.txt", "l3.txt")) # => true
p(File.identical?("a.txt", "b.txt")) # => false
p(File.identical?("a.txt", "nope")) # => false
32.9 rename
File.rename(String, String)
ファイルを移す(名前を変える)。0 を返します。移す先にファイルがあれば置き換えます。元が無ければ IOError。
File.write("a.txt", "x")
p(File.rename("a.txt", "b.txt")) # => 0
p(File.exist?("a.txt")) # => false
p(File.read("b.txt")) # => "x"
```ruby error File.rename(“nope”, “b.txt”) # !> IOError: File.rename: No such file or directory
## delete, unlink
`File.delete(String)`
`File.unlink(String, *String)`
ファイルを消します。`delete` は 1 つのパスを取り 1 を返します。`unlink` は 1 つ以上のパスを取り、消した数(Integer)を返します。ディレクトリは消せません(`Dir.rmdir`)。無いパスは `IOError`(Ruby の `Errno::ENOENT`)。
```ruby
File.write("a.txt", "")
File.write("b.txt", "")
File.write("c.txt", "")
p(File.delete("a.txt")) # => 1
p(File.unlink("b.txt", "c.txt")) # => 2
p(File.exist?("c.txt")) # => false
```ruby error File.delete(“nope”) # !> IOError: File.delete: No such file or directory
## symlink, link
`File.symlink(String, String)`
`File.link(String, String)`
`symlink(target, link)` はシンボリックリンク、`link(target, link)` はハードリンクを作り、0 を返します。`link` 側が既にあれば `IOError`。
```ruby
File.write("a.txt", "x")
p(File.symlink("a.txt", "s.txt")) # => 0
p(File.link("a.txt", "h.txt")) # => 0
p(File.read("s.txt")) # => "x"
p(File.symlink?("h.txt")) # => false
p(File.identical?("a.txt", "h.txt")) # => true
```ruby error File.write(“a.txt”, “”) File.symlink(“a.txt”, “a.txt”) # !> IOError: File.symlink: File exists
## readlink
`File.readlink(String)`
シンボリックリンクが指すパスを String で返します(書かれたままで、解決はしません)。リンクでないパスは `IOError`。
```ruby
File.write("a.txt", "")
File.symlink("a.txt", "l4.txt")
p(File.readlink("l4.txt")) # => "a.txt"
```ruby error File.write(“a.txt”, “”) File.readlink(“a.txt”) # !> IOError: File.readlink: Invalid argument
## chmod
`File.chmod(Integer, *String)`
許可ビットを `mode`(`0o644` など)に変え、変えたファイルの数を返します。パスが無ければ 0。無いパスは `IOError`。
```ruby
File.write("a.txt", "")
File.write("b.sh", "")
p(File.chmod(0o755, "a.txt", "b.sh")) # => 2
p(File.executable?("b.sh")) # => true
p(File.chmod(0o644)) # => 0
32.10 utime
File.utime(Time|Nil, Time|Nil, *String)
utime(atime, mtime, *paths): 読まれた時刻と変更された時刻を設定し、変えたファイルの数を返します。nil は現在時刻です。無いパスは IOError。
File.write("a.txt", "")
p(File.utime(Time.at(10), Time.at(20), "a.txt")) # => 1
p(Time.to_i(File.atime("a.txt"))) # => 10
p(Time.to_i(File.mtime("a.txt"))) # => 20
File.utime(nil, nil, "a.txt")
p(File.mtime("a.txt") > Time.at(20)) # => true
32.11 basename
File.basename(String, [String])
パスの最後の要素。第 2 引数に拡張子(".rb")を与えるとそれを取り、".*" ならどんな拡張子でも取ります。ファイルは見ません。
p(File.basename("/a/b/c.rb")) # => "c.rb"
p(File.basename("/a/b/c.rb", ".rb")) # => "c"
p(File.basename("c.tar.gz", ".*")) # => "c.tar"
p(File.basename("/a/b/")) # => "b"
32.12 dirname
File.dirname(String, [Integer])
最後の要素を取ったディレクトリ部分。第 2 引数 n で n 段上がります(省略時 1、0 なら元のまま)。ディレクトリ部分が無ければ "."。
p(File.dirname("/a/b/c.rb")) # => "/a/b"
p(File.dirname("/a/b/c.rb", 2)) # => "/a"
p(File.dirname("c.rb")) # => "."
32.13 extname
File.extname(String)
最後の要素の拡張子(ドットを含む)。無ければ ""。ドットで始まる名前(.bashrc)は拡張子無しです。
p(File.extname("c.tar.gz")) # => ".gz"
p(File.extname("c")) # => ""
p(File.extname(".bashrc")) # => ""
32.14 split
File.split(String)
[dirname, basename] の 2 要素の Tuple(String, String)。Ruby では Array ですが Sake では Tuple で、dir, base = File.split(p) で分解します。
dir, base = File.split("/a/b/c.rb")
p(dir) # => "/a/b"
p(base) # => "c.rb"
p(File.split("c.rb")) # => [".", "c.rb"]
32.15 join
File.join(*String|Array)
部分を / で繋いだパス。重なる / は 1 つにします。引数は String か String の Array(入れ子も平らにします)で、引数が無ければ ""。他の型は静的に type の問題です。
p(File.join("a", "b", "c.rb")) # => "a/b/c.rb"
p(File.join("a/", "/b")) # => "a/b"
p(File.join(Any["a", "b"], "c")) # => "a/b/c"
p(File.join()) # => ""
```ruby error File.join(1) # !> File.join: argument 1 must be String|Array, but is Integer
## expand_path, absolute_path
`File.expand_path(String, [String])`
`File.absolute_path(String, [String])`
相対パスを絶対パスにします。第 2 引数は基準のディレクトリで、省略すると現在のディレクトリ(`Dir.pwd`)。`.` と `..` は解決しますが、ファイルは見ずリンクもたどりません。違いは `~`: `expand_path` は `~` をホームディレクトリに展開し(知らないユーザーの `~user` は `ArgumentError`)、`absolute_path` は展開しません(Ruby と同じ)。
```ruby
p(File.expand_path("/x/../y")) # => "/y"
p(File.expand_path("b", "/a")) # => "/a/b"
p(File.absolute_path("b", "/a")) # => "/a/b"
p(File.expand_path("a") == File.join(Dir.pwd, "a")) # => true
p(File.expand_path("~") == Dir.home) # => true
p(File.absolute_path("~") == File.join(Dir.pwd, "~")) # => true
32.16 absolute_path?
File.absolute_path?(String)
パスが絶対(/ で始まる)なら true。ファイルは見ません。
p(File.absolute_path?("/a/b")) # => true
p(File.absolute_path?("a/b")) # => false
32.17 realpath
File.realpath(String, [String])
シンボリックリンクをすべてたどった実際の絶対パス。第 2 引数は相対パスの基準ディレクトリです。パスは実在しなければならず、無ければ IOError(expand_path との違い)。
File.write("a.txt", "")
File.symlink("a.txt", "l5.txt")
p(File.realpath("l5.txt") == File.join(Dir.pwd, "a.txt")) # => true
p(File.realpath("l5.txt", Dir.pwd) == File.realpath("./a.txt")) # => true
ruby error
File.realpath("nope") # !> IOError: File.realpath: No such file or directory
33. Dir
Dir はディレクトリをパス(String)で扱う操作の集まりです。Dir という型の値はなく(Ruby の Dir.open や Dir オブジェクトはありません)、すべて Ruby の Dir のクラスメソッドと同じ名前・同じ結果です(組み込み)。中身の一覧は String の Array で返ります。
システムコールの失敗(無いディレクトリ、空でないディレクトリの削除、など。Ruby の Errno::ENOENT 等)はすべて IOError で、メッセージは Ruby のものです(例外)。exist? は失敗せず false、glob は何にも合わなければ空の Array です。
Dir に演算子はありません。例は空のディレクトリで実行され、そこにディレクトリを作ります。
33.1 children, entries
Dir.children(String)
Dir.entries(String)
ディレクトリの中の名前(パスではなく名前だけ)の Array。children は . と .. を含まず、entries は含みます。順序は OS が返す順で決まっていないので、並べて使うなら Array.sort。無いディレクトリは IOError。
Dir.mkdir("d1")
File.write("d1/a.txt", "")
File.write("d1/b.rb", "")
p(Array.sort(Dir.children("d1"))) # => ["a.txt", "b.rb"]
p(Array.sort(Dir.entries("d1"))) # => [".", "..", "a.txt", "b.rb"]
```ruby error Dir.children(“nope”) # !> IOError: Dir.children: No such file or directory
## each_child
`Dir.each_child(String) { }`
`children` の各名前を順にブロックに渡し、nil を返します(Ruby は Dir を返しますが、Sake では nil)。ブロックの値は使いません。順序は `children` と同じく決まっていません。無いディレクトリは `IOError`。
```ruby
Dir.mkdir("d2")
File.write("d2/x", "")
File.write("d2/y", "")
names = Any[]
r = Dir.each_child("d2") { |n| Array.push(names, n) }
p(Array.sort(names)) # => ["x", "y"]
p(r) # => nil
33.2 glob
Dir.glob(String|Array, [base: String])
パターンに合うパスの Array(Ruby の Dir.glob。*、?、[...]、{a,b}、**/ が使えます)。パターンは String か String の Array(どれかに合えばよい)。結果は Ruby と同じく並べ替え済みで、ドットで始まる名前は * に合いません。キーワード base: を与えると、そのディレクトリから探して相対の名前を返します。合うものが無いときや base: のディレクトリが無いときは空の Array で、失敗しません。
Dir.mkdir("d3")
File.write("d3/a.txt", "")
File.write("d3/b.rb", "")
Dir.mkdir("d3/sub")
File.write("d3/sub/c.txt", "")
p(Dir.glob("d3/*.rb")) # => ["d3/b.rb"]
p(Dir.glob("d3/**/*.txt")) # => ["d3/a.txt", "d3/sub/c.txt"]
p(Dir.glob(Any["d3/*.txt", "d3/*.rb"])) # => ["d3/a.txt", "d3/b.rb"]
p(Dir.glob("*", base: "d3")) # => ["a.txt", "b.rb", "sub"]
p(Dir.glob("nope/*")) # => []
33.3 exist?
Dir.exist?(String)
パスにディレクトリがあれば true。ファイルや無いパスは false で、失敗しません。
Dir.mkdir("d4")
File.write("f4", "")
p(Dir.exist?("d4")) # => true
p(Dir.exist?("f4")) # => false
p(Dir.exist?("nope")) # => false
33.4 empty?
Dir.empty?(String)
ディレクトリが空(. と .. 以外に何も無い)なら true。無いディレクトリは IOError。
Dir.mkdir("d5")
p(Dir.empty?("d5")) # => true
File.write("d5/a", "")
p(Dir.empty?("d5")) # => false
33.5 mkdir
Dir.mkdir(String, [Integer])
ディレクトリを 1 つ作り、0 を返します。第 2 引数は許可ビット(0o700 など。省略時は 0o777 を umask で削ったもの)。親が無いときや、同じ名前が既にあるときは IOError(Ruby の mkdir -p に当たるものはありません)。
p(Dir.mkdir("d6")) # => 0
p(Dir.mkdir("d6/sub", 0o700)) # => 0
p(Dir.exist?("d6/sub")) # => true
```ruby error Dir.mkdir(“d6b”) Dir.mkdir(“d6b”) # !> IOError: Dir.mkdir: File exists
## rmdir, unlink
`Dir.rmdir(String)`
`Dir.unlink(String)`
空のディレクトリを消し、0 を返します(2 つは同じ。Ruby と同じ)。空でないディレクトリや無いパスは `IOError`。ファイルを消すのは `File.delete`。
```ruby
Dir.mkdir("d7")
p(Dir.rmdir("d7")) # => 0
p(Dir.exist?("d7")) # => false
Dir.mkdir("d7")
p(Dir.unlink("d7")) # => 0
```ruby error Dir.mkdir(“d7b”) File.write(“d7b/a”, “”) Dir.rmdir(“d7b”) # !> IOError: Dir.rmdir: Directory not empty
## pwd
`Dir.pwd()`
現在のディレクトリの絶対パス。Sake に `Dir.chdir` は無いので、プログラムの間変わりません。
```ruby
p(File.absolute_path?(Dir.pwd)) # => true
p(Dir.pwd == File.expand_path(".")) # => true
33.6 home
Dir.home([String])
ホームディレクトリの絶対パス。引数にユーザー名を与えるとそのユーザーのホームです。知らないユーザーは ArgumentError。
p(Dir.home == ENV.fetch("HOME")) # => true
p(File.expand_path("~") == Dir.home) # => true
```ruby error Dir.home(“no_such_user_xyz”) # !> ArgumentError: Dir.home: user no_such_user_xyz doesn’t exist
## tmpdir
`Dir.tmpdir()`
一時ファイル用のディレクトリ(`$TMPDIR` か `/tmp`)の絶対パス。
```ruby
p(Dir.exist?(Dir.tmpdir)) # => true
p(File.absolute_path?(Dir.tmpdir)) # => true
33.7 mktmpdir
Dir.mktmpdir([String], [String]) [{ }]
新しい一時ディレクトリを作ります。第 1 引数は名前の接頭辞(省略時 "d")、第 2 引数は作る場所(省略時 Dir.tmpdir)。ブロック無しならそのパス(String)を返し、消すのは呼ぶ側の仕事です(Dir.rmdir は空のときだけ)。ブロックを付けるとパスをブロックに渡し、ブロックの後にディレクトリを中身ごと消して、ブロックの値を返します(Ruby の tmpdir と同じ)。場所が無ければ IOError。
t = Dir.mktmpdir("pfx", ".")
p(String.start_with?(File.basename(t), "pfx")) # => true
p(Dir.exist?(t)) # => true
Dir.rmdir(t)
kept = ""
v = Dir.mktmpdir { |dir| File.write(File.join(dir, "f"), "1"); kept = dir; 42 }
p(v) # => 42
p(Dir.exist?(kept)) # => false
34. ENV
ENV はプロセスの環境変数を読み書きする操作の集まりです。Ruby の ENV["X"] は ENV.get("X")、ENV["X"] = v は ENV.set("X", v) と書きます(Sake では ENV は値ではなく、添字を付けられません。組み込み)。名前も値も String です。
変更はこのプロセスと、以後に Open3 や Kernel.system で起こす子プロセスに見えます。ENV.to_h は写しで、変えても環境は変わりません。Hash 全体を環境として戻すのが ENV.replace(h) です。
ENV に演算子はありません。例は SAKE_DOC_ で始まる名前を使い、終わりに消します。
34.1 get
ENV.get(String)
変数の値(String)、設定されていなければ nil です(Ruby の ENV["X"])。結果は String | nil なので、--strict(レベル 2)は確かめずに使うことを nil の問題として報告します。必ずある変数には fetch を使います。
ENV.set("SAKE_DOC_A", "1")
p(ENV.get("SAKE_DOC_A")) # => "1"
p(ENV.get("SAKE_DOC_NOPE")) # => nil
v = ENV.get("SAKE_DOC_A")
if v
p(String.to_i(v) + 1) # => 2
end
ENV.delete("SAKE_DOC_A")
```ruby error home = ENV.get(“HOME”) p(String.size(home)) # !> argument 1 may be nil
## fetch
`ENV.fetch(String, [String])`
変数の値(String)。設定されていなければ、第 2 引数があればそれを返し、無ければ `KeyError` です(Ruby の `ENV.fetch`。ブロック形はありません)。結果は nil になりません。
```ruby
ENV.set("SAKE_DOC_B", "x")
p(ENV.fetch("SAKE_DOC_B")) # => "x"
p(ENV.fetch("SAKE_DOC_NOPE", "dflt")) # => "dflt"
ENV.delete("SAKE_DOC_B")
```ruby error ENV.fetch(“SAKE_DOC_NOPE”) # !> KeyError: ENV.fetch: key not found: “SAKE_DOC_NOPE”
## set
`ENV.set(String, String)`
変数を設定し(あれば置き換え)、値を返します(Ruby の `ENV["X"] = v`)。値は String だけで、他の型は静的に `type` の問題です(消すのは `delete`)。空の名前や `=` を含む名前は OS が拒み、OS のメッセージを持つ `ArgumentError`(`Invalid argument - setenv()`)になります。rescue できます。
```ruby
p(ENV.set("SAKE_DOC_C", "1")) # => "1"
p(ENV.set("SAKE_DOC_C", "2")) # => "2"
out, status = Open3.capture2("sh", "-c", "echo $SAKE_DOC_C")
p(out) # => "2\n"
ENV.delete("SAKE_DOC_C")
```ruby error ENV.set(“SAKE_DOC_C”, 1) # !> ENV.set: argument 2 must be String, but is Integer
```ruby error
ENV.set("", "v") # !> ArgumentError: ENV.set: Invalid argument - setenv()
34.2 key?
ENV.key?(String)
変数が設定されていれば true(値が空文字列でも true)。
ENV.set("SAKE_DOC_D", "")
p(ENV.key?("SAKE_DOC_D")) # => true
p(ENV.key?("SAKE_DOC_NOPE")) # => false
ENV.delete("SAKE_DOC_D")
34.3 delete
ENV.delete(String)
変数を消し、消した値(String)を返します。設定されていなければ nil(String | nil、レベル 2)。
ENV.set("SAKE_DOC_E", "1")
p(ENV.delete("SAKE_DOC_E")) # => "1"
p(ENV.delete("SAKE_DOC_E")) # => nil
p(ENV.key?("SAKE_DOC_E")) # => false
34.4 keys
ENV.keys()
設定されている変数の名前すべての Array(String)。順序は決まっていません。
ENV.set("SAKE_DOC_F", "1")
p(Array.include?(ENV.keys, "SAKE_DOC_F")) # => true
p(Array.include?(ENV.keys, "SAKE_DOC_NOPE")) # => false
ENV.delete("SAKE_DOC_F")
34.5 to_h
ENV.to_h()
環境全体の写しを Hash(String → String)で返します。写しなので、Hash を変えても環境は変わらず、以後の ENV.set も Hash に映りません。
ENV.set("SAKE_DOC_G", "1")
h = ENV.to_h
p(h["SAKE_DOC_G"]) # => "1"
h["SAKE_DOC_H"] = "2"
p(ENV.key?("SAKE_DOC_H")) # => false
ENV.delete("SAKE_DOC_G")
p(Hash.key?(h, "SAKE_DOC_G")) # => true
34.6 replace
ENV.replace(Hash)
環境全体を Hash の組で置き換え、nil を返します(Ruby の ENV.replace): Hash に無い変数はすべて消え、各組が設定されます。キーも値も String でなければなりません。検査器はキーや値の型が他のものである Hash を拒みます(the value must be String, but is Integer、argument key must be String, but is :a。関数の引数を通しても同じ)。型が見られない場所(--strict=0 で走らせたプログラム)では、String でないキーや値は実行時に TypeError(keys and values must be String, got String => Integer)で、何かを変える前に投げられるので環境はそのままです。典型的な使い方は ENV.to_h で取った写しを戻すことです。
saved = ENV.to_h
p(ENV.replace(Hash.of("SAKE_DOC_R" => "1"))) # => nil
p(ENV.keys) # => ["SAKE_DOC_R"]
p(ENV.get("SAKE_DOC_R")) # => "1"
ENV.replace(saved)
p(ENV.key?("SAKE_DOC_R")) # => false
p(ENV.to_h == saved) # => true
ruby error
ENV.replace(Hash.of("SAKE_DOC_R" => 1)) # !> ENV.replace: the value must be String, but is Integer [type]
35. Process
Process はこのプロセスの情報と時計を読む操作の集まりです。Ruby の Process.pid と Process.clock_gettime だけがあり、fork、spawn、wait、kill などはありません(子プロセスは Open3 と Kernel.system で起こします)。Ruby の定数 Process::CLOCK_MONOTONIC は Sake では操作 Process.CLOCK_MONOTONIC です(Math.PI と同じく、Sake に値の定数は無いので。組み込み)。
Process に演算子はありません。例は環境に依らない値だけを表示します。
35.1 pid
Process.pid()
このプロセスの ID(正の Integer)。子プロセスから見た親の ID は Open3 で $PPID を読めば得られます。
p(Process.pid > 0) # => true
out, status = Open3.capture2("sh", "-c", "echo $PPID")
p(String.to_i(out) == Process.pid) # => true
35.2 clock_gettime
Process.clock_gettime(Integer, [Symbol])
時計 clock(下の 3 つの操作のどれかの値)の現在値を返します(Ruby の Process.clock_gettime)。経過時間を測るには Process.CLOCK_MONOTONIC を使います: 壁時計の調整で戻ることがありません。第 2 引数は Ruby と同じ単位の Symbol で、結果の型を決めます: 省略時と :float_second(既定)、:float_millisecond、:float_microsecond では Float、:second、:millisecond、:microsecond、:nanosecond では Integer です。検査器は Symbol リテラルで書かれた単位を読みます。変数に入れた単位では結果は Integer | Float になり、Integer や Float の操作に渡す前に case/in で分ける必要があります。知らない単位(unexpected unit: bogus)と知らない時計の番号(Invalid argument - clock_gettime(999))は ArgumentError です。
t0 = Process.clock_gettime(Process.CLOCK_MONOTONIC)
t1 = Process.clock_gettime(Process.CLOCK_MONOTONIC)
p(t1 >= t0) # => true
p(Process.clock_gettime(Process.CLOCK_REALTIME) > 1.0e9) # => true
ms = Process.clock_gettime(Process.CLOCK_MONOTONIC, :millisecond)
p(ms > 0) # => true
p(Integer.to_s(ms) == "#{ms}") # => true
f = Process.clock_gettime(Process.CLOCK_MONOTONIC, :float_millisecond)
p(Float.floor(f) >= 0) # => true
```ruby error Process.clock_gettime(Process.CLOCK_MONOTONIC, :bogus) # !> ArgumentError: Process.clock_gettime: unexpected unit: bogus
```ruby error
Process.clock_gettime(999) # !> ArgumentError: Process.clock_gettime: Invalid argument - clock_gettime(999)
```ruby error unit = :millisecond p(Integer.to_s(Process.clock_gettime(Process.CLOCK_MONOTONIC, unit))) # !> Integer.to_s: argument 1 must be Integer, but can be Float
## CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID
`Process.CLOCK_REALTIME()`
`Process.CLOCK_MONOTONIC()`
`Process.CLOCK_PROCESS_CPUTIME_ID()`
`clock_gettime` に渡す時計の番号(Integer)。`CLOCK_REALTIME` は壁時計(1970-01-01 UTC からの秒。`Time.now` と同じ値)、`CLOCK_MONOTONIC` は起点が不定で決して戻らない時計(経過時間用)、`CLOCK_PROCESS_CPUTIME_ID` はこのプロセスが使った CPU 時間です。番号そのものは OS で決まり、Linux では 0、1、2 です。他の Ruby の時計(`CLOCK_THREAD_CPUTIME_ID` など)はありません。
```ruby
p(Process.CLOCK_REALTIME != Process.CLOCK_MONOTONIC) # => true
wall = Process.clock_gettime(Process.CLOCK_REALTIME)
p(Float.abs(wall - Time.to_f(Time.now)) < 1.0) # => true
p(Process.clock_gettime(Process.CLOCK_PROCESS_CPUTIME_ID) >= 0.0) # => true
36. Open3
Open3 は子プロセスを走らせて出力と終了状態を集める操作の集まりです(Ruby の open3 ライブラリの capture2、capture2e、capture3。Sake のプログラムは他に子プロセスを起こす手段が無いので組み込みです。出力を集めず true/false だけ欲しければ Kernel.system。組み込み)。
Ruby の結果は Array [out, status] で status は Process::Status ですが、Sake では Tuple で、終了状態は Integer(終了コード。シグナルで死んだときは -1)です。out, status = Open3.capture2(...) と多重代入で受けます。コマンドが見つからない・起動できないときは IOError。コマンドは Ruby と同じく、1 引数なら shell で解釈、2 引数以上なら第 1 引数がプログラムで残りが引数(shell を通しません)。標準入力は与えられません(Ruby の stdin_data: はありません)。
Open3 に演算子はありません。
36.1 capture2
Open3.capture2(String, *String)
コマンドを走らせ、[stdout, status](String, Integer)の Tuple を返します。子の stderr はこのプロセスの stderr にそのまま流れます。
out, status = Open3.capture2("echo", "hi")
p(out) # => "hi\n"
p(status) # => 0
out2, status2 = Open3.capture2("sh", "-c", "echo $0; exit 3")
p(out2) # => "sh\n"
p(status2) # => 3
```ruby error Open3.capture2(“no_such_command_xyz”) # !> IOError: Open3.capture2: No such file or directory - no_such_command_xyz
## capture2e
`Open3.capture2e(String, *String)`
コマンドを走らせ、stdout と stderr を 1 つに混ぜた `[output, status]`(String, Integer)の Tuple を返します(Ruby の `capture2e`)。
```ruby
out, status = Open3.capture2e("sh", "-c", "echo err >&2; echo out; exit 2")
p(out) # => "err\nout\n"
p(status) # => 2
36.2 capture3
Open3.capture3(String, *String)
コマンドを走らせ、[stdout, stderr, status](String, String, Integer)の Tuple を返します。
out, err, status = Open3.capture3("sh", "-c", "echo err >&2; echo out; exit 2")
p(out) # => "out\n"
p(err) # => "err\n"
p(status) # => 2
p(Open3.capture3("true")) # => ["", "", 0]
killed = Open3.capture3("sh", "-c", "kill -9 $$")
p(killed[2]) # => -1
37. Zlib
Zlib は String を zlib 形式・gzip 形式で圧縮・展開する操作の集まりです(Ruby の Zlib.deflate、inflate、gzip、gunzip。速度のために組み込みです。組み込み)。圧縮レベルやストリームの形(Zlib::Deflate クラスなど)はありません。
圧縮した結果と展開した結果はどちらもバイナリの String(エンコーディング ASCII-8BIT)です。UTF-8 の文字列を展開した結果と元の文字列は、バイトは同じでもエンコーディングが違うので == になりません: 文字列として使うなら String.force_encoding(s, "UTF-8") を通します。壊れた入力や形式の違う入力(inflate に gzip を渡す、など)は ArgumentError で、メッセージは zlib のもの(incorrect header check など。Ruby の Zlib::DataError 等に当たる例外型はありません)。
Zlib に演算子はありません。
37.1 deflate, inflate
Zlib.deflate(String)
Zlib.inflate(String)
deflate は zlib 形式(RFC 1950)に圧縮した String、inflate はそれを元に戻した String を返します。空の文字列も圧縮・展開できますが、inflate("") は ArgumentError(buffer error)です。
s = String.*("hello ", 100)
z = Zlib.deflate(s)
p(String.bytesize(z) < String.bytesize(s)) # => true
p(Zlib.inflate(z) == s) # => true
p(String.encoding(Zlib.inflate(z))) # => "ASCII-8BIT"
j = Zlib.inflate(Zlib.deflate("日本"))
p(j == "日本") # => false
p(String.force_encoding(j, "UTF-8") == "日本") # => true
```ruby error Zlib.inflate(“not compressed”) # !> ArgumentError: Zlib.inflate: incorrect header check
## gzip, gunzip
`Zlib.gzip(String)`
`Zlib.gunzip(String)`
`gzip` は gzip 形式(RFC 1952。`gzip` コマンドや `.gz` ファイルと同じで、先頭 2 バイトは `0x1f 0x8b`)に圧縮した String、`gunzip` はそれを元に戻した String を返します。`deflate` の結果を `gunzip` に、`gzip` の結果を `inflate` に渡すことはできません(`ArgumentError`)。
```ruby
g = Zlib.gzip("hello")
p(String.getbyte(g, 0)) # => 31
p(String.getbyte(g, 1)) # => 139
p(Zlib.gunzip(g)) # => "hello"
File.write("a.gz", g)
out, status = Open3.capture2("sh", "-c", "gunzip -c a.gz")
p(out) # => "hello"
ruby error
Zlib.gunzip(Zlib.deflate("x")) # !> ArgumentError: Zlib.gunzip:
38. Thread
Thread はブロックを並行に走らせるスレッドの型です。Thread.new { ... } が作り、Thread.value(t) か Thread.join(t) で終わりを待ちます。Ruby の Thread と同じ仕組み(Ruby のスレッドそのもの)ですが、操作はここに挙げたものだけで、Thread#[]、status、priority、Thread.pass などはありません。Thread のリテラルはなく、値は Thread.new と Thread.current から来ます(値と型、組み込み)。
- 変数。 ブロックは周りの変数を共有しますが、スレッドでは
Thread.newを囲むブロックの変数(引数とローカル)とスレッドブロック自身の変数は開始時に複製され、関数(トップレベル)の変数は共有のままです。共有カウンタはMutex.synchronizeの中で更新します(Mutex)。 - 抜け方。 スレッドブロックからの
breakとreturnは静的エラーです(ブロックはThread.newが戻った後で走る)。next vがvで終えます。 - エラー。 スレッドの中の例外はそこでは報告されず、
Thread.valueかThread.joinを呼んだ側で上がります。エラーで終わったのにThread.value/Thread.joinで読まれなかったスレッドは、プログラムの終了時に stderr に報告されます(終了ステータスは変わりません):PATH: a thread ended with an error that no Thread.value / Thread.join read:の後に、そのエラーの報告が字下げされて続きます。メインプログラムが終わるとすべてのスレッドが止まり、そのときまだ走っていたスレッドは報告されません。 - 検査。 検査器はブロックを
Thread.newの場所で 1 度走らせ、その値をThread.valueの型にします。実行の交互は解析せず、各操作が実行時に引数を検査します。
Thread に使える演算子は ==、!= だけです(同じスレッドなら等しい)。
38.1 new
Thread.new() { }
ブロックを新しいスレッドで走らせ始め、すぐに Thread を返します。ブロックの値がスレッドの値(Thread.value)です。ブロックは必須で、引数は受け取りません。
t = Thread.new { 1 + 2 }
p(t) # => #<Thread>
p(Thread.value(t)) # => 3
ws = Array.map(Any[1, 2, 3]) { |i| Thread.new { i * 10 } }
p(Array.map(ws) { |w| Thread.value(w) }) # => [10, 20, 30]
```ruby error
t = Thread.new { return 1 } # !> return outside a function
## value
`Thread.value(x)`
スレッドが終わるのを待ち、ブロックの値を返します(Ruby の `t.value`)。何度呼んでも同じ値です。スレッドが例外で終わっていれば、その例外がここで上がります(何度でも)。`Thread.kill` で止められたスレッドの値は nil です。結果の型はブロックの型で、nil を返しうるブロックなら `nil` の検査を受けます。自分自身(`Thread.current`)を待つことはできず、`ThreadError`(`Target thread must not be current thread`)です。
```ruby
t = Thread.new { "done" }
p(Thread.value(t)) # => "done"
p(Thread.value(t)) # => "done"
e = Thread.new { raise ArgumentError, "boom" }
begin
Thread.value(e)
rescue ArgumentError => err
p(Exception.message(err)) # => "boom"
end
```ruby error t = Thread.new { raise ArgumentError, “boom” } Thread.value(t) # !> ArgumentError: boom
```ruby error
Thread.value(Thread.current) # !> ThreadError: Thread.value: Target thread must not be current thread
例外で終わったスレッドをどの value / join も読まずにプログラムが終わると、そのエラーは stderr に報告されます(終了ステータスは 0 のまま)。検査器は stderr が空であることを求めるので、端末での見え方をそのまま示します:
t = Thread.new { raise ArgumentError, "boom" }
Thread.join(Thread.new { 1 })
p(1) # 1 と出た後、stderr に:
# ex.sake: a thread ended with an error that no Thread.value / Thread.join read:
# ex.sake:1: in <main>: ArgumentError: boom
38.2 join
Thread.join(x, [Integer|Float|Rational])
スレッドが終わるのを待ち、スレッドを返します。第 2 引数に秒数を与えると、その時間を過ぎても終わらないときは待つのをやめて nil を返します(スレッドは走り続けます)。結果は 2 引数のときだけ Thread | nil で、レベル 2 で検査されます: if Thread.join(t, 5) で「時間内に終わった」が分かります(sakelib の Timeout.timeout はこう作られています)。スレッドの例外は value と同じくここで上がります。自分自身を待つことはできません(ThreadError)。
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.join(t, 0.01)) # => nil
p(Thread.alive?(t)) # => true
Queue.push(q, :go)
p(Thread.join(t) == t) # => true
p(Thread.join(t, 1) == t) # => true
38.3 alive?
Thread.alive?(x)
スレッドがまだ走っている(終わっていない)なら true。
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.alive?(t)) # => true
Queue.push(q, 1)
Thread.join(t)
p(Thread.alive?(t)) # => false
p(Thread.alive?(Thread.current)) # => true
38.4 current
Thread.current()
今走っているスレッド(メインプログラムのスレッド、または Thread.new で作ったもの)。同じスレッドを表す値はすべて == です。
me = Thread.current
p(Thread.current == me) # => true
t = Thread.new { Thread.current }
p(Thread.value(t) == t) # => true
p(Thread.value(t) == me) # => false
38.5 kill
Thread.kill(x)
スレッドを今いる場所で止め、スレッドを返します(Ruby の t.kill)。ensure 節は走ります。止めたスレッドの value は nil、alive? は false。既に終わっているスレッドには何もしません。
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.kill(t) == t) # => true
Thread.join(t)
p(Thread.alive?(t)) # => false
p(Thread.value(t)) # => nil
38.6 raise
Thread.raise(x, String)
スレッドの今いる場所で RuntimeError(メッセージ付き)を起こし、スレッドを返します。スレッドの中で rescue されなければスレッドはそれで終わり、value や join を呼んだ側でその RuntimeError が上がります(sakelib の Timeout.timeout はこれでブロックを中断します)。既に終わっているスレッドには何もしません。Thread.raise を呼ぶプログラムでは、検査器は Thread.value と Thread.join が RuntimeError を投げうるものとして扱うので、それらを囲む rescue RuntimeError => e が通ります(Thread.raise の無いプログラムでは「本体は RuntimeError を投げない」という rescue の問題になります)。
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.raise(t, "stop") == t) # => true
begin
Thread.value(t)
rescue RuntimeError => e
p(e) # => #<RuntimeError: stop>
end
begin
Thread.join(t)
rescue RuntimeError => e
p(Exception.message(e)) # => "stop"
end
done = Thread.new { 1 }
Thread.join(done)
Thread.raise(done, "late")
p(Thread.value(done)) # => 1
38.7 ==, !=
Thread.==(x, Any)
Thread.!=(x, Any)
2 つの値が同じスレッドなら等しい(!= はその否定)。Thread 以外の値とは等しくありません。
t = Thread.new { 1 }
p(t == t) # => true
p(t != Thread.new { 2 }) # => true
p(t == 1) # => false
39. Mutex
Mutex はスレッドの間で排他をとる錠の型です(Ruby の Thread::Mutex そのもの)。Mutex.new が作り、Mutex.synchronize(m) { ... } がブロックの間だけ錠を持ちます。スレッドが共有する変数(関数やトップレベルの変数、Array や Hash の中身)を複数のスレッドから更新するときに使います(Thread、組み込み)。リテラルはありません。
錠は再入できません: 持っているスレッドがもう一度 lock すると ThreadError(deadlock; recursive locking)です。持っていないスレッドが unlock するのも ThreadError。ThreadError は rescue できる組み込みの例外です(例外)。
Mutex に使える演算子は ==、!= だけです(同じ Mutex なら等しい)。
39.1 new
Mutex.new()
新しい、誰も持っていない Mutex を返します。
m = Mutex.new
p(m) # => #<Mutex>
p(Mutex.locked?(m)) # => false
39.2 synchronize
Mutex.synchronize(x) { }
錠を取り、ブロックを走らせ、ブロックが終わると(例外で抜けても)錠を離し、ブロックの値を返します。他のスレッドが持っている間は待ちます。普通はこの形だけで足ります。自分が持っている Mutex の synchronize を入れ子にすると、lock と同じく ThreadError(deadlock; recursive locking)です。rescue でき、外側の synchronize は抜けるときに錠を離します。
m = Mutex.new
count = 0
ths = Array.map(Any[1, 2, 3, 4]) { |i|
Thread.new { Array.each(Array.new(100, 0)) { |_| Mutex.synchronize(m) { count += 1 } } }
}
Array.each(ths) { |t| Thread.join(t) }
p(count) # => 400
r = Mutex.synchronize(m) { p(Mutex.locked?(m)); "val" } # => true
p(r) # => "val"
p(Mutex.locked?(m)) # => false
begin
Mutex.synchronize(m) { Mutex.synchronize(m) { 1 } }
rescue ThreadError => e
p(Exception.message(e)) # => "deadlock; recursive locking"
end
p(Mutex.locked?(m)) # => false
```ruby error m = Mutex.new Mutex.synchronize(m) { Mutex.synchronize(m) { 1 } } # !> ThreadError: Mutex.synchronize: deadlock; recursive locking
## lock
`Mutex.lock(x)`
錠を取り、Mutex を返します。他のスレッドが持っていれば離されるまで待ちます。自分が既に持っていれば `ThreadError`。`unlock` と組にして使いますが、例外で抜けるときに離し忘れないよう、`synchronize` が普通です。
```ruby
m = Mutex.new
p(Mutex.lock(m) == m) # => true
p(Mutex.locked?(m)) # => true
Mutex.unlock(m)
```ruby error m = Mutex.new Mutex.lock(m) Mutex.lock(m) # !> ThreadError: Mutex.lock: deadlock; recursive locking
## unlock
`Mutex.unlock(x)`
錠を離し、Mutex を返します。持っていない(誰も持っていない、または他のスレッドが持っている)Mutex は `ThreadError`。
```ruby
m = Mutex.new
Mutex.lock(m)
p(Mutex.unlock(m) == m) # => true
p(Mutex.locked?(m)) # => false
```ruby error m = Mutex.new Mutex.unlock(m) # !> ThreadError: Mutex.unlock: Attempt to unlock a mutex which is not locked
## try_lock
`Mutex.try_lock(x)`
錠が空いていれば取って true、誰かが持っていれば待たずに false を返します(自分が持っているときも false で、エラーにはなりません)。
```ruby
m = Mutex.new
p(Mutex.try_lock(m)) # => true
p(Mutex.try_lock(m)) # => false
other = Thread.new { Mutex.try_lock(m) }
p(Thread.value(other)) # => false
Mutex.unlock(m)
p(Mutex.try_lock(m)) # => true
39.3 locked?
Mutex.locked?(x)
どのスレッドかに関わらず、誰かが錠を持っていれば true。
m = Mutex.new
p(Mutex.locked?(m)) # => false
Mutex.lock(m)
p(Mutex.locked?(m)) # => true
p(Thread.value(Thread.new { Mutex.locked?(m) })) # => true
39.4 owned?
Mutex.owned?(x)
今のスレッドが錠を持っていれば true。他のスレッドが持っているときは false。
m = Mutex.new
p(Mutex.owned?(m)) # => false
Mutex.lock(m)
p(Mutex.owned?(m)) # => true
p(Thread.value(Thread.new { Mutex.owned?(m) })) # => false
39.5 ==, !=
Mutex.==(x, Any)
Mutex.!=(x, Any)
同じ Mutex なら等しい(!= はその否定)。Mutex.new の結果はそれぞれ別の値です。
m = Mutex.new
p(m == m) # => true
p(m == Mutex.new) # => false
p(m != Mutex.new) # => true
40. Queue
Queue はスレッドの間で値を渡す、スレッド安全な先入れ先出しの待ち行列です(Ruby の Thread::Queue そのもの)。Queue.push(q, x) が末尾に加え、Queue.pop(q) が先頭を取り出し、空なら誰かが push するまで待ちます。Queue.close(q) が「もう来ない」を伝え、閉じて空になった Queue の pop は nil です。これで生産者・消費者のパターンが書けます(Thread、組み込み)。リテラルはなく、長さの上限(Ruby の SizedQueue)はありません。
要素の型。 検査器にとって Queue の要素型は、その Queue に push されたものすべての和です。Queue.pop の型はそれに nil を加えたもので、--strict(レベル 2)はそのまま使うことを nil の問題として報告します: while (x = Queue.pop(q)) で受けるのが普通の形です。
Queue に使える演算子は ==、!= だけです(同じ Queue なら等しい)。
40.1 new
Queue.new()
新しい空の Queue を返します。
q = Queue.new
p(q) # => #<Queue>
p(Queue.empty?(q)) # => true
40.2 push
Queue.push(x, Any)
値を末尾に加え、Queue を返します。どんな値でも加えられ、nil も加えられますが、nil は pop 側で「閉じて空」と区別できません。閉じた Queue への push は IOError(Ruby では ClosedQueueError)。
q = Queue.new
p(Queue.push(q, 1) == q) # => true
Queue.push(q, "two")
p(Queue.size(q)) # => 2
```ruby error q = Queue.new Queue.close(q) Queue.push(q, 1) # !> IOError: Queue.push: push to a closed Queue
## pop
`Queue.pop(x, [Integer|Float|Rational])`
先頭の値を取り出して返します。空なら、誰かが push するか Queue が閉じられるまで待ちます。閉じていて空なら待たずに nil。第 2 引数に秒数を与えると、その時間待っても何も来なければ nil を返します(0 なら待ちません)。結果は常に「要素の型 | nil」で、レベル 2 で検査されます。
```ruby
q = Queue.new
Queue.push(q, 1)
Queue.push(q, 2)
p(Queue.pop(q)) # => 1
p(Queue.pop(q, 5)) # => 2
p(Queue.pop(q, 0.01)) # => nil
workers = Array.map(Any[1, 2]) { |i|
Thread.new { total = 0; while (x = Queue.pop(q)); total += x; end; total }
}
Array.each(Any[1, 2, 3, 4, 5, 6]) { |i| Queue.push(q, i) }
Queue.close(q)
p(Array.sum(Array.map(workers) { |w| Thread.value(w) })) # => 21
```ruby error q = Queue.new Queue.push(q, 1) p(Queue.pop(q) + 1) # !> the operands may be nil
## close
`Queue.close(x)`
Queue を閉じて返します。以後の `push` は `IOError`、`pop` は残りを順に返した後 nil を返し、待っている `pop` はすべて nil で起きます。もう一度閉じても何も起きません。
```ruby
q = Queue.new
Queue.push(q, 1)
p(Queue.close(q) == q) # => true
p(Queue.pop(q)) # => 1
p(Queue.pop(q)) # => nil
40.3 closed?
Queue.closed?(x)
閉じられていれば true。
q = Queue.new
p(Queue.closed?(q)) # => false
Queue.close(q)
p(Queue.closed?(q)) # => true
40.4 size
Queue.size(x)
今入っている値の個数(Integer)。
q = Queue.new
p(Queue.size(q)) # => 0
Queue.push(q, :a)
Queue.push(q, :b)
p(Queue.size(q)) # => 2
Queue.pop(q)
p(Queue.size(q)) # => 1
40.5 empty?
Queue.empty?(x)
何も入っていなければ true。
q = Queue.new
p(Queue.empty?(q)) # => true
Queue.push(q, 1)
p(Queue.empty?(q)) # => false
40.6 ==, !=
Queue.==(x, Any)
Queue.!=(x, Any)
同じ Queue なら等しい(!= はその否定)。Queue.new の結果はそれぞれ別の値です。
q = Queue.new
p(q == q) # => true
p(q == Queue.new) # => false
p(q != Queue.new) # => true
41. Socket
Socket は TCP 接続の型です。値は Socket.connect(host, port)(平文)、Socket.connect_ssl(host, port)(TLS)、そしてサーバ側の TCPServer.accept から来ます(TCPServer)。どれも同じ型 Socket で、同じ操作で読み書きします(Ruby の TCPSocket と OpenSSL::SSL::SSLSocket。組み込み)。読む操作は IO と同じ形(gets、read)ですが、Socket は IO ではなく、IO.puts などには渡せません。
接続できない・切れた・名前が解けないといったネットワークの失敗は IOError です(Ruby の Errno::ECONNREFUSED、SocketError、OpenSSL::SSL::SSLError などをまとめたもの。例外)。読み書きの待ち時間は set_timeout で決め、過ぎると IOError。gets と read が返す String はバイナリ(エンコーディング ASCII-8BIT)で、文字列として使うには String.force_encoding(s, "UTF-8") を通します。UDP や Unix ドメインソケットはありません。
Socket に使える演算子は ==、!= だけです(同じ接続なら等しい)。例は 127.0.0.1 のポート 0 に TCPServer を立て、同じプログラムの Thread から接続します。
41.1 connect
Socket.connect(String, Integer, [Integer|Float|Rational])
host(名前か IP アドレス)の port に TCP で接続し、Socket を返します。第 3 引数は接続を待つ秒数で、過ぎると IOError(省略時は OS の既定まで待ちます)。接続を拒まれたときも、名前が解けないとき(Ruby の Socket::ResolutionError)も IOError です。名前が解けないときのメッセージはリゾルバのもの(getaddrinfo(3): Name or service not known など)です。
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
client = Thread.new {
s = Socket.connect("127.0.0.1", port, 5)
Socket.write(s, "ping\n")
reply = Socket.gets(s)
Socket.close(s)
reply
}
conn = TCPServer.accept(srv)
p(Socket.gets(conn)) # => "ping\n"
Socket.write(conn, "pong\n")
Socket.close(conn)
p(Thread.value(client)) # => "pong\n"
TCPServer.close(srv)
```ruby error Socket.connect(“127.0.0.1”, 1) # !> IOError: Socket.connect: Connection refused
```ruby error
Socket.connect("no-such-host.invalid", 80) # !> IOError: Socket.connect: getaddrinfo
41.2 connect_ssl
Socket.connect_ssl(String, Integer, [Integer|Float|Rational])
host の port に TCP で接続し、その上で TLS のハンドシェイクをして Socket を返します。相手の証明書は OS の信頼ストアで検証し、host を SNI とホスト名の検査に使います(Ruby の OpenSSL::SSL::SSLSocket に VERIFY_PEER)。第 3 引数は TCP の接続を待つ秒数。接続の失敗もハンドシェイクの失敗(TLS を話さない相手、検証できない証明書)も IOError。close は TLS と TCP の両方を閉じます。外部に繋ぐ例は検査器の中では走らせられないので、ここでは形だけ示します:
s = Socket.connect_ssl("example.com", 443, 10)
Socket.write(s, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
p(Socket.gets(s)) # "HTTP/1.0 200 OK\r\n" など
Socket.close(s)
TLS を話さないサーバに繋ぐとハンドシェイクが IOError になります:
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) port = TCPServer.port(srv) t = Thread.new { c = TCPServer.accept(srv); Socket.close(c) } Socket.connect_ssl(“127.0.0.1”, port) # !> IOError: Socket.connect_ssl:
## set_timeout
`Socket.set_timeout(x, Integer|Float|Rational|Nil)`
以後の `gets`、`read`、`write` が待つ秒数を決め、Socket を返します(Ruby の `IO#timeout=`)。過ぎると `IOError`(`Blocking operation timed out!`)。nil で無制限(既定)に戻ります。
```ruby
srv = TCPServer.new("127.0.0.1", 0)
s = Socket.connect("127.0.0.1", TCPServer.port(srv))
p(Socket.set_timeout(s, 0.05) == s) # => true
begin
Socket.gets(s)
rescue IOError => e
p(Exception.message(e)) # => "Blocking operation timed out!"
end
Socket.set_timeout(s, nil)
Socket.close(s)
TCPServer.close(srv)
41.3 gets
Socket.gets(x)
次の 1 行を改行を含めて返し、相手が書き込み側を閉じて(close_write か close)データが尽きたら nil です。結果は String | nil で、--strict(レベル 2)は確かめずに使うことを nil の問題として報告します。set_timeout の時間を過ぎると IOError。
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
t = Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, "a\nb\n")
Socket.close(s)
}
c = TCPServer.accept(srv)
while (line = Socket.gets(c))
p(line) # => "a\n"
# => "b\n"
end
p(Socket.gets(c)) # => nil
Thread.join(t)
TCPServer.close(srv)
41.4 read
Socket.read(x, Integer)
ちょうど n バイト読んで返します。n バイト溜まるまで待ち、その前に相手が閉じれば残りだけを返し、もう何も無ければ nil です(String | nil、レベル 2。n が 0 なら常に "")。負の n は ArgumentError。Ruby の read と違い、引数無しの形(終端まで読む)はありません。
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
t = Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, "hello")
Socket.close(s)
}
c = TCPServer.accept(srv)
p(Socket.read(c, 2)) # => "he"
p(Socket.read(c, 10)) # => "llo"
p(Socket.read(c, 10)) # => nil
p(Socket.read(c, 0)) # => ""
Thread.join(t)
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) s = Socket.connect(“127.0.0.1”, TCPServer.port(srv)) Socket.read(s, -1) # !> ArgumentError: Socket.read: negative size -1
## write
`Socket.write(x, String)`
文字列を送り、送ったバイト数(Integer)を返します。改行は加えません。閉じた Socket や `close_write` の後は `IOError`。
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
t = Thread.new {
s = Socket.connect("127.0.0.1", port)
n = Socket.write(s, "日本\n")
Socket.close(s)
n
}
c = TCPServer.accept(srv)
got = Socket.gets(c)
if got
p(String.encoding(got)) # => "ASCII-8BIT"
p(String.force_encoding(got, "UTF-8")) # => "日本\n"
end
p(Thread.value(t)) # => 7
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) s = Socket.connect(“127.0.0.1”, TCPServer.port(srv)) Socket.close(s) Socket.write(s, “x”) # !> IOError: Socket.write: closed stream
## close_write
`Socket.close_write(x)`
書き込み側だけを閉じて nil を返します(TCP の半閉鎖)。相手の `gets` や `read` はデータが尽きた後 nil を受け取り、こちらは引き続き相手からの返事を読めます。「全部送ったら返事を待つ」プロトコルに使います。この後の `write` は `IOError`。
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
client = Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, "1 2 3")
Socket.close_write(s)
reply = Socket.gets(s)
Socket.close(s)
reply
}
c = TCPServer.accept(srv)
body = Socket.read(c, 100)
if body
Socket.write(c, String.upcase(body) + "\n")
end
Socket.close(c)
p(Thread.value(client)) # => "1 2 3\n"
TCPServer.close(srv)
41.5 close
Socket.close(x)
接続を閉じて nil を返します(TLS なら TCP も閉じます)。もう一度閉じても何も起きません。閉じた後の読み書きは IOError。
srv = TCPServer.new("127.0.0.1", 0)
s = Socket.connect("127.0.0.1", TCPServer.port(srv))
p(Socket.close(s)) # => nil
p(Socket.close(s)) # => nil
TCPServer.close(srv)
41.6 ==, !=
Socket.==(x, Any)
Socket.!=(x, Any)
2 つの値が同じ Socket なら等しい(!= はその否定)。1 つの接続の両端(クライアントの connect とサーバの accept)は別の Socket です。Socket 以外の値とは等しくなく、関数形でも false になります(エラーにはなりません)。
srv = TCPServer.new("127.0.0.1", 0)
s = Socket.connect("127.0.0.1", TCPServer.port(srv))
c = TCPServer.accept(srv)
p(s == s) # => true
p(s == c) # => false
p(s != c) # => true
p(Socket.==(s, 1)) # => false
Socket.close(s)
Socket.close(c)
TCPServer.close(srv)
42. TCPServer
TCPServer は TCP の接続を受け付ける待ち受けソケットの型です(Ruby の TCPServer)。TCPServer.new(host, port) がポートを開き、TCPServer.accept(s) が次の接続を待って Socket を返します。受け付けた接続は Socket の操作で読み書きします(組み込み)。リテラルはありません。
ポートを開けない(使用中、権限が無い)、閉じたサーバで accept する、などの失敗は IOError です(例外)。
TCPServer に使える演算子は ==、!= だけです(同じサーバなら等しい)。例は 127.0.0.1 のポート 0(空いているポートを OS が選ぶ)で立て、同じプログラムの Thread から接続します。
42.1 new
TCPServer.new(String, Integer)
host のアドレスの port で待ち受けを始め、TCPServer を返します。host は "127.0.0.1"(ローカルだけ)、"0.0.0.0"(すべてのインターフェース)、"localhost" などで、省略できません(Ruby の TCPServer.new(port) の形はありません)。port が 0 なら空いているポートを OS が選び、TCPServer.port で分かります。使用中のポートや権限の無いポート(1024 未満)は IOError。
srv = TCPServer.new("127.0.0.1", 0)
p(srv) # => #<TCPServer>
p(TCPServer.port(srv) > 0) # => true
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) TCPServer.new(“127.0.0.1”, TCPServer.port(srv)) # !> IOError: TCPServer.new: Address already in use
## port
`TCPServer.port(x)`
待ち受けているポート番号(Integer)。`TCPServer.new` に 0 を渡したときに、実際のポートを知るために使います。閉じたサーバでは `IOError`(`closed stream`)。
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
p(port >= 1024) # => true
p(TCPServer.port(srv) == port) # => true
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) TCPServer.close(srv) TCPServer.port(srv) # !> IOError: TCPServer.port: closed stream
## accept
`TCPServer.accept(x)`
次の接続が来るまで待ち、それを `Socket` として返します。接続は `TCPServer.new` の時点から OS が溜めるので、`accept` を呼ぶ前に相手が接続していても失われません。閉じたサーバでは `IOError`。待ち時間の上限はないので、別の Thread から接続するか、時間を区切るなら `Thread.join(t, limit)` と組み合わせます。
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
clients = Array.map(Any["a", "b"]) { |name|
Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, name + "\n")
reply = Socket.gets(s)
Socket.close(s)
reply
}
}
served = Any[]
Array.each(clients) { |_|
c = TCPServer.accept(srv)
p(c) # => #<Socket>
# => #<Socket>
line = Socket.gets(c)
if line
Array.push(served, String.chomp(line))
Socket.write(c, "hi " + line)
end
Socket.close(c)
}
replies = Array.compact(Array.map(clients) { |t| Thread.value(t) }) # Socket.gets は nil かもしれないので、並べ替える前に compact
p(Array.sort(replies)) # => ["hi a\n", "hi b\n"]
p(Array.sort(served)) # => ["a", "b"]
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) TCPServer.close(srv) TCPServer.accept(srv) # !> IOError: TCPServer.accept: closed stream
## close
`TCPServer.close(x)`
待ち受けを止めてポートを離し、nil を返します。既に `accept` した `Socket` はそのまま使えます。もう一度閉じても何も起きません。
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
p(TCPServer.close(srv)) # => nil
p(TCPServer.close(srv)) # => nil
again = TCPServer.new("127.0.0.1", port)
p(TCPServer.port(again) == port) # => true
TCPServer.close(again)
42.2 ==, !=
TCPServer.==(x, Any)
TCPServer.!=(x, Any)
2 つの値が同じサーバなら等しい(!= はその否定)。TCPServer.new の結果はそれぞれ別の値です。TCPServer 以外の値とは等しくなく、関数形でも false になります(エラーにはなりません)。
srv = TCPServer.new("127.0.0.1", 0)
other = TCPServer.new("127.0.0.1", 0)
p(srv == srv) # => true
p(srv == other) # => false
p(srv != other) # => true
p(TCPServer.==(srv, 1)) # => false
TCPServer.close(srv)
TCPServer.close(other)
A. Ruby との違いと未対応のもの
Sake の構文は Ruby のものですが、Ruby の書き方のいくつかは静的エラーになり、別の書き方に置き換えます。この付録は、その置き換えを 1 枚の表にまとめ、表の下で理由を述べます。Ruby で書いて静的エラーが出たら、まずこの表を引いてください。各エラーの hint も、表の Sake 側の形を示します。
A.1 Ruby から Sake へ
表の各行は、Ruby の書き方(左)、Sake での書き方(中)、その一言の理由(右)です。理由の詳しい説明と例は表の下の各節にあります。
| Ruby | Sake | 理由 |
|---|---|---|
s.upcase |
String.upcase(s) |
操作に型を書く |
p.x, p.x = v |
Point.x(p), p.Point.x = v。関数の中では @x, @x = v |
フィールドの読み書きも型を名乗る |
x.nil? |
x == nil |
値へのメソッド呼び出しは無い |
def self.f |
def f。module では module_function |
名前空間に 1 つの名前は 1 つの関数 |
class B < A(継承) |
class B < A(A の定義の書き写し。部分型ではない) |
呼び出し先は静的 |
attr_reader :x |
attr_reader x |
フィールド名は裸 |
def initialize(x) = @x = x |
C.new(x) が格納する。initialize(c) は検査と変換だけ |
引数は新しいインスタンス |
@items = [](既定値) |
initialize の中で @items = Any[] |
フィールドの既定値は無い |
Data.define |
Struct.new |
クラスは可変 |
[1, 2](伸びる配列) |
Any[1, 2]。[1, 2] は Tuple(長さ固定) |
リテラルの形が型を決める |
{a: 1}(Hash) |
Hash.of(a: 1)。{a: 1} は Record |
同上 |
%w[a b], %i[a b] |
String["a", "b"], Symbol[:a, :b] |
未決(Tuple か Array か) |
PI = 3.14 |
def pi = 3.14。表は once { ... } |
値の定数は無い |
Math::PI, ARGV, $stdout |
Math.PI(Math::PI も可)、ARGV、IO.stdout |
操作として読む |
case x when Integer |
case x in Integer |
=== は無い |
for x in xs |
Array.each(xs) { \|x\| } |
反復は操作 |
$1, $~ |
m = String.match(s, re) の後 m[1] |
グローバル変数は無い |
proc, lambda, ->, &:sym, method(:f) |
無し。ブロックは yield か &b で渡すだけ |
ブロックは第二級 |
obj.send(:f), define_method, method_missing, eval |
無し | 呼び出し先は静的 |
g(**opts) |
無し。Hash を位置で渡す | 設計判断中(D11) |
rescue A で A の子クラスも捕まえる |
rescue M(型が include したモジュール) |
class B < A は書き写し |
1 + money |
Money に coerce(m, other) を定義 |
左のオペランドが決める |
A.1.1 操作に型を書く
Sake では型を名乗るのは操作だけです。s.upcase のような値へのメソッド呼び出しは静的エラーで、検査器は String.upcase(s) を hint で示します(s.strip.upcase には連鎖の形 String.strip(s).upcase)(プログラムの構造と名前解決)。フィールドの読み書きも同じで、p.x は Point.x(p)、p.x = v は p.Point.x = v です。クラスの関数の中では @x が第 1 引数のフィールド x を指します(クラス)。
Point = Struct.new(:x, :y)
class Point
def shift(pt, d)
@x = @x + d # Point.set_x(pt, Point.x(pt) + d)
@x
end
end
pt = Point.new(1, 2)
p(Point.x(pt)) # => 1
pt.Point.x = 5
p(Point.shift(pt, 1)) # => 6
p(pt) # => #<struct Point x=6, y=2>
x.nil? も値へのメソッド呼び出しなので同じエラーになり、hint は x == nil を示します。
A.1.2 クラス
プログラムが定義する型はクラスです。class C に attr_* の行で宣言しても、略記の Struct.new(:x, :y) で作っても同じものです。Ruby の class と違う点は次のとおりです(クラス)。
def self.fは書かない。 Sake にselfは無く、class Cの中のdef fがそのままC.fです。Ruby のクラスメソッドもインスタンスメソッドも、Sake では同じdef fになります。module ではmodule_functionを書きます。- 継承は無い。
class B < Aは A の定義(フィールド、関数、include)を B に書き写す略記です。その後 A と B は無関係な型で、B の値をA.f(b)に渡すのは型エラーです。 - フィールド名は裸。
attr_reader :xは「:を付けずにxと書く」というエラーになります。 initializeは値を受け取らない。C.new(x)がフィールドを格納した後、def initialize(c)が新しいインスタンスを受けて走ります。Ruby のdef initialize(x) = @x = xは、インスタンス自身を自分のフィールドに入れることになるので静的エラーです。- 既定値は
initializeで。attr_reader items = Any[]はエラーで、最初の値はinitializeの中で@items = Any[]と設定します。C.newはそのフィールドを省略できます。 Data.defineは無い。 Sake のクラスは可変なので、Ruby のStructに当たるStruct.newを使います。
class A
attr_reader x
def double(a) = @x * 2
end
class B < A # A の x と double を B に書き写す
attr_reader y
end
b = B.new(1, 2)
p(B.double(b)) # => 2
p(b) # => #<struct B x=1, y=2>
```ruby error
class Bag
attr_reader name, items
def initialize(x) = @items = x # !> @items = x stores the new Bag in its own field: x is the instance, not a value
end
### リテラルと定数
リテラルは形で型が決まります([値と型](03-values.md))。
- **Ruby の `Array[1, 2]`、`Hash[...]`、`Set[...]` は、Sake では Array の Array、Hash の Array、Set の Array** です。どの T でも `T[...]` は T の Array だからです。何でも入る Array は `Any[...]`、Hash は `Hash.of(k => v)` か `Hash.new`、Set は `Set.of(a, b)` か `Set.new(xs)` で作ります。
- **`[1, 2]` は Tuple** で、長さが固定です。伸びる配列は `Any[1, 2]`、空なら `Any[]` と書きます。Tuple を `Array.push` に渡すと、`Any[...]` と書く hint 付きの型エラーです。
- **`{a: 1}` は Record** で、`Hash.of(a: 1)` が Hash です。Record のフィールドはパターン `r => {a:}` で読みます。
- **`%w[a b]` と `%i[a b]` は未対応** です。Tuple にするか Array にするかが決まっていません。`String["a", "b"]`、`Symbol[:a, :b]` と書くと、要素型付きの Array になります。
- **値の定数は無い。** `PI = 3.14` は静的エラーで、hint は `def pi = 3.14` を示します。計算して保持したい表は `def table = once { ... }` です([関数とブロック](04-functions.md))。定数に代入できるのは `Struct.new` と `Exception.new` だけです。
- **組み込みの定数は操作。** `Math.PI`、`ARGV`、`IO.stdout` のように呼びます。`Math::PI` など組み込みの数個は Ruby の形でも読めます。`$stdout` のようなグローバル変数はありません。
```ruby
t = [1, 2]
xs = Any[1, 2]
Array.push(xs, 3)
p(t, xs) # => [1, 2]
# => [1, 2, 3]
h = Hash.of(a: 1)
h[:b] = 2
p(h) # => {a: 1, b: 2}
def pi = 3.14
p(pi, Math.PI) # => 3.14
# => 3.141592653589793
```ruby error xs = [1, 2] Array.push(xs, 3) # !> Array.push: argument 1 must be Array, but is [Integer, Integer] [type]
### 制御と反復
- **`case`/`when` は無く、`case`/`in`** を使います。Ruby の `when` はレシーバで `===` をディスパッチしますが、Sake には `===` がありません。`case x in Integer` は型で分岐し、検査器が網羅性を見ます([制御構造とパターン](06-control.md))。
- **`for` は無く、操作で反復します。** `Array.each(xs) { |x| ... }`、`Range.each(1..3) { |i| ... }` のように、要素を渡すのは Array や Range の操作です([関数とブロック](04-functions.md))。
- **`$1`、`$~` は無い。** 一致を変数に保って読みます: `m = String.match(s, re)` の後、`m` が nil でなければ `m[1]`。
### ブロックと動的な機能
- **ブロックは第二級です。** 関数に渡して `yield` で呼ぶか、`&b` で次の関数に渡すだけで、変数に格納できません。`proc`、`lambda`、`->`、`method(:f)` は無く、`&:sym` も渡せません([関数とブロック](04-functions.md))。
- **呼び出し先は静的です。** `send`、`define_method`、`method_missing`、`eval` はありません。`eval` は「静的解析を無効にする」というエラーになります。
- **キーワードの受け渡し。** `def f(**opts)` で集めることはできますが、`g(**opts)` と次の関数に渡すことはできません(`**` is not supported)。Hash を位置引数で渡します。設計判断 D11 として開いています。
### 例外
`rescue A, B => e` は列挙した型を捕まえます([例外とエラー](08-exceptions.md))。`class MyErr < StandardError` と書いても MyErr は StandardError の部分型ではなく、`<` は書き写しなので、`rescue A` は `class B < A` を捕まえません。まとめて捕まえるには、型にモジュールを include させて `rescue M` と書きます。組み込みの型は Ruby の親子関係のとおりです(`rescue IndexError` は KeyError も捕まえる)。ただし `rescue => e`、`rescue StandardError => e`、`rescue Exception => e` の 3 つは、Ruby の「どの例外も」という意味を保って、rescue できるすべての例外を捕まえます。
```ruby
class MyErr < StandardError
end
begin
raise MyErr, "boom"
rescue KeyError, IndexError => e
p(1)
rescue MyErr => e
p(Exception.message(e)) # => "boom"
end
A.1.3 演算子
a + b は Arithmetic.+(a, b) の略記で、左のオペランドの型で解決されます(演算子と添字)。1 + money は Integer でディスパッチするので、Money の + には届きません。Ruby と同じプロトコルで、Money に coerce(m, other) を定義して [left, right] の Tuple を返すと、組を変換してから演算子が走ります。
class Money
include Arithmetic
attr_reader cents
def +(a, b) = Money.new(@cents + Money.cents(b))
def coerce(m, other) = [Money.new(other * 100), m]
end
m = Money.new(50)
p(2 + m) # => #<struct Money cents=250>
A.2 未対応のもの
次はいずれも静的に拒否され、「not supported」のエラーになります。多くは設計判断を待っています。
- Record のフィールドへの書き込み。
r[:a] = 2は Record を添字できないというエラーです。読むのはパターンr => {a:}です。 - 型スコープ
Integer.(a + b)。 case/when。case/inを使います。%w[]、%i[]。 Tuple か Array か未決です。for。 今のところ予定なし。Range.each(1..3) { |i| ... }のような操作で反復します。- パターンの他の形。 Tuple パターンの
*rest、find パターン、ピン^x、ガードin Integer if c。使えるパターンは制御構造とパターンにあります。 - 第一級のブロック。 ブロックの格納、
proc、lambda。 g(**opts)。 集めたキーワードを次に渡すこと。begin ... end while。whileを先に書きます。
A.3 設計の資料
DESIGN.md(日本語): 設計の経緯と各決定の理由。docs/comparison.md(日本語): Sake の新規性。軸ごとに Elm・Crystal・Rust・Elixir・TypeScript・Clojure や型推論の研究と比べ、何が新しく何が既存かを判定する。TODO.md: 開いている課題と設計判断(D1〜D13)。experiments/: 各実験の方法・結果・限界(README 付き)。
Contents
The language
This part describes the language Sake itself, as the v0 interpreter (bin/sake) implements it, in ten chapters and one appendix. It is written for readers who know Ruby, people and language models alike. The syntax is Ruby’s, so what the part explains is the meaning Sake gives to Ruby’s spelling, and the rules Ruby does not have.
One idea
A Sake program is written in Ruby’s syntax, but instead of name.upcase you write String.upcase(name). Only an operation names its type; a variable, a parameter, a return value or a field carries nothing.
words = String.split("the cat and the hat", " ")
counts = Hash.new(0)
Array.each(words) { |w| counts[w] += 1 }
p(Hash.to_a(counts).Array.sort_by { |w, n| -n }.first(2)) # [["the", 2], ["cat", 1]]
Nearly every property of the language follows from this one rule.
- One line is enough to read most calls.
Array.each(words)says whicheachruns without a declaration to look up; in a chainArray.sort_by(xs) { }.first(3),.firstis an operation of Array, the type the line named last. - Call targets are fixed before running.
String.upcase(s)names one function. A module’s mixin functionM.f(x), a listed call(A|B).f(x)and an operatora + bchoose by the type of the first argument, but the set of candidates (the types that includeM, the listed types, the operator table) is fixed before running, and a type outside the set is reported before running. Nothing searches the receiver at run time (nomethod_missing, no reflection), so a misspelled name (String.upcse) or a wrong number of arguments is also reported for the whole program before it starts. - Types are inferred, never written. The checker infers the types of the whole program and reports a value that does not fit an operation (
"" + 1, an unchecked nil) before running; how much of that stops a run is chosen with--strict, levels 0 to 4. - Run-time checks remain. Values carry type tags and every operation checks its arguments, so what the checks before running miss still stops at that operation’s line.
A few spellings mean something else than in Ruby. [a, b] is a Tuple and {x: 1} a Record; an Array and a Hash are written Any[...] and Hash.of(...). Inside a class, @x is “field x of the first argument”, not an instance’s own state. class B < A is shorthand for writing A’s definitions in B, not inheritance. The chapters of this part take up these differences one by one.
The chapters
| Chapter | Contents |
|---|---|
| Overview and running programs | The four principles, the command and its exit status, the --strict levels 0 to 4 and their items (type, nil, mixed, index-nil, exhaustive, rescue, unrescued), the format of static and runtime errors. |
| Program structure and name resolution | Files and require, qualified calls T.f(x), chains T.f(x).g and _, why there are no calls on values, unqualified calls (your own functions, puts, p), include, module_function and dispatch, listing the types (A\|B).f(x), arity and blocks. |
| Values and types | How values print, Tuple versus Record versus Array, Hash and Set, nil and the narrowing of nil \| T, Ruby’s other types (Symbol, Range, Regexp, Time, Rational, Complex). |
| Functions and blocks | Polymorphic functions without annotations, optional and keyword parameters, blocks passed with yield, once. |
| Operators and indexing | a + b is short for Arithmetic.+(a, b), resolved by the type of the left operand; Arithmetic, Comparable, Bitwise and Indexable give your own types operators; a[i] and a[i] = v. |
| Control flow and patterns | if, while, case/in, narrowing with x in T, the exhaustiveness check of a case. |
| Classes | Declaring a type with class and attr_* lines (Struct.new is the shorthand), what @x means, initialize, class B < A, one type per construction site. |
| Exceptions and errors | raise, rescue, ensure, retry, declaring exception types, the inference of which exceptions escape a function, the kinds of static and runtime error. |
| Built-in operations | A survey of the about 550 built-in operations (named after Ruby’s core library) and typed arrays T[...]. The exact signature and examples of each operation are in the Built-in reference. |
| The library (sakelib) | Ports of the standard library and of gems, minitest, how to write tests. |
| Appendix Differences from Ruby and what is not supported | Ruby’s spelling against Sake’s, the syntax and features not supported, the design material. |
How to read it
- If you know Ruby and want to start writing, read Overview and running programs, Program structure and name resolution, Values and types and Operators and indexing in that order, and keep the appendix’s table at hand: it explains most static errors you will meet. Read Classes when you declare a type.
- If a language model is to write Sake, hand it cheatsheet.md, which compresses the rules of this part into four pages (about 10k tokens). A guided introduction is tutorial.md.
- To try things, the playground runs in the browser, with completion, diagnostics as you type, and the inferred types on hover.
- Conventions. A
# valuein an example is the real output ofbin/sake. A “static error” is reported before running, and nothing runs; a “runtime error” stops at that operation. “Level n” means--strict=n.
1. Overview and running programs
Sake (/seɪk/) is an experimental language designed for the sake of finding a new place for types. It keeps Ruby’s syntax, but you write the type on each operation, never on a variable, a parameter, a return value, or a field.
name = "sake"
puts(String.upcase(name)) # => SAKE
Ruby’s spelling, name.upcase, is a static error, and its hint gives the form above.
```ruby error
name = “sake”
puts(name.upcase) # !> method call on a value name.upcase is not allowed
This book is the reference manual of the language as implemented by the v0 interpreter (`bin/sake`). For a guided introduction see [tutorial.md](https://github.com/ko1/sake-lang/blob/main/docs/tutorial.md); for a short summary to hand to a language model, [cheatsheet.md](https://github.com/ko1/sake-lang/blob/main/docs/cheatsheet.md) (about 10k tokens). The design notes and their reasons are in `DESIGN.md` (in Japanese).
## Principles
Nearly every property of Sake follows from four principles. Each chapter of this part works out their consequences.
1. **Ruby syntax.** A Sake program is a Ruby program as parsed by Prism. Sake accepts a subset of Ruby's syntax and gives some constructs a different meaning.
2. **Types are written on operations, not on bindings.** An operation is called with its type, `Type.op(subject, args...)`. Variables, parameters, return values, and fields carry no type annotations.
3. **Call targets are fixed before running.** `String.upcase(s)` names one function. A module's mixin function `M.f(x)`, a listed call `(A|B).f(x)` and an operator `a + b` choose by the type of the first argument, but the set of candidates is fixed before running. The candidates are the types that include `M`, the listed types, or the operator table. A type outside the set is reported before running. Nothing searches the receiver at run time (no `method_missing`, no reflection), so a misspelled name (`String.upcse`) or a wrong number of arguments is also reported for the whole program before it starts.
4. **Values carry type tags, and every operation checks them.** A wrong type is reported as an error at the operation that received it, with the line number. These checks are always enabled.
The checks of principle 4 catch what the checks before running let through. In the next example, `h["b"]` gives nil for the missing key, and `String.upcase` stops at that line.
```ruby error
h = Hash.of("a" => "x")
puts(String.upcase(h["b"])) # !> TypeError: String.upcase: argument 1 must be String, got nil
1.1 Running
bin/sake checks a program first and runs it when no problem is found. Options select checking only, or how strictly to check.
bin/sake FILE.sake # check, then run
bin/sake -c FILE.sake # check only
bin/sake --strict FILE.sake # check more strictly (level 2), then run
bin/sake --strict=3 FILE.sake # levels 0-4, or items: --strict=type,nil
bin/sake --types FILE.sake # experimental: print the inferred types instead of running
bin/sake --dump=ast FILE.sake # print the resolved program (SakeAST)
| Exit status | Meaning |
|---|---|
| 0 | success |
| 1 | runtime error |
| 2 | problem found before running (nothing was executed) |
The interpreter is written in Ruby and needs Ruby 4.0 (tested with 4.0.2 and Prism 1.9.0). It has no other dependencies.
1.2 Strictness (--strict)
--strict sets which problems stop the program before it runs. The checker infers the types of the whole program and sorts the problems it finds into items (type, nil, …). A level is a set of items that stop the program. Whatever is not reported is still checked while running.
| Level | Option | Items added | Stops |
|---|---|---|---|
| 0 | --strict=0 |
(none) | what is always checked |
| 1 | default | type, rescue |
a type that does not fit; a rescue an earlier clause already covers |
| 2 | --strict |
nil, mixed |
an unchecked nil; types that met in a field |
| 3 | --strict=3 |
index-nil, exhaustive |
the nil of a missed index; a case that is not exhaustive |
| 4 | --strict=4 |
unrescued |
a raise that reaches the top level |
Each level includes the items of the levels below it. One item, unused-rescue, is in no level: it is a warning unless it is chosen (--strict=2,unused-rescue).
1.2.1 Always checked (level 0)
These problems stop the program before running at every level: syntax, names, argument counts, blocks, calls on values, forbidden syntax, and the literal types in T[...].
1.2.2 type (level 1)
A value whose type, other than nil, does not fit the operation. It is reported both when the type is certain ("" + 1) and when the value may have a type that does not fit (pick() + 1 where pick returns 1 or "").
```ruby error def half(n) = n / 2 puts(half(“ten”)) # !> the operands are (String, Integer), which the left operand’s type does not support [type]
The report points at `n / 2` inside `half`. Its hint, `reached by the call at line 2`, names the call that led there.
### `rescue` (level 1)
A `rescue` clause that an earlier clause of the same `begin` already covers, so it is never reached. For an example, see "Grouping with a module" in [Exceptions](08-exceptions.md).
### `unused-rescue` (a warning)
A `rescue` of an exception the begin body never raises. It covers the exception types the program declares, and `RuntimeError`. Built-in kinds such as `KeyError` and `ZeroDivisionError` can come from ordinary operations, so they are always assumed possible and never reported.
It is a warning and the program runs. A defensive `rescue` is normal, and whether anything raises the exception depends on the whole program: in a library, a `rescue` for something only some apps raise would otherwise stop every other app. Choose the item to make it an error: `--strict=2,unused-rescue`.
```ruby
class ParseError < StandardError
end
begin
p(10 / 2) # => 5
rescue ParseError # warning: the begin body never raises ParseError [unused-rescue]
p(0)
end
1.2.3 nil (level 2)
A value that may be nil, used without a check. What is a TypeError while running at level 1 is reported before running at level 2.
```ruby error words = String.split(“a b”, “ “) w = Array.find(words) { |x| x == “c” } puts(String.upcase(w)) # !> argument 1 may be nil (nil | String) [nil]
The nil of a miss is excepted. A miss is the nil given by an index `x[k]`, by `Array.dig`, `Hash.dig`, `MatchData.begin`/`end`, and by these operations on an empty collection: `Array.first`, `last`, `pop`, `shift`, `min`, `max`, `minmax`, `at`, `slice`, `sample`, `delete_at`, `Set.first`, `min`, `max`, `min_by`, `max_by`. The `index-nil` item of level 3 covers them. How a nil is narrowed away is described in [Values and types](03-values.md).
### `mixed` (level 2; a warning at level 1)
`mixed` is a kind of `type` report. It is given when the failing types all appear, together with fitting ones, in one field of a class. The elements of a container held in a field count as well.
The checker gives a field one type per construction site ([Classes](07-classes.md)). When instances made at one place are used for different values, the types meet in that field. In the next example, the Heap that `make()` builds is used for Integers and for Strings.
```ruby error
class Heap
attr_accessor items
def initialize(h)
@items = Any[]
end
def push(h, x) = Array.push(@items, x)
def top(h) = Array.first(@items)
end
def make() = Heap.new(nil)
ints = make()
Heap.push(ints, 1)
strs = make()
Heap.push(strs, "a")
p(Heap.top(ints) + 1) # !> the operands may be (nil, Integer), (String, Integer), which the left operand's type does not support [mixed]
Such a report is likely a meeting rather than a mistake, so it stops the program from level 2, and at level 1 it is printed as a warning. At level 1 the example above prints the warning, runs, and prints 2.
The cost of this heuristic: a field that really got a wrong type at the same place is also mixed. For example, Config.new("h", port) where port is 80 on one call and "eighty" on another. Check such a value where it is stored, in initialize (@port => Integer).
1.2.4 index-nil (level 3)
The nil of a miss (above), used without a check. The next program runs up to level 2 and prints 2.
xs = Any[1, 2, 3]
x = xs[0]
p(x + 1) # => 2
At level 3 it stops before running.
$ bin/sake --strict=3 index.sake
index.sake:3:3: error: Arithmetic.+: the operands may be nil, because x[k] (or `a, b = array`) gives nil when the element is missing [index-nil]
hint: check the value first: `if x`, `while x`, `return unless x`, or `x != nil`
hint: or use Array.fetch / Hash.fetch, which raise instead
1.2.5 exhaustive (level 3)
A case/in that may get a value of an open type that no literal branch takes. An open type is one whose values cannot be listed: String, Integer, a Symbol not written as a literal, and so on. The next program runs up to level 2.
def kind(s)
case s
in "a" then 1
in "b" then 2
end
end
p(kind(String.downcase("A"))) # => 1
$ bin/sake --strict=3 kind.sake
kind.sake:2:3: error: case/in: no `in` branch matches some values of String [exhaustive]
hint: add an `else`, or `in` branches for the other values
hint: reached by the call at line 7
1.2.6 unrescued (level 4)
A raise that may reach the top level without being rescued. The item is meant for a program; a library’s raises are meant for its callers, so it does not suit a library.
def check(n)
raise ArgumentError, "negative" if n < 0
n
end
p(check(1)) # => 1
$ bin/sake --strict=4 check.sake
check.sake:2:3: error: raise: ArgumentError may reach the top level without being rescued [unrescued]
hint: rescue it, or check with a level below 4
1.2.7 Naming items
Items can be named instead of a level.
--strict=type,nilselects exactly these items.--strict=2,index-niladds an item to a level.--strict=3,-index-nilremoves an item from a level.
Each report ends with its item, such as [type], which tells you which item to add or remove.
1.2.8 How reports behave
- Errors inside functions. A report inside a polymorphic function adds a hint naming the call that led there (the
halfexample above). - Unreached branches. Branches are not evaluated, so a problem in a branch that never runs is still reported, as in Erlang’s Dialyzer.
- Internal errors. If the type inference itself fails, the type checks are skipped with a warning, and the program runs.
- Too many element pairs. Comparing Arrays or Tuples (
<,<=>, sorting) checks that each pair of element types can be compared. When the element types of one comparison make more than 50,000 pairs, that comparison’s elements are not checked, with a warning at the comparison. The other checks are unaffected.
1.3 Format of static errors
Static errors are reported all together, sorted by position, and nothing runs. Each line gives the file, the line, the column and the message; the hint: lines that follow suggest a fix.
FILE:LINE:COLUMN: error: MESSAGE
hint: SUGGESTION
The name.upcase example at the top of this chapter prints:
greet.sake:2:11: error: method call on a value `name.upcase` is not allowed
hint: String.upcase(name)
hint: Symbol.upcase(name)
1.4 Format of runtime errors
A runtime error prints the line of the operation that stopped and the chain of calls that led there (the from lines).
FILE:LINE: in FUNCTION: KIND: MESSAGE
from FILE:LINE: in CALLER
hint: SUGGESTION
```ruby error def third(xs) = Array.fetch(xs, 2) p(third(Any[1, 2])) # !> IndexError: Array.fetch: index 2 outside of array bounds: -2…2
third.sake:1: in third: IndexError: Array.fetch: index 2 outside of array bounds: -2…2 from third.sake:2: in <main> ```
The kinds are listed in Exceptions and errors.
2. Program structure and name resolution
This chapter covers what a program is made of and how the names in it are resolved. One idea runs through it: every definition is collected before anything runs, and every call’s callee is decided before running. So a function may be called on a line above its definition, and a misspelled name is reported before running.
2.1 Files and require
A program is a file and the files it requires. The top level of a file holds definitions and statements. Definitions are collected together before running; statements run from top to bottom.
puts(greet("Sake")) # => Hello, Sake
def greet(name) = "Hello, #{name}" # callable from a line above its definition
def String.shout(s) = String.upcase(s) + "!"
puts(String.shout("hi")) # => HI!
Point = Struct.new(:x, :y)
class Point
def to_s(p) = "(#{@x}, #{@y})"
end
module Util
module_function
def origin = Point.new(0, 0)
end
puts(Util.origin) # => (0, 0)
2.1.1 What the top level may contain
require "path": reads another file;generate "gen"joins the source a generator prints. See the next sections.- Function definitions:
def name(params) ... endanddef name(params) = expr. - Namespaced function definitions:
def Type.name(params) ..., equivalent to definingnameinsideclass Type. - Namespaces:
class Name ... endandmodule Name ... end. - Classes:
class Namewithattr_*lines, orName = Struct.new(:field, ...). - Statements: any other expression. Statements run in order.
2.1.2 require
require "path" reads path.sake. The extension may be left out, and the path is relative to the directory of the file that requires it.
# lib/units.sake
puts("loading units")
def km_to_m(km) = km * 1000
# main.sake
require "lib/units" # => loading units
puts("main") # => main
puts(km_to_m(3)) # => 3000
A glob, require "sql/*", reads every file matching sql/*.sake next to the requiring file, in name order (the requiring file itself excepted); a glob that matches nothing is an error. Definitions are collected first, so the order matters only for top-level statements.
- Decided before running. The argument must be a string literal, and
requiremust be a statement at the top level of a file. The files of a program are decided before running. - Once. Each file is read once, whatever requires it. A cycle stops at a file already read.
- Order. A required file’s top-level statements run before those of the file that requires it. All
requires of a file are read first. That is whyloading unitscomes beforemainabove. - One collection of definitions. The definitions of every file are collected together, so names may be used across files in any order. Defining a name in two files is an error, as it is in one file.
- Messages. Error messages name the file of each line (
lib/units.sake:4,from lib/broken.sake:3). - Library. A name with no sibling file, such as
require "json", is looked up in the library shipped with the interpreter (sakelib/).
2.1.3 generate: code made before the program runs
Sake defines no function while running. Code that Ruby would make while booting (a function per column of a schema, a template compiled to a function) is made one stage earlier by generate "gen", "arg", ...: before the program is checked, gen.sake is checked and run as a program of its own (the other arguments are its ARGV), and what it prints is joined into the program as if it were a required file at that point. Then the whole program, generated code included, is checked and run.
# gen.sake: a reader per line of cols.txt ("name", "age")
Array.each_with_index(String.lines(File.read("cols.txt"))) do |c, i|
puts "# sake-origin: cols.txt:#{i + 1}"
puts "def #{String.strip(c)}(row) = row[#{Kernel.inspect(String.strip(c))}]"
end
# main.sake
generate "gen"
p(name(Hash.of("name" => "ko1")))
- Every run. The generator runs at every start, so the generated code is never stale, and it is always checked. It cannot see the program it generates for. The same generator with the same arguments runs once.
- Like
require. The arguments are string literals, andgenerateis a statement at the top level of a file (elsewhere,generate(...)is an ordinary call). The generator is found asrequirefinds a file. - Where errors point. Generated code is named
(generate at main.sake:2). A line# sake-origin: FILE:LINEwritten by the generator makes the lines after it, up to the next such line, report as FILE:LINE (herecols.txt:1), in static errors, runtime errors and backtraces;# sake-origin: noneends it. A problem in the generator itself, or an error it ends with, is reported at thegenerateline, and nothing runs. - Seeing it.
bin/sake --dump=generated main.sakeprints the generated source.
2.1.4 Namespaces: class and module
class Name ... end and module Name ... end open a namespace. class declares a type (a class) or adds operations to an existing one, including a built-in type such as String. module is a namespace with no type.
The body of a namespace may contain only the following.
def name(...)(no receiver)include Modulemodule_function(in a module; see “Calling a module’s functions” below)- a nested
class/module, orName = Struct.new(...) - a type alias
Name = A::B(see “Type aliases” below) - in a class,
attr_reader/attr_accessor/attr_writerlines (Classes)
Any other statement in a body is a static error. module on a type is a static error too, with a hint that gives class.
```ruby error
module String # !> module String: String is a type; add operations to a type with class
def shout(s) = String.upcase(s)
end
### Namespaces nest
`class B` (or `module B`, or `B = Struct.new(...)`) written inside `module A` declares `A::B`. `class A::B` at the top level opens the same namespace, so either spelling adds operations to the same class.
```ruby
module Geo
Point = Struct.new(:x, :y)
class Point
def norm2(p) = @x * @x + @y * @y
end
module Util
module_function
def origin = Point.new(0, 0) # Point here is Geo::Point
end
end
class Geo::Point # the Ruby spelling opens the same class
def to_s(p) = "(#{@x}, #{@y})"
end
pt = Geo::Point.new(3, 4)
p(Geo::Point.norm2(pt)) # => 25
p(pt.Geo::Point.norm2) # => 25
puts(Geo::Util.origin) # => (0, 0)
p((pt in Geo::Point)) # => true
- The full name outside. Write
A::B.f(x),x.A::B.f,include A::M,class C < A::B,x in A::B,(A::B|C).f(x),rescue A::E. - The short name inside. Inside
A, a bareBmeansA::B. As in Ruby, the lookup starts at the innermost namespace, and falls back to the top-levelB. - No
def A::B.f. It is not Ruby syntax. A module function of a nested module is defined inside it (module A::B+module_function). - A namespace is not a value.
p(A::B)is an error, andA::Balone has no meaning.
```ruby error
module Geo
Point = Struct.new(:x, :y)
end
p(Geo::Point) # !> type Geo::Point cannot be used as a value
### Type aliases
`Client = Handshake::Client` gives another name to an existing type: a class, a module, or a built-in type (`Str = String`). The alias is a constant, with Ruby's scope: it belongs to the namespace it is written in (the top level, or a `class` / `module` body), it is found as a type name is (the innermost namespace first, then outward, then the top level), and from outside it is `WebSocket::Client`. The right-hand side is a constant path, resolved where the alias is written; it may be another alias.
```ruby
module WebSocket
module Handshake
class Client
attr_reader url
def host(c) = String.split(@url, "/")[2]
end
end
Client = Handshake::Client # WebSocket::Client
module Session
module_function
def open(url) = Client.new(url) # Client here is WebSocket::Client
end
end
C = WebSocket::Client # an alias of an alias
c = WebSocket::Session.open("ws://example.com/chat")
p(C.host(c)) # => "example.com"
p(c.WebSocket::Client.host) # => "example.com"
p((c in WebSocket::Handshake::Client)) # => true
- A name, not a new type.
Client.new,Client.f(x),x.Client.f,def Client.f,include,class C < Client,in Client,rescue Client,raise ClientandClient::Innerall mean the type it names. Messages name that type; an undefined function’s hint says which type the alias names. - One name per namespace. An alias whose name is already a type or an alias of its namespace is a static error, as a duplicate definition is. A class is not defined through an alias (
class Client::Inner): use the type’s own name. - Only types. A right-hand side that names no type, and a cycle of aliases, are static errors.
Pi = Math::PIstays a value constant (an error with the hintdef pi = Math::PI).
```ruby error
module Net
class Conn
end
Socket = Net::Con # !> Socket = Net::Con: Net::Con is not a type (a constant set to a constant is another name for a class or a module)
end
### Classes do not inherit
For any class, `class B < A` is shorthand for writing A's definitions in B. Afterwards A and B are unrelated types, and `A.f` takes only A's values ([Classes](07-classes.md)).
```ruby
class A
attr_reader x
def double(a) = @x * 2
end
class B < A
attr_reader y
end
b = B.new(1, 2)
p(B.double(b)) # => 2
p((b in A)) # => false
```ruby error class A attr_reader x def double(a) = @x * 2 # !> A.x: argument 1 must be A, but is B [type] end class B < A attr_reader y end p(A.double(B.new(1, 2)))
### No `self`
`def self.x` is rejected, because Sake has no `self`. A `def x` inside a class already defines `A.x`.
```ruby error
class A
attr_reader x
def self.make = A.new(1) # !> `def self.make` inside `A`: write `def make` (it defines A.make)
end
2.1.5 One definition per name
Defining the same name twice in one namespace is an error. Redefining a built-in operation is also an error.
```ruby error
def f = 1
def f = 2 # !> f is already defined at line 1
```ruby error
def String.upcase(s) = s # !> `String.upcase` is a built-in operation and cannot be redefined
2.1.6 No value constants
Constants can only be assigned from Struct.new (or Exception.new), or a type’s name (a type alias, above). For a named value, define a function (def pi = 3.14159) and call it (pi). PI = 3.14 is a static error whose hint gives that function, and each use of PI gets the hint to call pi. once { ... } computes a value once (Built-in operations).
def pi = 3.14159
p(pi * 2) # => 6.28318
```ruby error
PI = 3.14 # !> Sake has no value constants; a constant names a class (Point = Struct.new(:x, :y), Err = Exception.new(:msg)), and a value goes in a function
p(PI * 2) # !> PI is not defined (Sake has no value constants)
## Qualified calls
`Type.op(args...)` calls the operation `op` of namespace `Type`. By convention the subject is the first argument. Both `Type` and `op` must exist, or the program is rejected before running. The error offers spelling suggestions and lists other namespaces that define `op`.
```ruby error
puts(String.upcse("a")) # !> undefined function `String.upcse`
```ruby error
puts(Strng.upcase(“a”)) # !> undefined type or module Strng
```ruby error
puts(Integer.upcase("a")) # !> undefined function `Integer.upcase`
The hint of the last one is `upcase` is defined in `String.upcase`, `Symbol.upcase`.
2.2 Chains and _
Two forms let a sequence of operations read from left to right, top to bottom. In both, the type of each operation is found on the same line.
2.2.1 Chains
x.T.f(args...) is T.f(x, args...): the step .T names a type or module, and the value to its left becomes the first argument. Steps can follow one another, also with the dot at the start of the next line.
A chain starts with T.f(x). A step after it is written .g when the value has the same type, and .U.g when the type changes (Steps without a type). Starting from a variable, x.T.f, is allowed but not recommended: the type’s name sits in the middle of the line and is hard to read, and the checker’s hints do not suggest it.
line = "a, b ,c"
r = String.split(line, ",")
.Array.map { |s| String.strip(s) }
.join("|")
p(r) # => "a|b|c"
2.2.2 Steps without a type
A chain step written without a type continues with the type the chain names last. String.strip(s).upcase is String.strip(s).String.upcase, and the step after it continues with String too. The type is written only where the chain starts and where the value’s type changes.
xs = Any[3, 1, 2]
p(Array.sort(xs).reverse.first(2)) # => [3, 2]
p(String.strip(" ab ").upcase.reverse) # => "BA"
words = Any["b", "a", "b"]
p(Array.tally(words).Hash.keys.Array.sort) # => ["a", "b"]
In the last one, Array.tally gives a Hash, so the step names .Hash.keys, and Hash.keys gives an Array, so the next one names .Array.sort.
The type a step continues with is found by reading left on the same line, not chosen by the value’s type. A variable or a literal receiver names no type, so x.f stays a static error.
- If the type has no such operation, it is a static error. The hint names the types that have one.
- If the value is not of that type, it is a type error, as
T.f(r)would be. The hint names the value’s type.
```ruby error counts = Hash.of(“a” => 2, “b” => 1) p(Hash.sort_by(counts) { |k, n| n }.first) # !> Hash.first: argument 1 must be Hash, but is Array
`Hash.sort_by` gives an Array, so the step names it: `.Array.first`.
`x.T` with no operation after it is a static error.
```ruby error
s = "x"
p(s.String) # !> `s.String` needs an operation after the type: `s.String.op(...)`
2.2.3 _
_ is the value of the previous statement in the same body (a function, a block, a branch, or the top level).
line = "a, b ,c"
String.split(line, ",")
Array.map(_) { |s| String.strip(s) }
Array.join(_, "|")
p(_) # => "a|b|c"
_ refers to a statement, not an expression, because a statement is unambiguous. An expression-level _ would have to pick one of the sub-expressions of the line before, and there is no natural answer to which one. “The statement before” is one thing.
- Not in a first statement. Reading
_in the first statement of a body, or right after a definition, is a static error. - Parentheses and interpolation. In parentheses and in string interpolation, the first statement reads the
_of the enclosing statement. _as a name._may still be bound as a name to ignore (|_, v|,a, _ = t). But a_that names a local variable cannot be read.
String.upcase("sake")
puts("got #{_}") # => got SAKE
Any[1, 2]
p((Array.size(_) + 1)) # => 3
```ruby error
def f(x)
p(_) # !> _ is the previous statement’s value, and no statement precedes it here
end
f(1)
```ruby error
a, _ = [1, 2]
p(_) # !> `_` is the previous statement's value, but here it names a local variable
2.3 No calls on values
A call with a lowercase receiver, such as x.op(...), "lit".op, or 3.times, is a static error. The error suggests the qualified form, and when the receiver is a call that names no type (f(x).op) also the chain form (f(x).T.op(...)). A call on a call that names a type is not an error: it is a step that continues with that type.
```ruby error
s = “ a “
puts(s.strip.upcase) # !> method call on a value s.strip.upcase is not allowed
- **Chains** are rewritten as a whole: `s.strip.upcase` suggests `String.strip(s).upcase`, the first step as `T.f(x)`, and the steps after it without the type when it stays the same, with it when it changes.
- **Field access** gets the reader: `p.x` suggests `Point.x(p)`, and `p.x = v` suggests `p.Point.x = v`. With a setter function, write `Point.set_x(p, v)`.
- **Literal receivers** narrow the suggestions to the literal's type: `"lit".upcase` suggests only `String.upcase("lit")`.
- **`x.nil?`** suggests `x == nil`.
```ruby error
Point = Struct.new(:x, :y)
pt = Point.new(1, 2)
p(pt.x) # !> method call on a value `pt.x` is not allowed
```ruby error
3.times { puts(“hi”) } # !> method call on a value 3.times is not allowed
## Unqualified calls
A call without a receiver, `f(args)`, is resolved before running. The search goes in this order, and the first match wins.
1. The enclosing class or module, including its built-in operations, field readers and writers, and the functions it includes. Only the innermost one: inside `A::B`, a function of `A` is called as `A.f(...)` (names of types, unlike functions, are looked up outward).
2. Top-level functions.
3. `Kernel` (`puts`, `print`, `p`).
```ruby
def size(x) = "top-level size"
class Box
attr_reader items
def size(b) = Array.size(@items)
def describe(b) = "#{size(b)} items" # Box.size
end
b = Box.new(Any[1, 2])
puts(Box.describe(b)) # => 2 items
puts(size(b)) # => top-level size
- Shadowing. An inner definition shadows an outer one. It is not an error for the same name to exist at several levels.
- The outer one, qualified. An outer definition can always be reached with its namespace (
Kernel.puts). - Top level. Code at the top level has no enclosing namespace, so resolution starts at step 2.
class Logger
attr_reader prefix
def puts(l, msg) = Kernel.puts("#{@prefix}#{msg}")
def run(l) = puts(l, "start") # Logger.puts
end
Logger.run(Logger.new("> ")) # => > start
puts("done") # => done
2.4 include
include M in a class or module body copies the functions of the module M into the includer. This is Ruby’s module, resolved statically.
module Summary
def total(x) = Array.inject(items(x), 0) { |a, v| a + v }
def report(x) = "total=#{total(x)}"
end
class Basket
include Summary
attr_reader items
end
b = Basket.new(Any[1, 2, 3])
puts(Basket.report(b)) # => total=6
puts(Summary.total(b)) # => 6
2.4.1 Copying
Each function f of M becomes X.f in the including namespace X. If X already has f, X’s own definition wins. Modules included by M are copied too.
module A
def bar(x) = "A"
def baz(x) = "A"
end
class C
include A
attr_reader v
def bar(x) = "C"
end
p(C.bar(C.new(1))) # => "C"
p(C.baz(C.new(1))) # => "A"
2.4.2 Order
Modules are searched in Ruby’s ancestor order: the module included last comes first. With include A then include B, a bar in both comes from B.
module A
def bar(x) = "A"
end
module B
def bar(x) = "B"
end
class C
include A
include B
attr_reader v
end
p(C.bar(C.new(1))) # => "B"
2.4.3 Resolution in the includer
Inside a copied function, unqualified names are resolved in X. So M can use functions that X provides, such as each. Nothing is dispatched at run time: each X.f is fixed before running. @x refers to a field of X when X is a class.
module Named
def label(x) = "<#{@name}>"
end
class Cat
include Named
attr_reader name
end
puts(Cat.label(Cat.new("tama"))) # => <tama>
2.4.4 Requirements
The unqualified names that the bodies of M’s functions call (each(x), say) and the fields @x they read, minus what M itself, the top level and Kernel define, are M’s requirements. Nothing is declared; they are collected from the bodies.
- Missing in the includer. If
Xlacks one (noeach, no fieldx),include Mis a static error. It names the function and what it needed. - Called directly. Calling a function with requirements directly, as in
M.total(x), without going through an includer, is a static error too. The hint gives the call through an includer. This concerns a module functiontotal; for a mixin function,M.total(x)is the dispatch of the next section.
```ruby error
module Summary
def total(x) = Array.inject(items(x), 0) { |a, v| a + v }
end
class Empty
include Summary # !> include Summary in Empty: Summary.total needs items, which Empty does not define (used at line 2)
attr_reader v
end
```ruby error
module Summary
module_function
def total(x) = Array.inject(items(x), 0) { |a, v| a + v }
end
class Basket
include Summary
attr_reader items
end
puts(Summary.total(Basket.new(Any[1]))) # !> Summary.total needs `items` from a namespace that includes Summary
2.4.5 Restrictions
- Only modules can be included. Including a class is a static error.
- Include cycles are errors.
- Not inheritance.
includeadds no subtype relation. A value of a type that includesMdoes not become a value ofM.
```ruby error
class A
attr_reader x
end
class B
include A # !> include A: A is not a module
attr_reader y
end
## Calling a module's functions: module_function and dispatch
A name in a type or module is one function: Sake has no `def self.f` next to `def f`, because the "instance" form only means that the value comes first (`T.f(x, ...)`). Where Ruby has both a class method and an instance method of one name (`Net::HTTP.get(uri)` and `http.get(path)`), one of them takes another name, or one function tells the forms apart by the type of its first argument.
A module's functions are of two kinds, as in Ruby.
| Kind | How it is declared | `M.f(args)` |
|---|---|---|
| module function | after `module_function`, by `module_function :f`, or with `def M.f` | runs `M`'s own `f` (static) |
| mixin function | any other function of a module | **dispatches**: runs the `f` of the first argument's type, which must include `M` |
```ruby
module Describe
def describe(x) = "#{prefix}#{name(x)}" # a mixin function
def prefix = "* "
module_function :prefix # a module function
end
class Cat
include Describe
attr_reader name
end
class Dog
include Describe
attr_reader name
end
p(Describe.prefix) # => "* "
puts(Describe.describe(Cat.new("tama"))) # => * tama
puts(Describe.describe(Dog.new("pochi"))) # => * pochi
puts(Cat.describe(Cat.new("tama"))) # => * tama
2.4.6 No subject
A mixin function cannot be called without arguments (M.foo), because there is nothing to dispatch on. Calling one when no type includes M is also a static error, with a hint to use module_function.
```ruby error module Greeting def hello(x) = “hello” end puts(Greeting.hello(1)) # !> Greeting.hello is a mixin function, and no type includes Greeting
### Type check
The type inference checks that the first argument's possible types all include `M`, and reports one that does not as a `type` problem. While running, a type that does not include `M` raises `TypeError`.
```ruby error
module Describe
def describe(x) = "#{name(x)}"
end
class Cat
include Describe
attr_reader name
end
Dog = Struct.new(:name)
def pick(flag) = flag ? Cat.new("tama") : Dog.new("pochi")
puts(Describe.describe(pick(true))) # !> Describe.describe dispatches on its first argument, which can be Dog; the types that include Describe are Cat [type]
2.4.7 Static calls
A type’s own namespace is always static. In the example above, Cat.describe(c) runs Cat’s describe (copied from Describe); Describe.describe(x) is the dispatching form.
2.4.8 Kernel
Every type includes Kernel. Kernel.to_s(x) and Kernel.inspect(x) dispatch to a type’s own to_s and inspect, or use the built-in form. "#{x}", puts, and p are shorthands for them.
class Temp
attr_reader deg
def to_s(t) = "#{@deg} C"
end
t = Temp.new(20)
puts(t) # => 20 C
puts("now #{t}") # => now 20 C
puts(Kernel.to_s(t)) # => 20 C
p(Kernel.to_s(5)) # => "5"
2.4.9 Required functions
A function of a module whose body is only raise NotImplementedError (with or without a message) is required: each type that includes the module defines it, and M.f(x) dispatches to that definition. Whether it takes a block comes from the types’ definitions.
module Shape
def area(s) = raise NotImplementedError
def describe(s) = "area=#{area(s)}"
end
class Sq
include Shape
attr_reader side
def area(s) = @side * @side
end
puts(Shape.describe(Sq.new(3))) # => area=9
puts(Shape.area(Sq.new(2))) # => 4
- A type that does not define it. A type that includes the module without defining it is a
typeproblem wherever a call can reach it, andNotImplementedErrorwhile running. - Not rescued.
NotImplementedErroris a program error and cannot be rescued. - Message.
raise Twith an exception type and no message gives the type’s name as the message.
```ruby error
module Shape
def area(s) = raise NotImplementedError
def describe(s) = “area=#{area(s)}” # !> Shape.area: Blob includes Shape but does not define area, which Shape requires (raise NotImplementedError) [type]
end
class Blob
include Shape
attr_reader v
end
puts(Shape.describe(Blob.new(1)))
## Listing the types: `(A|B).f(x)`
`(A|B).f(x, args...)` lists, on the operation itself, the types that `x` may have. While running, the type of `x` picks `A.f` or `B.f`; any other type raises `TypeError`. The result is the union of the results.
```ruby
Leaf = Struct.new(:weight)
Node = Struct.new(:weight, :kids)
def weight(t) = (Leaf|Node).weight(t) # a field of the same name in two classes
p(weight(Leaf.new(2))) # => 2
p(weight(Node.new(5, Any[]))) # => 5
def size_of(x) = (String|Array|Hash).size(x)
p(size_of("abc")) # => 3
p(size_of(Any[1, 2])) # => 2
Two things are checked before running.
- Every listed type has
f. Otherwise it is a static error. - No unlisted type arrives. The checker reports a value of
xwhose type is not listed as atypeproblem.
```ruby error
def shout(x) = (String|Integer).upcase(x) # !> Integer has no upcase, so (String|Integer).upcase cannot dispatch to it
p(shout(“abc”))
```ruby error
def size_of(x) = (String|Array).size(x) # !> (String|Array).size: argument 1 must be String or Array, but is Integer [type]
p(size_of(5))
- The list names types. Built-in types (
IOamong them) and classes, each at most once.nilcannot be listed; check for nil first. - The shapes may differ. The listed functions may differ in shape: a user function’s
*restis packed for its branch ((IO|StringIO).print(io, a, b)). A function that takes keywords cannot be listed; dispatch on the type withcase. - Relation to mixin dispatch. A mixin function’s dispatch
M.f(x)is the same selection as(A|B|...).f(x)listing every class that includesM. The difference is where the set is stated:includedeclares it on each class,(A|B)states it at the call. So nothing has to be declared, and it covers types that share an operation’s name without sharing a module, such as built-in types or same-named fields.
2.5 Arity and blocks
Since the callee is decided before running, the number of arguments and the presence of a block are checked before running.
- Argument count. Checked for both user functions and built-in operations.
- A block to a user function. A block is passed iff the function contains
yield(or takes&b), except that a function that checksblock_given?may be called without one. - A block to a built-in. A built-in operation either requires a block or rejects one.
```ruby error def area(w, h) = w * h p(area(3)) # !> wrong number of arguments for area (given 1, expected 2)
```ruby error
def twice(x) = x * 2
p(twice(3) { 1 }) # !> twice does not take a block (it has no `yield`)
```ruby error p(Array.map(Any[1])) # !> Array.map requires a block
### Splat arguments
`*xs` spreads a Tuple or an Array; anything else raises `TypeError`. It goes only into a rest parameter: a built-in's (`puts(*lines)`, `format(fmt, *row)`, `Any[*xs, 0]`, `Set.of(*xs)`, `Array.push(a, *xs)`) or a user function's `*rest` (`join(*parts)`, [Functions and blocks](04-functions.md)).
```ruby
def join(sep, *parts) = Array.join(parts, sep)
xs = Any["a", "b"]
p(join("-", *xs)) # => "a-b"
t = [1, 2]
p(Any[*t, 0]) # => [1, 2, 0]
puts(*xs) # => a
# => b
- The arguments before it are written out. So their count is still checked.
- Static errors. A splat into a function without
*rest, or one that would fill a required or optional parameter, is a static error. So are splats intoArray.zip/Range.zip(their Tuples are as long as the argument list) andHash.of(...). - In the typer. A Tuple spreads position by position; an Array stands for any number of arguments of its element types.
ruby error
def two(a, b) = a + b
t = [1, 2]
p(two(*t)) # !> `*t`: splat arguments go only to a `*rest` parameter or to built-ins that take any number of arguments (puts, format, Any[...], Set.of, Array.push, ...)
3. Values and types
Sake’s values look much like Ruby’s, but every value carries one type tag, and every operation checks it. This chapter lists the types and how each is written. Where the spelling is Ruby’s but the meaning differs: [a, b] is a Tuple, {x: 1} is a Record, and {} and {k => v} are static errors.
| Type | How it is written (a literal, or the operation that makes it) | Notes |
|---|---|---|
| Integer | 42, -7 |
arbitrary precision (the interpreter; the width is the implementation’s: the C and Rust generators use 64 bits, and an overflow is an error) |
| Float | 1.5, 2.0 |
|
| String | "abc", 'abc', "a#{x}" |
interpolation uses to_s (see “Showing values”) |
| true / false | true, false |
internally one type, Boolean, which cannot be written in source |
| nil | nil |
see “nil” below |
| Tuple | [a, b, ...] |
length and positional types fixed at creation |
| Record | {x: a, y: b} |
set of (field, type) pairs fixed at creation |
| Array | Any[a, ...] |
no declared element type |
| Array of T | T[a, ...], e.g. Float[], Point[p] |
element type T, checked on every write |
| Hash | Hash.of("a" => 1, b: 2), Hash.new(0) |
keys compare by value and type (Ruby’s eql?) |
| Set | Set.of(1, 2) |
elements compare by value and type (Ruby’s eql?) |
| Symbol | :name |
|
| Range | 1..5, 1...5, 1.. |
ends are Integer, Float, String, or nil |
| Regexp, MatchData | /(\d+)-(\d+)/, /#{x}/, String.match(s, re) |
|
| Rational, Complex | 2r, 1/3r, Rational(1, 3), 2i, Complex(1, 2) |
Ruby’s numeric tower |
| Time | Time.now, Time.at(0), Time.new(2026, 10, 1) |
|
| IO | IO.stdout, File.open(path) |
one type for files and streams |
| Thread, Mutex, Queue, TCPServer, Socket | Thread.new { }, Mutex.new, … |
concurrency and networking |
| class | Point.new(x, y) |
record with mutable fields (Classes) |
- Truthiness. Only
nilandfalseare falsy. Every other value, including0and"", is truthy. - Error messages.
nil,true, andfalseare shown as values (got nil). Every other value is shown by its type name (got Integer).
p(0 ? "yes" : "no") # => "yes"
p("" ? "yes" : "no") # => "yes"
p(nil ? "yes" : "no") # => "no"
p(false ? "yes" : "no") # => "no"
3.1 Showing values
Every value can be shown in two forms. to_s is the form for a reader, inspect the form that shows a programmer the kind of value as well; they are used as in Ruby.
| Form | Used by | Built-in form |
|---|---|---|
to_s |
puts, print, "#{x}", Array.join, format’s %s, :"#{x}", /#{x}/ |
as Ruby’s to_s: nil shows as empty, Arrays and Tuples as inspect |
inspect |
p, Kernel.inspect, format’s %p, and elements inside an Array, Tuple, Hash, or Record |
as Ruby’s inspect: #<struct Point x=1, y=2> |
Point = Struct.new(:x, :y)
puts("a") # => a
p("a") # => "a"
puts("#{nil}|") # => |
puts("#{[1, "a"]}") # => [1, "a"]
puts(format("%s %p", "a", "a")) # => a "a"
p(Point.new(1, 2)) # => #<struct Point x=1, y=2>
3.1.1 Your own form
A class can define its own to_s and inspect in its body. Each takes one required argument, the value, and must return a String. Parameters with defaults may follow (to_s(n, base = 10)).
class Point
attr_accessor x, y
def to_s(p) = "(#{@x}, #{@y})"
def inspect(p) = "Point(#{@x}, #{@y})"
end
pt = Point.new(1, 2)
puts(pt) # => (1, 2)
puts("at #{pt}") # => at (1, 2)
p(pt) # => Point(1, 2)
p([pt]) # => [Point(1, 2)]
A non-String result is reported before running as a type problem. If it is reached while running, it is a TypeError.
```ruby error class Point attr_accessor x, y def to_s(p) = @x end puts(Point.new(1, 2)) # !> Point.to_s must return a String, but returns Integer [type]
- **Which one runs.** The set of types is closed, so which `to_s` runs is known whenever the value's type is.
## Tuples, Records, and arrays
The literals `[...]` and `{...}` have no operation with a type, so their **shape** fixes their type at creation: `[a, b]` is a Tuple and `{x: a, y: b}` is a Record. A growable collection is made by an operation with a type (`Any[...]`, `T[...]`, `Hash.of(...)`).
### What a literal is
- **Contents are mutable.** The contents of a literal may be replaced by values of the same type, and each write is checked. Tuples are written with `t[i] = v`. Records have no write syntax yet.
- **Length is fixed.** The length is part of the type, so an empty `[]` cannot grow.
- **nil.** A position created with `nil` has the type nil, and no other value can be stored there later. To leave room for a value, create it with a placeholder of the intended type, such as `0` or `""`.
```ruby error
xs = []
Array.push(xs, 1) # !> `[...]` is a Tuple with a fixed length; for a growable Array, write `Any[...]`
```ruby error t = [1, nil] t[1] = 2 # !> Indexable.[]=: the value must be Nil, but is Integer [type]
### Tuple
The literal `[a, b, ...]` is a Tuple. Its elements are read by multiple assignment, `x, y = t`, or by index, `t[0]`.
```ruby
t = [1, "a"]
x, y = t
p(x) # => 1
p(y) # => "a"
t[0] = 2
p(t) # => [2, "a"]
p(Tuple.size(t)) # => 2
p(Tuple.max([3, 9, 4])) # => 9
- Multiple assignment.
x, y = ttakes a Tuple apart. It also takes an Array apart (key, value = String.split(s, "=")). As in Ruby, missing elements are nil and extra elements are dropped. - An element that may be missing. A variable that may get one has the type
nil | T, asx[k]does (theindex-nilitem, level 3). - Targets. Elements and fields may be targets too:
a[i], a[j] = a[j], a[i]swaps, and@done, @rest = Array.partition(xs) { ... }assigns to fields. Their receivers and indexes are evaluated first, then the right side. - The rest.
first, *rest = xsputs the remaining elements in a new Array.a, *mid, z = xsdoes the same, andx, * = xsdrops them. Only a local variable takes the rest. - A splat inside
[...]. A Tuple’s length must be known, so[*xs, 1]is rejected.Any[*xs, 1]makes an Array. - Indexing.
t[0]reads a position andt[0] = vreplaces it with a value of the same type. - Operations.
Tuple.sizeandTuple.lengthgive the number of elements.Tuple.max([a, b])is Ruby’s[a, b].max; the length is known, so the result is never nil.
first, *rest = Any[1, 2, 3]
p(first) # => 1
p(rest) # => [2, 3]
key, value = String.split("a=1", "=")
p(value) # => "1"
a = Any[1, 2]
a[0], a[1] = a[1], a[0]
p(a) # => [2, 1]
3.1.2 Record
The literal {x: a, y: b} is a Record: a small fixed group of values read by field name.
- Type. The type of a Record is its set of (field, type) pairs, for example
{x: Integer, y: Integer}. Two Records with the same set have the same type. - Field order. Order does not matter:
{y: 2, x: 1}is printed as{x: 1, y: 2}, and the two are==. - Not a class value. A Record is never a value of a class, even when the fields match: classes are nominal, and Record types are structural.
- Reading fields. Take fields apart with a pattern,
r => {x:, y: name}. This binds the localxto fieldxand the localnameto fieldy. Listing only some of the fields is allowed.Record.to_h(r),Record.keys(r), andRecord.values(r)exist too. - A missing field. It is reported before running at
type. If it is reached while running, a missing field raisesKeyError, and a value that is not a Record raisesTypeError. - Restrictions. Field names are written as labels (
x:). An empty{}and{key => value}are static errors (a Hash isHash.of(...)).
Point = Struct.new(:x, :y)
r = {y: 2, x: 1}
p(r) # => {x: 1, y: 2}
p(r == {x: 1, y: 2}) # => true
p(r == Point.new(1, 2)) # => false
r => {x:, y: name}
p(x) # => 1
p(name) # => 2
p(Record.keys(r)) # => [:x, :y]
```ruby error
r = {x: 1}
r => {z:} # !> the pattern needs field z, but the value is {x: Integer} [type]
```ruby error
h = {"a" => 1} # !> `{"a" => ...}` is not a Hash in Sake: `{name: value}` makes a Record
3.1.3 Array
Any[a, ...] creates an Array with no declared element type. Any value can be added to it. Arrays are mutable and shared by reference. Any is the prelude’s name for Kernel (Any = Kernel): every type includes Kernel, so its Array takes every value. Array[...] is not this Array: like T[...] for every T below, it is an Array of Arrays.
3.1.4 Array of T
T[a, ...] creates an Array whose element type is T, for every T. T is a built-in type (Integer, Float, Rational, Complex, String, Symbol, Tuple, and Array, Hash, Set, whose [...] makes an Array of Arrays, of Hashes, of Sets), a class, or a module. A Hash itself is made with Hash.of(k => v) or Hash.new, a Set with Set.of(a, b) or Set.new(xs). A module’s Array M[...] holds values of the types that include M, so a mixin function M.f(x) runs on each element.
- Write checks. Every write is checked: creation,
Array.push,Array.append, andArray.concat. A mismatch raisesTypeError. There is no implicit conversion, so an Integer cannot go intoFloat[]. - Static check. If a literal argument of
T[...]has another type, the error is reported before running. This catchesPoint[1, 2], Ruby’s spelling ofPoint.new(1, 2). - Untyped results. Arrays returned by
map,select,sort, and similar operations have no declared element type.
```ruby error fs = Float[1.5] Array.push(fs, 2.5) Array.push(fs, 3) # !> Array.push: an element must be Float, but is Integer [type]
```ruby error
Point = Struct.new(:x, :y)
pt = Point[1, 2] # !> Point.new(1, 2) creates one Point; Point[...] is an Array of Point
Point = Struct.new(:x, :y)
pts = Point[Point.new(1, 2), Point.new(3, 4)]
xs = Array.map(pts) { |pt| Point.x(pt) }
Array.push(xs, "s")
p(xs) # => [1, 3, "s"]
module Shape
def area(s) = raise NotImplementedError
end
class Sq
include Shape
attr_reader n
def area(s) = @n * @n
end
class Rect
include Shape
attr_reader w, h
def area(s) = @w * @h
end
shapes = Shape[Sq.new(2), Rect.new(2, 3)]
p(Array.map(shapes) { |s| Shape.area(s) }) # => [4, 6]
3.1.5 Ruby habits
[]where an Array is expected. A Tuple or Record passed where an Array is expected, such asresult = []followed byArray.push(result, x), fails with a hint to writeAny[](the example above).- In-place operations that change the element type. The checker gives a container one element type for the whole program. So after
Array.map!(xs) { |x| Integer.to_s(x) }orHash.transform_keys!(h) { ... }it takes the container to hold both the old and the new type, and reports operations that fit only one of them astype(partial). Use the in-place forms for a mapping within one type, andArray.map/Hash.transform_keys(a new container) to change it.
```ruby error xs = Any[1, 2] Array.map!(xs) { |x| Integer.to_s(x) } p(Array.sum(xs)) # !> Array.sum: an element must be Integer|Float|Rational|Complex, but can be String [type]
## Hash and Set
A Hash and a Set are growable collections that find a key or an element by its value. The literal `{...}` is a Record, so both are made by an operation with a type.
### Constructors
`Hash.of(k => v, ...)`, `Hash.new`, and `Hash.new(default)` create a Hash; `Set.of(x, ...)` creates a Set. The keys of `Hash.of(name: v)` are Symbols.
```ruby
h = Hash.of("a" => 1, b: 2)
p(h) # => {"a" => 1, b: 2}
counts = Hash.new(0)
Array.each(String.split("the cat the", " ")) { |w| counts[w] += 1 }
p(counts) # => {"the" => 2, "cat" => 1}
Hash.each(counts) { |k, v| puts("#{k}: #{v}") } # => the: 2
# => cat: 1
3.1.6 Comparing keys and elements
Hash keys and Set elements compare as Ruby’s eql? does: by value, with the type included. 1 and 1.0 are different keys, while 1 == 1.0 is true.
p(Hash.of(1 => "a")[1.0]) # => nil
p(Set.of(1, 1.0)) # => Set[1, 1.0]
p(1 == 1.0) # => true
- Allowed. Integer, Float, String, Symbol, true, false, nil, Time, and Tuples, Records, Arrays, Hashes, Sets, and values of a class made of these.
- Not allowed (
TypeError). Regexp, Range, and values of a class that defines its own equality (==, orComparablewith<=>), because the key comparison could disagree with that equality. - Changing a key. As in Ruby, changing an Array, Hash, Set, or class value after using it as a key makes it unfindable.
- Keys are copied. A Tuple or Record key is copied when it is stored, so a later write to the original does not change the key.
```ruby error h = Hash.of(/a/ => 1) # !> Regexp cannot be a Hash key or Set element
```ruby
k = [1, 2]
h = Hash.of(k => "a")
k[0] = 9
p(Hash.keys(h)) # => [[1, 2]]
3.1.7 Default values
Hash.new(default) gives default for a missing key, as in Ruby, and the same object is shared, as in Ruby. The block form Hash.new { ... } is not available, because blocks are not values.
h = Hash.new(Any[])
Array.push(h[:a], 1)
p(h[:b]) # => [1]
```ruby error h = Hash.new { |hh, k| 0 } # !> Hash.new does not take a block
### Blocks and order
- **Blocks.** A block over a Hash receives `[key, value]` as one Tuple, so `|k, v|` takes it apart (the `Hash.each` example above).
- **Order.** Iteration follows insertion order, as in Ruby.
## nil
`nil` is an ordinary value. A value that may be absent has the type `nil | T`, and there is no Option wrapper. Before such a value goes to an operation that cannot take nil, a check removes nil from its type.
### Run time
An operation that receives `nil` where it needs T raises `TypeError ... got nil`. The interpreter then runs the static analysis, on the error path only, and adds hints naming the fields that may hold `nil` and the lines that store it.
```ruby error
h = Hash.of("a" => "x")
puts(String.upcase(h["b"])) # !> TypeError: String.upcase: argument 1 must be String, got nil
3.1.8 Narrowing
The static analysis narrows a local variable in the following places.
| Form | Where x is narrowed |
|---|---|
if x / while x / x && … |
non-nil in the branch taken when x is truthy |
x != nil / x == nil |
nil or non-nil in the matching branch |
!x / unless x |
the same, with the branches swapped (if !x … else is non-nil in the else) |
return unless x, next unless x, break unless x, and other early exits |
non-nil after the statement |
String.size(x), or any built-in operation taking x as an argument |
after the call, a type that the operation accepts (it checks its arguments while running) |
x in T / x => T / case x in T |
the matching types (Control flow and patterns) |
def find_even(xs) = Array.find(xs) { |x| x % 2 == 0 }
x = find_even(Any[1, 4])
if x
p(x + 1) # => 5
end
p(x + 1) if x != nil # => 5
def twice(xs)
x = find_even(xs)
return 0 unless x
x * 2
end
p(twice(Any[1, 4])) # => 8
p(twice(Any[1, 3])) # => 0
A field read from a local variable (Node.next(n), or @next in a function of the class) is narrowed too. But an operation that may change the field, between the test and the use, ends the narrowing there, because fields are mutable and another name may change them. Those operations are a field write, a call of a user function, a call that passes a block and yield, and an assignment to the variable. Inside a block, a field narrowed outside it is not narrowed.
```ruby error class Node attr_accessor value, :next end def mk(v) = Node.new(v, nil) def drop_next(n) = Node.set_next(n, nil) a = mk(1) Node.set_next(a, mk(2)) if Node.next(a) p(Node.value(Node.next(a))) # narrowed drop_next(a) p(Node.value(Node.next(a))) # !> Node.value: argument 1 may be nil (nil | Node) [nil] end
When a call comes in between, copy the field into a local variable first, then test the local.
```ruby
class Node
attr_accessor value, :next
end
def mk(v) = Node.new(v, nil)
a = mk(1)
Node.set_next(a, mk(2))
nx = Node.next(a)
if nx
p(Node.value(nx)) # => 2
end
3.1.9 --strict
Level 2 reports, before running, every operation that may receive an unchecked nil. Level 3 also covers results of x[k] (Overview and running programs).
3.2 Ruby’s other types
Ruby’s other types are made with the same spelling and give the same values as in Ruby. The differences are that operations are called with their type, and that there are no global variables ($1, $stdout).
3.2.1 Range
Operations that iterate need a Range that starts with an Integer or a String. "A".."Z" walks with String#succ, as Ruby’s does. step, sum, and size need an Integer. The checker reports a Range of another type (1.0..2.0) at type.
p(Range.to_a("a".."e")) # => ["a", "b", "c", "d", "e"]
p(Range.sum(1..4)) # => 10
p((1..3) == (1..3)) # => true
p(/a/i == /a/i) # => true
```ruby error Range.each(1.0..2.0) { |x| p(x) } # !> Range.each: the Range’s first value must be Integer|String, but is Float [type]
- **Equality.** Two Ranges are `==` when their ends and `exclude_end?` are; two Regexps when their source and options are.
- **Endless Ranges.** Operations that need a finite Range raise `RangeError` on an endless one.
```ruby error
p(Range.to_a(1..)) # !> RangeError: Range.to_a: cannot do this on an endless Range 1..
3.2.2 Regexp and MatchData
Globals such as $1 and $~ do not exist; keep the MatchData in a variable and index it (m[1]). String.match gives nil when nothing matches, so check it first. String.sub, gsub, index, and split also take a Regexp. /.../n is a byte regexp, as in Ruby.
m = String.match("2026-10", /(\d+)-(\d+)/)
if m
p(m[1]) # => "2026"
p(m[2]) # => "10"
end
p(String.sub("a-b", /-/, "+")) # => "a+b"
p(String.scan("a1b22", /\d+/)) # => ["1", "22"]
```ruby error
m = String.match(“a1”, /(\d)/)
puts($1) # !> Sake has no global variables ($1)
### Time
A zone is a fixed UTC offset, as Ruby's: `"+09:00"`, `"-0500"`, `"Z"`, `"UTC"`, a military letter, or seconds (`3600`). `Time.new`, `Time.at`, and `Time.now` also take it as the keyword `in:`. `Time.utc(t)` and `Time.localtime(t, zone)` give a converted copy (Ruby's `utc` and `localtime` change the receiver).
```ruby
t = Time.new(2026, 10, 1, 9, 0, 0, in: "+09:00")
p(t) # => 2026-10-01 09:00:00 +0900
p(Time.utc(t)) # => 2026-10-01 00:00:00 UTC
p(t) # => 2026-10-01 09:00:00 +0900
p(Time.at(0, in: 3600)) # => 1970-01-01 01:00:00 +0100
3.2.3 IO
IO.stdin, IO.stdout, and IO.stderr give the program’s streams as values of type IO. Sake has no $stdout or STDOUT; as with ARGV, a value comes from an operation. File.open(path, mode = "r") gives an IO too; with a block it gives the block’s value and closes the file after the block.
- One type. There is one type for files and streams, so one function can write to either.
- Patterns and equality.
IOcan be matched (in IO), and two IO values are==when they are the same stream or file.
def say(io, s) = IO.puts(io, s)
say(IO.stdout, "to stdout") # => to stdout
File.open("out.txt", "w") { |f| say(f, "to a file") }
p(File.read("out.txt")) # => "to a file\n"
p(IO.stdout == IO.stdout) # => true
3.2.4 Encodings
Strings of incompatible encodings meeting (a byte from Integer.chr(227) next to UTF-8 text) raise EncodingError, which can be rescued.
begin
s = Integer.chr(227) + "あ"
rescue EncodingError => e
puts("rescued") # => rescued
end
4. Functions and blocks
This chapter covers the functions defined with def and the blocks passed to calls. Neither carries a type. Since every call’s callee is known before running, the number of arguments and the keyword names are checked before running.
4.1 Functions
A function is defined with def, and its calls are checked against its parameter count and keyword names. Both the one-line def f(x) = expr and the form closed by end are available; as in Ruby style, the one-line form is for a short expression, and a body of several steps goes in def ... end.
def area(w, h) = w * h
def describe(n)
return "negative" if n < 0
Integer.to_s(n)
end
p(area(3, 4)) # => 12
p(describe(-1)) # => "negative"
p(describe(7)) # => "7"
4.1.1 Positional parameters and defaults
Required positional parameters come first, then optional ones with a default. As in Ruby, a default is evaluated at the call, after the earlier parameters, when the call does not give that argument. It may use the earlier parameters.
def f(a, b = 1, c = b + 1) = [a, b, c]
p(f(10)) # => [10, 1, 2]
p(f(10, 5)) # => [10, 5, 6]
p(f(10, 5, 0)) # => [10, 5, 0]
- Count. A call gives between the required count and the full count of arguments. Any other count is an error before running.
- Order. Arguments are evaluated in the order written.
```ruby error def area(w, h) = w * h p(area(3)) # !> wrong number of arguments for area (given 1, expected 2)
### Keyword parameters
Keyword parameters come last in the parameter list. Each is required (`w:`) or has a default (`h: w`), and they mix freely with optional positional parameters.
```ruby
def greet(name, greeting = "Hello", punct: "!") = "#{greeting}, #{name}#{punct}"
p(greet("Ruby")) # => "Hello, Ruby!"
p(greet("Ruby", punct: "?")) # => "Hello, Ruby?"
p(greet("Ruby", "Hi", punct: ".")) # => "Hi, Ruby."
punct = "!!"
p(greet("Sake", punct:)) # => "Hello, Sake!!"
Since the callee is known before running, the checker delivers k: v to its parameter by name. Each of the following is an error before running.
- An unknown keyword, a keyword given twice, or a missing required keyword.
k: vto a function without keyword parameters. There is no implicit Hash argument: writeHash.of(k: v)for a Hash or{k: v}for a Record.
```ruby error
def size(w:, h: w) = w * h
p(size(w: 3, d: 2)) # !> size has no keyword parameter d
p(size(h: 3)) # !> size needs keyword argument w:
- **`f(k:)`** passes the variable `k`, as in Ruby.
- **Built-ins.** A few built-ins take Ruby's keywords too, checked the same way: `Time.at(t, in: "+09:00")`, `Dir.glob(pat, base: dir)`. The reference writes them as `[k: T]`.
### `*rest`: the remaining positional arguments
`*rest` after the optional parameters collects the remaining positional arguments in a new Array. A function with `*rest` takes any number of arguments. On the call side, `*xs` spreads a Tuple or an Array into the call.
```ruby
def join(sep, *parts) = Array.join(parts, sep)
p(join("-")) # => ""
p(join("-", "a", "b", "c")) # => "a-b-c"
xs = ["x", "y"]
p(join("+", *xs)) # => "x+y"
- Element type.
restis one Array, so its elements share one type: the union of everything any call passes. To keep a type per position, pass one Tuple: callnotify("stock", ["AAPL", 120])and take it apart inside withname, price = event. - Where it is built. The Array is built at the call, since the callee is known.
- Order. The parameters go required, optional,
*rest, keywords,**opts. A positional parameter after*rest, and a nameless*, are errors before running. - Where
*xsmay go.*xsgoes only to a*restparameter or to a built-in that takes any number of arguments (puts,format,Any[...],Array.push, …). It cannot fill required or optional parameters.
```ruby error
def f(*rest, z) = z # !> parameters after *rest are not supported: required, optional, *rest, keywords, **opts, in that order
### `**opts`: the remaining keywords
`**opts` at the end collects the keywords that are not parameter names in a Hash with Symbol keys. It too is built at the call.
```ruby
def tag(name, **opts) = [name, opts]
p(tag("div")) # => ["div", {}]
p(tag("div", id: "main", hidden: true)) # => ["div", {id: "main", hidden: true}]
- Misspellings. A function with
**optsaccepts any keyword, so a misspelled one is no longer an error there. - A nameless
**is an error before running.
4.1.2 Passing keywords on: g(**opts)
g(**opts) passes the collected keywords on to a function g defined with def, as in Ruby. Each key goes to g’s keyword of that name, and g’s own ** gets the rest.
def parse(s, sep: ",", upcase: false)
fields = String.split(s, sep)
upcase ? Array.map(fields) { String.upcase(_1) } : fields
end
def read(s, **opts) = parse(s, **opts)
p(read("a;b", sep: ";")) # => ["a", "b"]
p(read("a;b", sep: ";", upcase: true)) # => ["A", "B"]
- Each key keeps its type. The checker follows each key from the call that collected it, so here
sepis a String andupcasea Boolean, not the union of both. - Only the function’s own
**parameter can be passed on, not any Hash. - Checked like written keywords. A key
ghas no keyword for, and a required keyword the keywords lack, are errors before running (at run time, Ruby’sArgumentError). - Keywords written beside it. One written before
**optsis a default that opts overrides (parse(s, sep: ";", **opts)); one written after overrides opts. - Changing opts first. Once opts is written to (
Hash.delete(opts, :k),opts[k] = v), its keys are no longer followed one by one: each keyword gets the union of the values, and a key may be onegdoes not take.
```ruby error def parse(s, sep: “,”) = String.split(s, sep) def read(s, **opts) = parse(s, **opts) p(read(“a;b”, sepp: “;”))
The last line is reported at `**opts`: `parse: the keywords passed on with ** hold :sepp, which parse has no keyword parameter for`, with the hint `did you mean sep:?`.
### The block parameter `&b`
`&b` (or `&` alone) is a parameter whose only use is to pass the function's block on to another call. It is described under "Blocks".
### Agreement among a mixin function's definitions
A module's mixin function dispatches to the definition of its first argument's type ([Program structure and name resolution](02-program.md)). So the types' definitions must agree on the parameter counts, the keyword names, and on having `*rest` / `**opts`. A disagreement is an error before running, at the call.
```ruby error
module Shape
def area(s) = raise NotImplementedError
end
class Sq
include Shape
attr_reader side
def area(s) = @side * @side
end
class Rect
include Shape
attr_reader w, h
def area(r, scale) = @w * @h * scale
end
p(Shape.area(Sq.new(2))) # !> Shape.area dispatches to Rect.area, whose arguments or block differ from Shape.area
4.1.3 Return value
A function’s value is the value of its last expression, or of return expr. return a, b returns the Tuple [a, b], so the caller can take it with a, b = f(...).
def minmax(xs) = return Array.min(xs), Array.max(xs)
lo, hi = minmax(Any[3, 1, 2])
p([lo, hi]) # => [1, 3]
4.1.4 Polymorphism
Functions are polymorphic. Having no type annotations, a function works on any arguments its operations accept. A type error surfaces at the operation that fails: the checker reports that line, with a hint naming the call that reached it.
def twice(x) = x + x
p(twice(2)) # => 4
p(twice("ab")) # => "abab"
p(twice(1.5)) # => 3.0
```ruby error def twice(x) = x + x # !> Arithmetic.+: the operands are (:a, :a), which the left operand’s type does not support p(twice(:a))
### Local variables
Each function has its own scope. It has no access to the top-level locals.
```ruby error
count = 10
def bump = count + 1 # !> undefined local variable or function `count`
As in Ruby, reading a local whose assignment appears earlier in the text but has not run yet gives nil.
def f
x = 1 if false
p(x) # => nil
end
f
4.1.5 Recursion
Recursion is allowed. A depth over 10,000 calls raises SystemStackError. bin/sake runs the program on a thread with a large stack, so that this limit applies rather than Ruby’s stack. A long backtrace is shortened in the middle.
def fact(n) = n <= 1 ? 1 : n * fact(n - 1)
p(fact(20)) # => 2432902008176640000
def down(n) = n == 0 ? 0 : down(n - 1)
p(down(9_000)) # => 0
```ruby error def down(n) = n == 0 ? 0 : down(n - 1) p(down(20_000)) # !> SystemStackError: stack level too deep
## Blocks
A block is passed to a built-in operation or to a user function that yields. Most iteration is written with block-taking operations such as `Array.each`.
```ruby
xs = Any[1, 2, 3]
p(Array.map(xs) { |x| x * 2 }) # => [2, 4, 6]
Hash.each(Hash.of(a: 1, b: 2)) do |k, v|
puts "#{k}=#{v}" # => a=1
# => b=2
end
Integer.times(3) { p it } # => 0
# => 1
# => 2
4.1.6 Not values
Blocks are second-class. They cannot be stored in a variable or returned from a function. proc, lambda, and -> are not available.
```ruby error
f = proc { |x| x } # !> undefined function proc
### Parameters
`|a, b|` lists plain names. `it` and `_1` … `_9` work as in Ruby.
```ruby
p(Array.map(Any[1, 2]) { _1 * 10 }) # => [10, 20]
p(Array.map(Any[1, 2]) { it + 1 }) # => [2, 3]
4.1.7 Destructuring
When a block declares two or more parameters and receives a single Tuple or Array, its elements become the parameters. Missing ones are nil and extra ones are dropped, as in Ruby. A parameter can itself be taken apart with ( ).
pairs = Any[["a", 1], ["b", 2]]
Array.each(pairs) { |name, n| p("#{name}=#{n}") } # => "a=1"
# => "b=2"
Array.each_with_index(pairs) { |(name, n), i| p([i, name]) } # => [0, "a"]
# => [1, "b"]
p(Array.inject(pairs, 0) { |acc, (k, v)| acc + v }) # => 3
Array.each(Any[[1, 2]]) { |a, b, c| p([a, b, c]) } # => [1, 2, nil]
4.1.8 Rest parameter
|a, *rest| (and |a, *rest, z|, |*all|) collects the remaining arguments, or the remaining elements of a destructured Tuple or Array, in a new Array, as a, *rest = x does. |*all| alone does not destructure.
Array.each(Any[[1, 2, 3]]) { |a, *rest| p([a, rest]) } # => [1, [2, 3]]
Array.each(Any[[1, 2, 3]]) { |a, *rest, z| p([a, rest, z]) } # => [1, [2], 3]
Array.each(Any[[1, 2, 3]]) { |*all| p(all) } # => [[1, 2, 3]]
4.1.9 Parameter count
A block with no parameters ignores its arguments. Otherwise, a block called with the wrong number of arguments raises ArgumentError while running. With a rest parameter, only fewer arguments than the other parameters is wrong.
Integer.times(2) { puts "hi" } # => hi
# => hi
```ruby error Array.each_with_index(Any[“a”]) { |x| p(x) } # !> ArgumentError: block takes 1 parameter(s) but was given 2
### Scope
A block sees and can assign the enclosing local variables.
```ruby
sum = 0
Array.each(Any[1, 2, 3]) { |x| sum += x }
p(sum) # => 6
4.1.10 yield and optional blocks
yield(args...) calls the block given to the current function. A yield outside a function is a syntax error. A function that contains yield must get a block at every call.
```ruby error
def each_twice(xs)
Array.each(xs) { |x| yield x; yield x }
end
each_twice(Any[1, 2]) # !> each_twice uses yield but no block is given
`block_given?` tells whether the current function got a block. A function that checks it may be called without one.
```ruby
def info(msg = nil) = puts(block_given? ? yield : msg)
info("plain") # => plain
info { "from block" } # => from block
- Branch analysis. The checker knows for each call whether a block was given, so it analyzes only the branch taken. This holds for
block_given?under&&,||, and!as well. - A
yieldthat should be unreachable. Ayieldreached without a block (or&bto a call that needs a block) is an error at thetypelevel. While running it raisesLocalJumpError, which cannot be rescued.
4.1.11 Passing a block on: &b
def f(xs, &b) = Array.map(xs, &b) passes the function’s own block to another call. & alone works too. b is used only as &b.
def twice(xs, &b) = Array.map(xs, &b)
p(twice(Any[1, 2]) { |x| x * 2 }) # => [2, 4]
def each_pair(h, &) = Hash.each(h, &)
each_pair(Hash.of(a: 1)) { |k, v| p([k, v]) } # => [:a, 1]
- Calling without a block. As in Ruby, a function that only passes its block on may be called without one. If nothing then reaches a call that needs a block, it is an error at the
typelevel. break. Abreakin the passed block ends the call the block was written for (twice(...)above).
```ruby error def twice(xs, &b) = Array.map(xs, &b) # !> the block passed on is missing here, and this call needs one p(twice(Any[1, 2]))
### `next`, `break`, and `return`
The three ways out are Ruby's.
| Form | What it ends | Value |
|---|---|---|
| `next [v]` | the current block call | `v` (default `nil`) is the block's value |
| `break [v]` | the call the block was given to | `v` (default `nil`) is that call's value |
| `return [v]` | the enclosing **function** | `v` is the function's value |
```ruby
xs = Any[1, 2, 3, 4]
p(Array.map(xs) { |x| next 0 if x % 2 == 0; x }) # => [1, 0, 3, 0]
p(Array.each(xs) { |x| break x if x > 2 }) # => 3
p(Array.each(xs) { |x| x }) # => [1, 2, 3, 4]
def first_big(xs)
Array.each(xs) { |x| return x if x > 2 }
nil
end
p(first_big(xs)) # => 3
- Inside a
while. Inside awhilein the block,breakleaves thewhile. - Type. The checker adds the
breakvalues to the call’s result type.
Note
A block’s parameters and locals belong to the enclosing function’s frame. When a block is handed to a function that runs it later, on a thread (a concurrent-ruby style post { ... }), it reads the values as they are when the thread runs (the last value of a loop variable). In Ruby a block’s parameters are fresh for each call. The difference is observable only with threads (see TODO.md).
4.2 once
once { ... } gives the block’s value. The block runs the first time that place in the program is reached, and later passes through it give the same value, for the whole program and every thread. It is Sake’s way to compute a table or a named value once, since there are no value constants.
def table = once { puts "computing"; Array.map(Any[1, 2, 3]) { |i| i * i } }
p(table) # => computing
# => [1, 4, 9]
p(table) # => [1, 4, 9]
Array.push(table, 16)
p(table) # => [1, 4, 9, 16]
- Shared. The value is shared, as Ruby’s constants are. As above, an Array kept by
oncecan still be changed. - Re-entry. A block that reaches its own
onceagain while computing it is a program error:SystemStackError. - Type. For the checks before running, its type is the union of the block’s results over every call that may compute it.
ruby error
def loop_once = once { loop_once }
p(loop_once) # !> SystemStackError: once: the block reached its own once again while computing it
5. Operators and indexing
This chapter covers the binary operators such as a + b and the index x[k]. Both are shorthands for a module’s function, resolved by the type of the left operand (the receiver). A class gets operators and an index by including that module and defining the functions.
5.1 Binary operators
Each operator belongs to a module, and a OP b is shorthand for calling that module’s function: a + b is Arithmetic.+(a, b). A module’s function is resolved by the type of its first argument (module functions and dispatch), so the call runs T.+(a, b) where T is the type of a: an operator dispatches on its left operand. Error messages name the module’s function (Arithmetic.*: ...).
p(1 + 2) # => 3
p(Arithmetic.+(1, 2)) # => 3
p(Integer.+(1, 2)) # => 3
p("a" + "b") # => "ab"
| Module | Operators | Built-in types that include it |
|---|---|---|
Arithmetic |
+ - * / % **, unary -x +x (-@ +@) |
Integer, Float, Rational, Complex, String, Time, Set |
Comparable |
<=> < <= > >= |
Integer, Float, Rational, String, Time, Tuple, Array |
Bitwise |
& \| ^ << >>, unary ~x |
Integer, Set |
Indexable |
[], []= (see “Indexing”) |
Array, Hash, String, Tuple, MatchData |
Kernel |
== != =~ !~ |
every type |
5.1.1 Explicit forms
Arithmetic.+(a, b) dispatches exactly as a + b does. Integer.+(a, b) calls Integer’s + directly: a must be an Integer, and b may be any type that Integer’s + accepts.
```ruby error p(Integer.+(“a”, “b”)) # !> Integer.+: argument 1 must be Integer, but is String
### Giving your own type an operator
A class joins an operator by including the module and defining the operator in its body, such as `include Arithmetic` with `def +(a, b)`. With `include Comparable`, defining `<=>` is enough: `<`, `<=`, `>`, and `>=` come from it, and `Array.sort`, `min`, and `max` use that `<=>` too.
```ruby
class Money
include Arithmetic
include Comparable
attr_reader cents
def +(a, b) = Money.new(@cents + Money.cents(b))
def <=>(a, b) = @cents <=> Money.cents(b)
def to_s(m) = format("$%d.%02d", @cents / 100, @cents % 100)
end
a = Money.new(150)
b = Money.new(250)
puts(a + b) # => $4.00
p(a < b) # => true
puts(Array.join(Array.sort(Any[b, a]), " ")) # => $1.50 $2.50
When the left operand’s type does not include the operator’s module, or does not define the operator, it is a type problem before running and a TypeError while running.
```ruby error Point = Struct.new(:x, :y) p(Point.new(1, 2) + Point.new(1, 2)) # !> Arithmetic.+: Point does not include Arithmetic
### A built-in on the left: coerce
`2 + money` dispatches on Integer, whose table has no row for Money. A class that defines `coerce(b, a)`, giving a Tuple `[left, right]` as Ruby's protocol does, has the pair converted first and the operator run on it. With the `coerce` below, `2 + money` becomes `Money.new(200) + money`. The checker follows the conversion.
```ruby
class Money
include Arithmetic
attr_reader cents
def +(a, b) = Money.new(@cents + Money.cents(b))
def coerce(m, other) = [Money.new(other * 100), m]
end
m = Money.new(50)
p(2 + m) # => #<struct Money cents=250>
5.1.2 Equality
== on a class’s values compares the type and the fields, as Ruby’s Struct does, unless the type defines its own ==. Array.include?, index, and similar operations use the same equality.
Point = Struct.new(:x, :y)
p(Point.new(1, 2) == Point.new(1, 2)) # => true
p(Point.new(1, 2) == Point.new(2, 1)) # => false
p(Point.new(1, 2) == "x") # => false
p(Array.include?(Any[Point.new(1, 2)], Point.new(1, 2))) # => true
A type that includes Comparable and defines <=> (and not ==) is equal to a value of the same type when <=> gives 0, as Ruby’s Comparable#==. A value of another type, such as nil, is never equal, and <=> is not called then. For the Money above, a == Money.new(150) is true and a == nil is false.
5.1.3 The table for the built-in types
For the built-in types, the result type depends on both operand types, through a closed table.
| Operator | Rows |
|---|---|
+ |
(Integer, Integer) → Integer; with a Float → Float; with a Rational (and no Float) → Rational; with a Complex → Complex; (String, String) → String |
- * / % ** |
numeric pairs as for + |
* |
also (String, Integer) → String |
< <= > >= <=> |
numeric pairs, (String, String), (Tuple, Tuple), (Array, Array) → true/false (<=>: -1, 0, 1, or nil) |
== != |
any two values: equal values of the same type (numbers compare across Integer, Float, Rational); values of different types are not equal, as in Ruby |
+ - |
also (Array, Array) → Array (concatenation; difference) |
* |
also (Array, Integer) → Array (repetition) |
< <= > >= <=> |
also (Symbol, Symbol) |
unary - + |
Integer, Float, Rational, Complex → the same type |
unary ~ |
Integer → Integer |
& \| ^ << >> |
(Integer, Integer) → Integer |
\| & - |
(Set, Set) → Set (union, intersection, difference) |
=~ |
(String, Regexp), (Regexp, String) → Integer or nil; !~ (String, Regexp) → true/false |
% |
(String, Integer / Float / String / Symbol / Tuple / nil / true / false) → String, Ruby’s format ("%d items" % 3, "%s-%s" % [a, b]) |
p("ab" * 3) # => "ababab"
p(Any[1, 2] + Any[3]) # => [1, 2, 3]
p(Any[1, 2, 3] - Any[2]) # => [1, 3]
p(Set.of(1, 2) | Set.of(2, 3)) # => Set[1, 2, 3]
p(5 & 3) # => 1
p(1 << 4) # => 16
p("abc" =~ /b/) # => 1
p("abc" !~ /z/) # => true
p("%s-%s" % ["a", "b"]) # => "a-b"
5.1.4 No matching row
If no row matches the operand types, it is a type problem before running and a TypeError while running. The runtime message lists the rows that do exist. There is no implicit conversion.
```ruby error p(“a” - “b”) # !> Arithmetic.-: the operands are (String, String), which the left operand’s type does not support
### Numbers
`/` and `%` on Integers follow Ruby, rounding toward the floor for negative numbers. Dividing an Integer by zero raises `ZeroDivisionError`; Float division by zero follows IEEE.
```ruby
p(7 / 2) # => 3
p(-7 / 2) # => -4
p(-7 % 2) # => 1
p(7.0 / 2) # => 3.5
p(1 / 2r) # => (1/2)
p(1.0 / 0) # => Infinity
- Negative exponent.
Integer ** negative IntegerraisesArgumentError, so that the type ofa ** bdoes not depend on the value ofb. Write2r ** -1for a Rational ((1/2)). - Complex. A Complex has no ordering, so
<and the like are not defined for it.
```ruby error p(1 / 0) # !> ZeroDivisionError: Arithmetic./: divided by 0
```ruby error
p(2 ** -1) # !> ArgumentError: Arithmetic.**: Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
5.1.5 Time
Time ± number gives a Time, and Time - Time gives a Float of seconds. Two Times compare.
t = Time.at(0, in: "+00:00")
p(t + 60) # => 1970-01-01 00:01:00 +0000
p(Time.at(60) - t) # => 60.0
p(t < Time.at(1)) # => true
5.1.6 Rules of equality and ordering
Values of different types are never equal: 1 == :a and struct == "x" are false, as in Ruby. A class’s own == decides for its values. Only numbers compare across Integer, Float, and Rational.
p(1 == 1.0) # => true
p(1 == 1r) # => true
p(1 == :a) # => false
p("1" == 1) # => false
Tuples, Arrays, Sets, Hashes, and Records are equal when their contents are. Two Records also need the same fields. Tuples and Arrays are ordered element by element, as Ruby’s Arrays are.
p([1, 2] == [1, 2]) # => true
p([1, 2] == Any[1, 2]) # => false
p({x: 1} == {x: 1, y: 2}) # => false
p(Set.of(1, 2) == Set.of(2, 1)) # => true
p([1, 2] < [1, 3]) # => true
p(Any[1, 2] <=> Any[1, 2, 0]) # => -1
When two elements cannot be compared, < and the like raise ArgumentError and <=> gives nil. Before running, the checker reports element types that cannot be compared.
```ruby error p(Any[“a”, 1] < Any[“a”, “b”]) # !> Comparable.<: elements compared in order may be (Integer, String), which cannot be compared
### Not operators
- **`!x`** is `x ? false : true`. It works on any value and is not an operation of a type.
- **Compound assignment.** `x OP= e` means `x = x OP e`.
- **`&&` and `||`** short-circuit and return one of their operands, as in Ruby.
```ruby
p(!0) # => false
p(!nil) # => true
x = 5
x += 2
p(x) # => 7
p(nil || "default") # => "default"
p(1 && "second") # => "second"
5.2 Indexing
x[k] means Indexable.[](x, k), and x[k] = v means Indexable.[]=(x, k, v): they run the [] and []= of x’s type.
xs = Any[10, 20, 30]
p(xs[0]) # => 10
p(xs[-1]) # => 30
p(xs[1..]) # => [20, 30]
p(xs[0, 2]) # => [10, 20]
p(Indexable.[](xs, 1)) # => 20
xs[1] = 25
p(xs) # => [10, 25, 30]
The built-in types behave as follows.
| Receiver, index | x[k] |
x[k] = v |
|---|---|---|
| Array, Integer | the element, or nil outside the Array | stores v (an Array of T checks v) |
| String, Integer | a one-character String, or nil outside the String | not available |
| Tuple, Integer | the element; outside the Tuple, IndexError |
stores v if it has the type of that position |
| Array, Range / String, Range | the slice, or nil | not available |
Array, Integer, Integer / String, Integer, Integer (s[start, length]) |
the slice, or nil | not available |
| Hash, any key | the value, or the default (nil unless made by Hash.new(default)) |
stores v |
| MatchData, Integer / String | the group, or nil | not available |
s = "hello"
p(s[0]) # => "h"
p(s[1..2]) # => "el"
p(s[1, 3]) # => "ell"
h = Hash.of("a" => 1)
p(h["a"]) # => 1
p(h["z"]) # => nil
p(Hash.new(0)["z"]) # => 0
m = String.match("2026-10", /(\d+)-(\d+)/)
if m
p(m[0]) # => "2026-10"
p(m[2]) # => "10"
end
5.2.1 An index outside
Negative indexes count from the end, as in Ruby. A miss gives nil, as in Ruby, so the static type of a[i] is T | nil. A result used unchecked is reported by index-nil at level 3; level 2 lets it pass, and the program stops at that operation while running. Array.fetch(a, i) raises IndexError instead.
```ruby error xs = Any[10, 20, 30] p(xs[5] + 1) # !> TypeError: Arithmetic.+: no implementation for (nil, Integer)
```ruby error
xs = Any[10, 20, 30]
p(Array.fetch(xs, 5)) # !> IndexError: Array.fetch: index 5 outside of array bounds: -3...3
A Tuple’s length is part of its type, so an index outside it is an IndexError. With a constant index it is reported before running.
```ruby error t = [1, “a”] p(t[2]) # !> Indexable.[]: the index is outside the Tuple [Integer, String]
### Writing
Writing past the end of an untyped Array fills the gap with nil, as in Ruby. An Array of T raises `IndexError` instead, because nil is not a T. Writing a value that is not a T is a `type` problem before running. A Tuple takes only a value of the position's type. A String cannot be written to.
```ruby
xs = Any[1]
xs[3] = 4
p(xs) # => [1, nil, nil, 4]
```ruby error ys = Integer[1] ys[3] = 4 # !> IndexError: Array.[]=: index 3 is past the end of Integer[] (length 1); the gap would be nil
```ruby error
ys = Integer[1]
ys[0] = "a" # !> Indexable.[]=: an element must be Integer, but is String
5.2.2 Compound assignment
x[k] OP= v and x[k] ||= v evaluate x and k once. On a local, y ||= v assigns only when y is nil or false.
h = Hash.new
h[:a] ||= Any[]
Array.push(h[:a], 1)
h[:a] ||= Any[]
p(h) # => {a: [1]}
y = nil
y ||= 5
y ||= 6
p(y) # => 5
5.2.3 Giving your own type an index
A class joins with include Indexable and def [](x, k) / def []=(x, k, v). With two indexes, m[r, c] calls def [](m, r, c) and m[r, c] = v calls def []=(m, r, c, v).
class Grid
include Indexable
attr_reader w, cells
def [](g, r, c) = @cells[r * @w + c]
def []=(g, r, c, v)
@cells[r * @w + c] = v
end
end
g = Grid.new(2, Any[0, 0, 0, 0])
g[1, 0] = 7
p(g[1, 0]) # => 7
p(Grid.cells(g)) # => [0, 0, 7, 0]
6. Control flow and patterns
Sake’s control flow is Ruby’s. if and while, the modifier forms, an early return, and case are written as in Ruby. There are three differences. Iteration is written with block-taking operations such as Array.each, not with for. case branches on patterns with in, not with when. And a condition does more than test a value: it narrows the type the checker tracks for a variable. This narrowing is how a value that may be nil, or a union such as Integer | String, is used.
The # => value comments in this chapter are the real output of bin/sake --strict=2.
6.1 Conditions
if / elsif / else, unless / else, and the ternary c ? a : b are as in Ruby. Each is an expression whose value is that of the branch taken. When no branch is taken, the value is nil.
n = 5
r = if n > 10 then "big" elsif n > 3 then "mid" else "small" end
p(r) # => "mid"
p(if n > 10 then "big" end) # => nil
p(unless n > 10 then "not big" end) # => "not big"
p(n > 3 ? "yes" : "no") # => "yes"
6.1.1 Truthiness
Only nil and false are false. Every other value is true, including 0, "", and an empty Array. &&, ||, and ! are as in Ruby; && and || return the last operand they evaluated.
p(0 ? "truthy" : "falsy") # => "truthy"
p("" ? "truthy" : "falsy") # => "truthy"
p(nil ? "truthy" : "falsy") # => "falsy"
p(nil || "default") # => "default"
p(0 && "zero is truthy") # => "zero is truthy"
6.1.2 Modifier forms
A condition may follow the statement: stmt if c, stmt unless c, stmt while c, and stmt until c.
n = 5
puts("positive") if n > 0 # => positive
i = 0
i += 1 while i < 3
p(i) # => 3
i -= 1 until i == 0
p(i) # => 0
6.2 Loops
while and until are as in Ruby. Inside a loop, break leaves the loop and next starts the next iteration. The value of a loop is nil, except that break v leaves it with the value v.
i = 0
out = Any[]
r = while i < 10
i += 1
next if i % 2 == 0
break if i > 7
Array.push(out, i)
end
p(out) # => [1, 3, 5, 7]
p(r) # => nil
6.2.1 Iteration is a block-taking operation
for is not supported. To walk a sequence, write a block-taking operation such as Array.each, Integer.times, or Range.each (Functions and blocks). Inside the block, break v ends the call the block was given to, with the value v; next v ends the current block call with v.
xs = Any[3, 8, 12, 5]
big = Array.each(xs) { |x| break x if x > 10 }
p(big) # => 12
squares = Any[]
Integer.times(4) { |i| next if i == 0; Array.push(squares, i * i) }
p(squares) # => [1, 4, 9]
loop { ... } repeats until a break, and the value of the break is its value.
i = 0
r = loop do
i += 1
break i * 10 if i == 3
end
p(r) # => 30
6.2.2 Forms that are not supported
for and begin ... end while are static errors.
```ruby error
for i in 1..3 # !> for is not supported; iterate with an operation
p(i)
end
## Leaving a function early
`return` ends the function at once. `return v` makes `v` the function's value, and `return a, b` returns the Tuple `[a, b]`. The modifier form `return v if c` is the way to dispose of a precondition before the main case. A `return` inside a block also returns from the enclosing **function**, not from the block ([Functions and blocks](04-functions.md)).
```ruby
def sign(n)
return "negative" if n < 0
return "zero" if n == 0
"positive"
end
p(sign(-2)) # => "negative"
p(sign(0)) # => "zero"
p(sign(7)) # => "positive"
6.3 case / in
case x followed by in P then ... branches runs the first branch whose pattern matches. A branch’s body may follow then on the same line or start on the next line. When no branch matches and there is no else, the result is the runtime error NoMatchingPatternError.
def kind(v)
case v
in nil then "nothing"
in true | false then "bool"
in Integer | Float then "number"
in String
"text"
end
end
p(kind(nil)) # => "nothing"
p(kind(true)) # => "bool"
p(kind(2.5)) # => "number"
p(kind("s")) # => "text"
case/when is not supported. Ruby’s when dispatches on the receiver’s ===; Sake has no ===, and a match compares type tags and values. What can be written as a pattern is the table in the last section of this chapter, “Pattern matching”.
```ruby error
x = 1
case x # !> case/when is not supported (Ruby’s === dispatches on the receiver); match with case x / in Type
when 1 then puts(“one”)
end
## Narrowing by a condition
The checker infers the type of each local variable over the whole program. A condition **narrows** that type inside the branch taken: inside `if x`, `x` is not nil; inside `if x in Integer`, `x` is an Integer. This is how a value that may be nil (`nil | String`) or a union (`Integer | String`) is used without a `type` or `nil` report.
### Removing nil
A local variable `x` is narrowed in the following places (the same table as in the nil section of [Values and types](03-values.md)).
| Form | Where `x` is narrowed |
|---|---|
| `if x` / `while x` / `x && …` | non-nil in the branch taken when `x` is truthy |
| `x != nil` / `x == nil` | nil or non-nil in the matching branch |
| `!x` / `unless x` | the same, with the branches swapped (`if !x … else` is non-nil in the else) |
| `return unless x`, `next unless x`, `break unless x`, and other early exits | non-nil after the statement |
| `String.size(x)`, or any built-in operation taking `x` as an argument | after the call, a type that the operation accepts (it checks its arguments while running) |
```ruby
def greet(name = nil)
if name
"Hello, " + String.upcase(name)
else
"Hello"
end
end
p(greet("ko1")) # => "Hello, KO1"
p(greet()) # => "Hello"
Without the if name, the checker reports the call greet(). It analyzes a function for each call, so the report names the call that led there.
```ruby error def greet(name = nil) String.upcase(name) # !> String.upcase: argument 1 must be String, but is nil [type] end p(greet(“ko1”)) p(greet())
An early exit is a common way to remove nil. `while x` narrows the same way.
```ruby
def size_or_zero(xs)
x = Array.first(xs)
return 0 unless x
String.size(x)
end
p(size_or_zero(Any["abc"])) # => 3
p(size_or_zero(Any[])) # => 0
6.3.1 When a field’s narrowing ends
A field read from a local variable (Node.next(n), @next) is narrowed to non-nil inside if Node.next(n) too. Fields are mutable, though, so an operation that may change the field between the test and the use ends the narrowing: a field write, a call of a user function, a call that passes a block and yield, and an assignment to the variable. Inside a block, a field narrowed outside it is not narrowed either. In those places, copy the field into a local variable first, then test the local.
class Node
attr_accessor value, :next
end
def next_value(n)
nx = Node.next(n)
if nx
Node.value(nx)
else
0
end
end
a = Node.new(1, nil)
b = Node.new(2, a)
p(next_value(b)) # => 1
p(next_value(a)) # => 0
```ruby error class Node attr_accessor value, :next end def bump(n) = Node.set_value(n, Node.value(n) + 1) def next_value(n) if Node.next(n) bump(n) Node.value(Node.next(n)) # !> Node.value: argument 1 must be Node, but is nil [type] else 0 end end p(next_value(Node.new(1, nil)))
### Narrowing to a type
`x in Integer` is true when `x` is an Integer, and inside that branch `x` is narrowed to Integer. `elsif` and `else` see the types that are left. The `in` branches of `case x` work the same way. A module name matches any type that includes it (`in Shape` is `in Circle | Square`), and a built-in exception type includes its Ruby subclasses (`in IndexError` also matches KeyError).
```ruby
def pick(flag) = flag ? 1 : "one"
def describe(x)
if x in Integer
x + 1
elsif x in String
String.size(x)
else
x
end
end
p(describe(pick(true))) # => 2
p(describe(pick(false))) # => 3
6.3.2 Which level reports it
A value that may be nil, passed to an operation without a check, is reported as the nil item at level 2 (--strict). The nil of a miss (x[k], Array.first, Hash.dig, and the like) is the index-nil item at level 3. The levels and items are in Overview and running. A nil that was not reported still stops the program at run time, with a TypeError from the operation that received it.
6.4 Pattern matching
x in P is true when x matches the pattern P. x => P asserts it: it raises NoMatchingPatternError when x does not match, and binds a Record pattern’s fields. The in P branches of case x take the same patterns.
| Pattern | Matches |
|---|---|
a type name: Integer, String, Tuple, Hash, Point, Record, IO, … |
a value of that type (its type tag) |
nil, true, false, 1, "s", :ok |
an equal value of the same type |
P \| Q |
either |
{x:, y: name} |
a Record with those fields; binds the locals x and name |
[P, Q] |
a Tuple of that length whose positions match P and Q (nested patterns allowed) |
x (a bare name inside [...], or alone) |
anything; binds the local x |
Patterns outside this table (*rest, find patterns, pins ^x, guards, and the Integer => n form) are not supported and are static errors (Differences from Ruby).
6.4.1 No dispatch
A match compares type tags and values. There is no ===, so case/when is not supported. A type-name pattern looks at the value’s type tag only; the B of class B < A does not match A (Classes).
6.4.2 Binding
Record and Tuple patterns take the elements out into local variables. {x:} binds field x to a local of the same name; {y: name} binds field y to the local name. P => name binds what the pattern P matched, narrowed as P: in [Integer => code, String => body].
pt = {x: 3, y: 4}
pt => {x:, y: why}
p(x) # => 3
p(why) # => 4
pair = [1, "one"]
case pair
in [n, s] then p([s, n]) # => ["one", 1]
end
p((pair in [Integer, String])) # => true
p((pair in [String, Integer])) # => false
case pair
in [Integer => n, String => s] then p(String.upcase(s) * n) # => "ONE"
end
6.4.3 Narrowing
In if x in Integer, and in each in branch of case x, a local x is narrowed to the matching types. The else branch, and each later branch, sees the types that are left. This is how a union such as Integer | String is used without a type report (“Narrowing to a type” in the previous section).
def pick(flag) = flag ? 1 : "one"
def f(x)
case x
in Integer then x + 1
else String.size(x)
end
end
p(f(pick(true))) # => 2
p(f(pick(false))) # => 3
6.4.4 Exhaustiveness
A case without else that may leave a possible type unmatched is reported as type. The set of types is closed, so the checker can check this.
```ruby error
def show(x)
case x # !> case/in: no in branch matches String [type]
in Integer then “int”
end
end
p(show(41))
p(show(“abc”))
Symbol literals are tracked as values. When `op` only ever holds those literals, `case op in :add ... in :sub` is complete.
```ruby
def name(op)
case op
in :add then "plus"
in :sub then "minus"
end
end
p(name(:add)) # => "plus"
p(name(:sub)) # => "minus"
When literal branches may leave some values of an open type unmatched (some String, Integer, or a Symbol made at run time), the report is the exhaustive item (level 3). The program may well be correct, and NoMatchingPatternError still stops it if not. The next example runs at level 2 and is reported at --strict=3.
```ruby error
def name(op)
case op # !> case/in: no in branch matches some values of String [exhaustive]
in “add” then “plus”
in “sub” then “minus”
end
end
p(name(“add”))
p(name(String.downcase(“SUB”)))
### Assertion: x => P
After `x => P`, a local `x` is narrowed to the matching types, as in an `in` branch. Inside `initialize`, a field `@x` of the new instance is narrowed the same way. It is how a fact such as "a port is an Integer" is written where the value is stored: `p => Integer`, then `@port = p`.
- A value that surely does not match is reported as `type` (level 1).
- A value that may not match (another type may come) is checked when it runs, and reported only as `exhaustive` (level 3). The failure is `NoMatchingPatternError`, which can be rescued, as in Ruby.
- `x => P` is itself a check for nil, so a value that may be nil is not reported. It is treated like `Array.fetch`, which checks for a missing index.
```ruby error
x = "abc"
x => Integer # !> `=> Integer`: the value is String, which does not match [type]
def pick(flag) = flag ? 1 : nil
x = pick(true)
x => Integer
p(x + 1) # => 2
6.4.5 Parentheses
As in Ruby, x in P must be in parentheses when it is an argument: p((x in Integer)). x in T ? a : b and cond && x in T also parse differently from what they look like; write (x in T).
x = 1
p((x in Integer)) # => true
r = (x in Integer) ? "int" : "other"
p(r) # => "int"
p(x > 0 && (x in Integer)) # => true
ruby error
x = 1
p(x in Integer) # !> syntax error: unexpected 'in'; expected a `)` to close the arguments
7. Classes
A type a program defines is a class. Declared with attr_* lines in a class or made with Struct.new(:x, :y), it is the same thing: its values hold fields (and print as Ruby’s Struct values, #<struct Point x=3, y=4>), and its operations are called with the type, Point.norm2(pt). Classes do not inherit and values take no method calls, so what a Sake class shares with Ruby’s is declaring a type and gathering its operations.
The # => value comments in this chapter are the real output of bin/sake --strict=2.
7.1 Declaring a type: class and attr_*
class C ... end declares the type C. The attr_* lines of its body give the fields, and its defs give the operations. C.new makes a value; a field is read with the reader C.x(c) and written with the writer C.set_x(c, v).
class Account
attr_reader owner
attr_accessor balance
def initialize(a)
@balance = 0 if @balance == nil
end
end
acct = Account.new("ko1")
p(acct) # => #<struct Account owner="ko1", balance=0>
p(Account.owner(acct)) # => "ko1"
Account.set_balance(acct, 100)
p(Account.balance(acct)) # => 100
rich = Account.new("you", 500)
p(Account.balance(rich)) # => 500
Every class is a type. Its fields are declared in the body of its first class; a later class Account adds functions only. The field names are written bare.
| Line | Meaning |
|---|---|
attr_accessor x, ... |
fields with a reader C.x(c) and a writer C.set_x(c, v) |
attr_reader x, ... |
the reader only; inside the type’s functions, @x = v still writes them |
attr_writer x, ... |
the writer only; inside the type’s functions, @x still reads them |
private attr_... x |
the reader and writer are for the class’s own functions only; new still takes it |
A private field can be read and written by the class’s functions on any of its values (@x, or T.x(other)). T.x(c) from outside is a static error.
class Counter
attr_reader label
private attr_accessor n
def bump(c) = @n += 1
def same?(c, other) = n(c) == n(other)
end
c = Counter.new("hits", 0)
Counter.bump(c)
p(Counter.bump(c)) # => 2
p(Counter.same?(c, Counter.new("x", 2))) # => true
```ruby error
class Counter
attr_reader label
private attr_accessor n
end
c = Counter.new(“hits”, 0)
p(Counter.n(c)) # !> field n of Counter is private (private attr_*)
### Field order and new
Fields are in the order written, across lines. That is the order of `C.new`'s arguments. `C.new` takes the fields positionally, whatever their access, and also by name: `Logger.new("app", level: :warn)` gives the first field by position and `level` by keyword.
- Prefer the names for a type with several fields: a positional `new` is not checked against the field order.
- An unknown name, and one field given twice, are static errors.
- Without `initialize`, every field must be given.
- With `initialize`, trailing fields may be left out, or skipped for a later keyword. A field left out is nil when initialize starts, and initialize sets it.
```ruby
class Logger
attr_reader name, level
def initialize(l)
@level = :info if @level == nil
end
end
a = Logger.new("app")
p(Logger.level(a)) # => :info
b = Logger.new("app", level: :warn)
p(Logger.level(b)) # => :warn
c = Logger.new(level: :debug, name: "db")
p(c) # => #<struct Logger name="db", level=:debug>
```ruby error
class Logger
attr_reader name, level
def initialize(l)
@level = :info if @level == nil
end
end
Logger.new(“app”, lvl: :warn) # !> Logger has no field lvl
### initialize
`def initialize(c)` in a class runs **after** `C.new` has stored the fields, with the new instance. As in Ruby, it is the place for first values (`@items = Any[]`), checks (`@port => Integer`), and conversions (`@celsius = Float(@celsius)`). `@level = :info if @level == nil` keeps a value that `new` gave.
- It takes exactly that one parameter, the new instance. Two or more is a static error.
- Calling `C.initialize` directly is a static error; `C.new` calls it.
- The checker analyzes initialize for each `C.new` call. There, `@x` reads the value that call gave, so a wrong argument is reported for that call. The fields hold what initialize leaves in them.
```ruby
class Temp
attr_reader celsius, label
def initialize(t)
@celsius = Float(@celsius)
@label = "C" if @label == nil
end
end
t = Temp.new(21)
p(Temp.celsius(t)) # => 21.0
p(Temp.label(t)) # => "C"
p(Temp.new(1, "K")) # => #<struct Temp celsius=1.0, label="K">
```ruby error
class Box
attr_reader value
def initialize(b)
@value => Integer # !> => Integer: the value is String, which does not match [type]
end
end
p(Box.new(1))
p(Box.new(“s”))
> [!WARNING]
> Ruby's `def initialize(x) = @x = x` is a static error in Sake: `x` is the new instance, not a value, so this would store the instance in its own field. `C.new(...)` has already stored the fields; initialize only checks or converts them.
```ruby error
class Box
attr_reader value
def initialize(x) = @value = x # !> `@value = x` stores the new Box in its own field: `x` is the instance, not a value
end
7.1.1 No default values
attr_reader items = Any[] is a static error. A field’s first value is set in initialize: it is the one place that runs for each new instance, and it can read the other fields (@len = String.bytesize(@src)). The checker follows the fields through initialize, so a field that initialize always sets is not nil afterwards.
```ruby error class Bag attr_reader items = Any[] # !> a field has no default value; set it in initialize, which runs after Bag.new end
```ruby
class Bag
attr_reader items
def initialize(b)
@items = Any[] if @items == nil
end
def add(b, x) = Array.push(@items, x)
end
b = Bag.new
Bag.add(b, 1)
p(Bag.items(b)) # => [1]
7.1.2 A field named like a reserved word
A field named like a reserved word is declared as a Symbol: attr_accessor :next. Written bare it would not parse. Its reader is Node.next(n), and @next inside the functions works as usual.
class Node
attr_accessor value, :next
def last_value(n)
nx = @next
nx ? last_value(nx) : @value
end
end
list = Node.new(1, Node.new(2, nil))
p(Node.next(Node.next(list))) # => nil
p(Node.last_value(list)) # => 2
7.1.3 One type per construction
For the checker, each C.new is its own type. Instances made at different places keep their own field types, so a Heap of Integers and a Heap of Strings do not mix. When one C.new sits in a function called with different argument types (expect(42) and expect("x")), the types are separate per call too. A generic wrapper (Ok.new(yield(@value)) in Result.map) is one type per caller.
- Messages name such a type with its site (
Heap@L7) only when the type has several. - Values built from values (
Value.new(a + b)inside a function taking Values) stay one type per place.
class Heap
attr_reader items
def push(h, x) = Array.push(@items, x)
def top(h) = Array.first(@items)
end
ints = Heap.new(Any[])
Heap.push(ints, 3)
words = Heap.new(Any[])
Heap.push(words, "job")
p(Heap.top(ints) + 1) # => 4
p(String.upcase(Heap.top(words))) # => "JOB"
7.1.4 class B < A
class B < A is shorthand for writing A’s definitions in B. It is not inheritance.
- A’s fields come first, then B’s.
- A’s functions are B’s too. Inside them, unqualified names and
@xmean B’s. - A’s
includes are B’s. - B’s own definition of a function wins over A’s.
- Nothing relates A and B afterwards. A B is not an A:
A.f(b)is a type error, andb in Ais false. <takes any class of the program, alsoStruct.new(...)written in place (class P < Struct.new(:x)). A module is included withinclude.
class Shape
attr_reader name
def describe(s) = "a shape called " + @name
end
class Circle < Shape
attr_reader r
def area(c) = 3.0 * @r * @r
end
class Square < Shape
attr_reader side
def describe(s) = "a square called " + @name
end
c = Circle.new("c1", 2.0)
p(c) # => #<struct Circle name="c1", r=2.0>
p(Circle.describe(c)) # => "a shape called c1"
p(Circle.area(c)) # => 12.0
p(Square.describe(Square.new("s1", 3.0))) # => "a square called s1"
```ruby error class Shape attr_reader name def describe(s) = @name # !> Shape.name: argument 1 must be Shape, but is Circle [type] end class Circle < Shape attr_reader r end p(Shape.describe(Circle.new(“c”, 2.0)))
### Exception types
`class E < Exception` (or `< StandardError`) declares an exception type: `message` is its first field, then the fields of its `attr_*` lines. The fields after `message` may always be left out: `E.new("msg")` gives the message only. The form `raise E, "msg"` is for an exception type with no fields besides `message` ([Exceptions and errors](08-exceptions.md)).
```ruby
class ParseError < StandardError
attr_reader line
end
begin
raise ParseError.new("bad token", 7)
rescue ParseError => e
p(ParseError.message(e)) # => "bad token"
p(ParseError.line(e)) # => 7
end
begin
raise ParseError.new("no line")
rescue ParseError => e
p(ParseError.line(e)) # => nil
end
7.1.5 Shorthands: Struct.new and Exception.new
Struct.new(:x, :y) is shorthand for class C with attr_accessor x, y. Ruby’s form class C < Struct.new(:x, :y) also works: it declares those fields first, then the body’s attr_* lines and functions. Exception.new(:line) is shorthand for class C < Exception with attr_accessor line.
class Point < Struct.new(:x, :y)
attr_reader label
def to_s(pt) = @label + "(" + Integer.to_s(@x) + ", " + Integer.to_s(@y) + ")"
end
p(Point.to_s(Point.new(1, 2, "P"))) # => "P(1, 2)"
Oops = Exception.new(:line)
begin
raise Oops.new("oops", 3)
rescue Oops => e
p(Oops.line(e)) # => 3
end
7.1.6 Static errors in a declaration
Each of the following is a static error, and the checker’s hint shows the fix.
attr_reader :x: a field that is not a reserved word, written as a Symbol (write it bare).- A field declared in a
class Cafter the first one. - A default value,
attr_reader x = v. - A write from outside the class to a read-only (
attr_reader) field. - The old form
class C < {reader: [...]}; the hint gives theattr_*lines.
7.2 The operations of Struct.new
Point = Struct.new(:x, :y)
This defines the namespace Point with the following operations.
| Operation | Meaning |
|---|---|
Point.new(x, y) |
create; positional arguments, one per field |
Point.x(p), p.Point.x |
read field x (the reader has the field’s name) |
Point.set_x(p, v), p.Point.x = v |
write field x in place; returns v |
p.Point.x += v, \|\|=, &&= |
p.Point.x = p.Point.x + v with p evaluated once |
Point[p1, ...] |
an Array of Point |
p == nil, p != nil |
comparison with nil |
Point = Struct.new(:x, :y)
pt = Point.new(3, 4)
p(pt) # => #<struct Point x=3, y=4>
p(Point.x(pt)) # => 3
p(Point.set_x(pt, 10)) # => 10
pt.Point.y += 1
p(pt) # => #<struct Point x=10, y=5>
pts = Point[Point.new(1, 2), Point.new(3, 4)]
p(pts) # => [#<struct Point x=1, y=2>, #<struct Point x=3, y=4>]
p(pt == nil) # => false
7.2.1 Mutability
Values are mutable and shared by reference. A change made through one variable is visible through every other variable that holds the same value.
Point = Struct.new(:x, :y)
pt = Point.new(3, 4)
q = pt
Point.set_y(q, 0)
p(pt) # => #<struct Point x=3, y=0>
7.2.2 Adding operations
Add your own operations in class Point ... end or with def Point.f. Inside them, the readers and writers can be called unqualified (x(p), set_x(p, v)). A function named like a field (def x(p) = ...) replaces its reader, and one named like its writer (def set_x(p, v)) replaces the writer, also for p.Point.x = v and +=, as a method after attr_accessor does in Ruby; @x still reads and writes the field.
Point = Struct.new(:x, :y)
def Point.shift(pt, dx)
@x += dx
pt
end
class Point
def norm2(pt) = @x * @x + @y * @y
def x(pt) = 100
end
pt = Point.new(3, 4)
p(Point.shift(pt, 1)) # => #<struct Point x=4, y=4>
p(Point.norm2(pt)) # => 32
p(Point.x(pt)) # => 100
7.2.3 Fields by name
Point.field_names is the Array of the fields’ names as Symbols, in order (Ruby’s Struct.members). Point.read_field(p, name) and Point.write_field(p, name, v) read and write the field a Symbol names, as @x does (Ruby’s p[name] and p[name] = v on a Struct). Code that walks every field, such as a conversion to a Hash or from a row of a table, is written once with them.
Point = Struct.new(:x, :y)
pt = Point.new(3, 4)
p(Point.field_names) # => [:x, :y]
p(Array.map(Point.field_names) { |f| Point.read_field(pt, f) }) # => [3, 4]
Point.write_field(pt, :y, 10)
p(Point.read_field(pt, :y) + 1) # => 11
- A literal name is that field:
Point.read_field(pt, :y)has the type ofy, and:zis a static error (“Point has no field :z”). A Symbol narrowed to literals (case name in :x | :y) reaches those fields only. - Any other Symbol reads the union of the fields’ types, and writes the value into every field as far as the checker knows. To keep each field’s type, write in a
caseover the names:in :id then Post.write_field(post, name, Integer(v)). - A Symbol that names no field raises
KeyError. - Outside the class’s functions, the fields a name may reach must have a public reader (
read_field) or writer (write_field). Inside them, every field, private ones too; a base class’s function (class B < A) reads B’s fields withread_field(b, :id)even though A has noid.
7.2.4 Field shorthand @x
Inside a function of a class (in class Point or def Point.f), @x means field x of the function’s first parameter. The first parameter is the subject by convention.
| Written | Means |
|---|---|
@x |
Point.x(p) |
@x = v |
Point.set_x(p, v) |
@x OP= v |
Point.set_x(p, Point.x(p) OP v) |
@x \|\|= v |
Point.x(p) \|\| Point.set_x(p, v) |
pis the first parameter’s current value, even inside a block whose parameter has the same name.- The usual runtime check applies: the first argument must be a Point.
@xis a static error in each of these cases: outside a function of a class, in a function with no parameters, and when the field does not exist.
```ruby error
Point = Struct.new(:x, :y)
def far(pt) = @x > 100 # !> @x means a field of the first argument, so it is only available in a function of a class
### Printing
`p` prints a class instance as `#<struct Point x=1, y=2>`. `puts` prints it the same way.
### Restrictions of Struct.new
`Struct.new` must be assigned to a constant, at the top level or directly in a class or module body (`module Geo` with `Point = Struct.new(:x, :y)` makes `Geo::Point`). It takes symbols only, and no block; functions are defined in `class Point`.
There is no `Data.define`: it is rejected with a hint to use `Struct.new`. Ruby's `Data` is immutable, but Sake's named types are mutable, which is what Ruby's `Struct` provides.
```ruby error
Point = Data.define(:x, :y) # !> Sake's named types are mutable, so they are made with Struct.new, not Data.define
7.2.5 Copying: Kernel.dup
Kernel.dup(x) is a shallow copy: new containers and class instances, the same elements. A type that defines dup gets its own.
Point = Struct.new(:x, :y)
a = Point.new(1, Any[2])
b = Kernel.dup(a)
Point.set_x(b, 9)
Array.push(Point.y(b), 3)
p(a) # => #<struct Point x=1, y=[2, 3]>
p(b) # => #<struct Point x=9, y=[2, 3]>
8. Exceptions and errors
Exceptions in Sake are written as in Ruby: raise throws one, begin/rescue/else/ensure receives it, and retry starts over. Two things differ. An exception type is an ordinary class, and types are grouped by a module, not by inheritance. And the checker tracks, before the program runs, which exceptions may leave each function. The first half of this chapter is how to write exceptions; the second half lists the kinds of static and runtime error. The format of the reports is in Overview.
8.1 Exceptions
ParseError = Exception.new(:line) # fields: message, line
def parse(s)
raise ParseError.new("empty", 1) if s == ""
Integer(s)
end
begin
parse("")
rescue ParseError => e
p(ParseError.line(e)) # => 1
rescue KeyError, IndexError => e
p(Exception.message(e))
rescue => e # any rescuable exception
raise # re-raise
else
p(:ok) # when nothing was raised
ensure
p(:ensure) # => :ensure
end
The shape is Ruby’s. The sections below go through declaring an exception type, the forms of raise and rescue, what the checker looks at, and what happens when nothing catches an exception.
8.1.1 Declaring an exception type
An exception type is a class (Classes). Its first field is message, followed by the fields you declare. As for any other class, Name.new("msg", ...), Name.message(e), Name.field(e), and @field inside a function all work.
class ParseError < Exception
attr_reader line
end
e = ParseError.new("empty", 1)
p(ParseError.message(e)) # => "empty"
p(ParseError.line(e)) # => 1
p(e) # => #<ParseError: empty line=1>
- Two spellings.
class Name < Exceptionwithattr_reader fieldlines, and its shorthandName = Exception.new(:field, ...), declare the same thing. - Grouped by a module.
class B < Ais a copy, andrescue Adoes not catch B. To group exception types, include a module in them and writerescue M(below). The built-in types follow Ruby’s parents:rescue IndexErroralso catchesKeyError,rescue IOErrorEOFError,rescue RangeErrorFloatDomainError, andrescue ArgumentErrorMath::DomainError. A class declared under a built-in exception, such asclass ConvError < RangeError, is caught byrescue RangeErrortoo. - Built-in exception types. These are raised by built-in operations, and each has only
message:RuntimeError,ArgumentError,TypeError,KeyError,IndexError,ZeroDivisionError,RangeError,IOError,EOFError,RegexpError,FloatDomainError,EncodingError,ThreadError,NoMatchingPatternError,Math::DomainError. Each is described in the reference’s Exceptions.
8.1.2 Forms of raise
raise is syntax, not an operation. It has four forms.
| Written | Raises |
|---|---|
raise "msg" |
RuntimeError |
raise T, "msg" |
T.new("msg"); T has no fields besides message |
raise value |
that exception value |
raise (inside a rescue clause) |
the exception being handled, again |
begin
raise "boom"
rescue => e
p(e) # => #<RuntimeError: boom>
end
begin
raise KeyError, "no such key"
rescue KeyError => e
p(e) # => #<KeyError: no such key>
end
begin
raise IndexError.new("out")
rescue IndexError => e
p(Exception.message(e)) # => "out"
end
A type with fields besides message cannot be raised with raise T, "msg". Build the value and write raise T.new(...).
```ruby error
E = Exception.new(:code)
raise E, “msg” # !> E has fields besides message; raise it with raise E.new(...)
### rescue
`rescue A, B => e` catches the listed types. A name that is a module catches the exception types that include it, and a built-in parent (`IndexError` and the others) also catches its Ruby subclasses.
To catch everything, write `rescue => e`. `rescue StandardError => e` and `rescue Exception => e` mean the same. The type of `e` is then a union, so to handle each type differently, narrow it with `case e in A ...` ([Control flow and patterns](06-control.md)).
```ruby
def check(x)
raise ArgumentError, "negative" if x < 0
x
end
begin
check(-1)
rescue ArgumentError, ZeroDivisionError => e
v = case e
in ArgumentError then "argument"
in ZeroDivisionError then "zero"
end
p(v) # => "argument"
end
8.1.3 Grouping with a module
A Ruby library makes its exceptions children of class Error < StandardError and catches them together with rescue MyLib::Error. In Sake the parent is a module that each exception type includes. rescue M => e catches every exception type that includes M (also through a module that includes M), and e is their union. A function of the module, M.f(e), dispatches to each type’s definition.
module ParseFailure
def describe(e) = "parse: " + Exception.message(e)
end
class BadToken < StandardError
include ParseFailure
end
class Unclosed < StandardError
include ParseFailure
attr_reader line
end
def parse(s)
raise BadToken, "bad token" if s == "?"
raise Unclosed.new("unclosed", 3) if s == "("
s
end
begin
parse("(")
rescue ParseFailure => e
p(ParseFailure.describe(e)) # => "parse: unclosed"
case e
in Unclosed then p(Unclosed.line(e)) # => 3
in BadToken then p("token")
end
end
A module is not raised (raise ParseFailure, "x" is a static error). When the Ruby code raises the parent class itself, add one concrete type that includes the module. Level 1 stops a rescue A for a type an earlier rescue M already catches; a rescue M whose begin body raises none of the module’s types is an unused-rescue warning.
```ruby error module ParseFailure end class BadToken < StandardError include ParseFailure end def parse(s) raise BadToken, “bad token” if s == “?” s end begin parse(“?”) rescue ParseFailure => e p(1) rescue BadToken => e # !> rescue BadToken: an earlier rescue (ParseFailure) already catches it [rescue] p(2) end
### Reading the message
`Exception.message(e)` reads the message of an exception value of any type. Use it when the type of `e` is a union. When the type is known, `ParseError.message(e)` does the same.
The message of an exception raised by a built-in operation is Ruby's text. The report of one that ends the program unrescued also names the operation (`ZeroDivisionError: Arithmetic./: divided by 0`).
```ruby
begin
p(1 / 0)
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
8.1.4 Backtrace and cause
Exception.backtrace(e) gives the calls in effect when the exception was raised, as an Array of Strings in Ruby’s form "FILE:LINE:in 'FUNCTION'", innermost first. It is nil for an exception value that was never raised (made with E.new only). Raising it again with raise e or a bare raise keeps the backtrace of the first raise.
Exception.cause(e) gives the exception a rescue clause was handling when e was raised, or nil. Both may be nil, so under --strict check them before use.
Unlike Ruby, lines inside built-in operations (Ruby’s 'Integer#/' and the like) are not in the list.
def parse(s)
Integer(s)
rescue ArgumentError
raise "not a number: #{s}"
end
begin
parse("x")
rescue => e
bt = Exception.backtrace(e)
p(Array.map(bt) { |l| String.sub(l, /\A.*?:/, "") }) if bt # => ["4:in 'parse'", "8:in '<main>'"]
c = Exception.cause(e)
p(c) if c # => #<ArgumentError: invalid value for Integer(): "x">
end
p(Exception.backtrace(RuntimeError.new("not raised"))) # => nil
8.1.5 Program errors
SystemStackError, LocalJumpError, and NotImplementedError are program errors and cannot be rescued. Naming one in rescue is a static error.
```ruby error begin p(1) rescue SystemStackError => e # !> SystemStackError cannot be rescued: it is a program error, which the checks before running report p(e) end
`TypeError` (an operation given a value of the wrong type) and `NoMatchingPatternError` can be rescued, as in Ruby. Most of the type mistakes they report, however, are stopped by the checks before running ([Overview](01-overview.md)). The next example stops before running at the default level, as a `type` problem. Run with `--strict=0`, `len(1)` raises a `TypeError`, which is rescued like any other exception, and the message is `"argument 1 must be String, got Integer"`.
```ruby error
def len(x)
String.length(x) # !> String.length: argument 1 must be String, but is Integer [type]
end
begin
len(1)
rescue TypeError => e
p(Exception.message(e))
end
8.1.6 Other forms
def f ... rescue ... end, the modifier expr rescue fallback, and retry work as in Ruby. ensure runs once, when the begin block is left. It runs when return or exit leaves the block, too.
def safe_div(a, b)
a / b
rescue ZeroDivisionError
0
end
p(safe_div(6, 0)) # => 0
n = Integer("x") rescue -1
p(n) # => -1
tries = 0
begin
tries += 1
raise "again" if tries < 3
rescue RuntimeError
retry
end
p(tries) # => 3
8.1.7 Checking the flow of exceptions
The checker tracks which explicitly raised types may leave each function. Two items use this (the levels are in Overview).
unused-rescue(a warning at levels 1 to 4; level 0 runs no type inference). The checker reports a rescue clause for a type that the begin body never raises. Built-in kinds such asZeroDivisionErrorcan come from ordinary operations, so they are always assumed possible and never reported. The program still runs: a defensiverescueis normal, and whether anything raises depends on the whole program (a library’srescuefor what only some apps raise).--strict=2,unused-rescuemakes it an error. The relatedrescueitem (level 1) stops a clause an earlier clause already covers.unrescued(level 4). The checker reports araisethat may reach the top level without being rescued, asraise: RuntimeError may reach the top level without being rescued [unrescued].
ParseError = Exception.new(:line)
begin
p(Integer("1")) # => 1
rescue ParseError => e # warning: the begin body never raises ParseError [unused-rescue]
p(e)
end
8.1.8 Uncaught exceptions
An exception nothing rescues ends the program in the same form as a runtime error: FILE:LINE: in FUNCTION: ParseError: message. The exit status is 1.
```ruby error def check(x) raise ArgumentError, “x must be positive” if x <= 0 # !> ArgumentError: x must be positive x end check(0)
### Deadlock
When every thread is waiting, the program ends with Sake's `ThreadError`. A `Queue.pop` with nothing left to push, a `Mutex.synchronize` taken again inside itself, and `Thread.join`s waiting for each other are the cases ([Built-in operations](09-builtins.md)).
```ruby error
q = Queue.new
Queue.pop(q) # !> ThreadError: deadlock: every thread is waiting
8.2 Kinds of static error
Static errors are reported all together, sorted by position, and nothing runs. The exit status is 2. The format is in Overview.
| Kind | Example |
|---|---|
| syntax error | anything Prism cannot parse |
| undefined type, operation, or function | String.upcse(s) |
| wrong argument count | |
| block mismatch | a block passed where none is taken, or missing where one is required |
| a call on a value | name.upcase |
| forbidden construct | see below |
| unsupported syntax | appendix Differences from Ruby |
| duplicate definition | |
literal type mismatch in T[...] |
Integer["a"] |
an item selected by --strict |
by default, a value whose type does not fit |
The forbidden constructs are send, public_send, __send__, method_missing, define_method, the eval family, instance_variable_get/set, const_get/set, binding, self, and @x outside a function of a class. Each either leaves the callee undecided until run time or names a concept Sake does not have (instance state).
8.3 Kinds of runtime error
A runtime error stops the program at the line of the operation. The exit status is 1. Each is an exception, so it can be rescued. The exceptions are SystemStackError and the deadlock detected when every thread is waiting (the ThreadError of re-entering Mutex.synchronize can be rescued).
| Kind | Raised by |
|---|---|
TypeError |
an operation given a value of the wrong type (see below) |
IndexError |
an index outside the bounds (see below) |
ArgumentError |
block parameter count; negative sizes; Integer ** negative; comparing incomparable values in sort |
ZeroDivisionError |
Integer / or % by zero |
KeyError |
Hash.fetch of a missing key; a Record pattern naming a missing field |
RangeError |
an operation that needs a finite Range, given an endless one |
RegexpError |
Regexp.new with an invalid pattern |
IOError |
a file, directory, or socket operation failing (see below) |
EOFError |
raised by no built-in (see below) |
EncodingError |
an operation on Strings whose encodings do not fit |
FloatDomainError |
converting NaN or Infinity to Integer |
Math::DomainError |
e.g. Math.sqrt(-1) |
SystemStackError |
recursion deeper than 10,000 |
ThreadError |
deadlock |
TypeError. An operation received a value of the wrong type. An operator not supported by the left operand’s type. A write of another type into a typed Array. Multiple assignment from a value other than a Tuple or an Array.IndexError. A Tuple index outside the Tuple.Array.fetchoutside the Array. Writing past the end of an Array of T (xs[5] = 3; the gap would be nil).IOError.File.read,Dir.mkdir, and the like failing. In Ruby these areErrno::ENOENTand co., and the message is Ruby’s. Socket errors (refused, reset, unknown host) areIOErrortoo.EOFError. No built-in raises it. A read at the end gives nil or"", and Ruby’s EOFError is folded intoIOError. The type exists, so it can be named inrescue.
ruby error
h = Hash.of("a" => 1)
p(Hash.fetch(h, "b")) # !> KeyError: Hash.fetch: key not found: "b"
9. Built-in operations
There are about 550 built-in operations, and their names are those of Ruby’s core library. This chapter is a guide by namespace: what each type is for, the two or three operations you reach for first, a short example, and a link to the chapter with the details. Every operation’s exact signature, arguments, nil cases, exceptions, and examples are in Part 2, the built-in reference, one chapter per type (Array, String, Hash, Kernel, …).
The table in each section groups the operations by what you want to do; the “Result” column gives the result type. Operations marked “block” require one. [x] is an optional argument. The rules for operators (+, <, []) are in Operators and indexing; the properties of each type’s values are in Values and types.
9.1 Kernel
Kernel is the set of operations callable by bare name from anywhere in the program. You write puts(x), Integer(s), loop { }, and need not write Kernel.puts(x). The first ones you use are p and puts for output, Integer(s) to convert a string to a number, and loop to repeat. Details: Kernel.
puts("total:", 3)
# => total:
# => 3
p(format("%05.1f", 3.14159)) # => "003.1"
p(Integer("42") + 1) # => 43
x = loop { break 7 }
p(x) # => 7
p(Math.PI > 3) # => true
| Operation | Result | Notes |
|---|---|---|
puts(*xs) |
nil | prints like Ruby’s puts; Arrays and Tuples print one element per line |
print(*xs) |
nil | no newline |
p(x), p(x, y), pp(x) |
x · [x, y] · x |
prints each argument in Ruby’s inspect format, one per line; p() gives nil |
format(fmt, *xs), sprintf |
String | Ruby’s format; arguments are Integer, Float, String, Symbol, nil, true, false |
Integer(x), Float(x), Rational(a, [b]), Complex(re, [im]) |
the number | Ruby’s strict conversions; ArgumentError on bad input |
rand, rand(n), srand(seed) |
Float, or Integer/Float below n | |
sleep(secs) |
Integer | |
loop { } |
the value of a break |
runs the block until a break |
dup(x) |
a copy of x | Ruby’s obj.dup; a type that defines dup gets its own |
gets |
String or nil | a line of standard input |
exit(status) |
Integer, or true for 0 / false for 1 (default 0); ensure clauses run; code after it is not reached |
|
warn(x, ...) |
nil | to the error stream, as puts does to the output |
system(cmd) |
true / false / nil | Ruby’s system |
at_exit { } |
nil | a block run when the program ends |
ARGV |
Array of String | the program’s arguments; written like Ruby’s constant, but an operation (Kernel.ARGV); every use gives the same Array, so Array.shift(ARGV) works |
Kernel.PROGRAM_NAME |
String | the running file’s name; unlike ARGV it cannot be written bare (a capitalized name is a type) |
__FILE__, __LINE__, __dir__ |
String, Integer, String | this file (the main file as given, a required file’s full path), this line, and this file’s directory as a full path, as Ruby’s: File.join(__dir__, "views") |
equal?(a, b) |
true/false | identity |
block_given? |
true/false | whether the current function got a block |
- Constants are operations.
Math::PI,Math::E,Float::INFINITY,Float::NAN,Float::EPSILON,Float::MAX,Float::MINare read as operations (Math.PI), asARGVis. Sake has no value constants, and no other built-in constants (a nested name such asA::Bis a namespace, never a value).
9.2 Integer / Float
Integer is an integer without an upper bound; Float is a double-precision floating-point number. Arithmetic and comparison are written with operators; conversions such as Integer.to_s(n, 16) and iterations such as Integer.times(n) { } are called with the type. The first ones you use are / and % (integer division between Integers), Float.round, and Integer.times. Details: Integer, Float, Arithmetic.
p(7 / 2) # => 3
p(7.0 / 2) # => 3.5
p(Integer.divmod(7, 2)) # => [3, 1]
p(Integer.to_s(255, 16)) # => "ff"
p(Float.round(3.14159, 2)) # => 3.14
Integer.times(3) { |i| print(i) }
puts # => 012
| Operation | Result |
|---|---|
+ - * / % ** |
Integer (Float with a Float) |
< <= > >= == != |
true/false |
Integer.to_s(n, base = 10), to_f, abs, succ, pred |
String, Float, Integer, Integer, Integer |
even?, odd?, zero? |
true/false |
times(n) { \|i\| } · upto(a, b) { } · downto(a, b) { } · step |
n · a · a (block) |
& \| ^ << >> |
Integer |
divmod(a, b) |
[quotient, remainder] Tuple ([Integer, Float] for a Float) |
gcd, lcm, pow(a, b, [mod]), bit_length, sqrt(n), clamp(n, lo, hi) |
Integer |
digits · chr · between?(n, lo, hi) |
Array of Integer · String · true/false |
Float.to_i, floor, ceil, truncate · round(f) / round(f, digits) |
Integer · Integer / Float |
nan? · finite? · infinite? |
true/false · true/false · 1, -1, or nil |
Float.to_r, rationalize |
Rational |
- Rounding any real number.
Arithmetic.round(x),floor,ceil,truncate(with an optional digit count),abs,to_f,to_i,zero?take any real number (Integer, Float, Rational), as Ruby’sx.rounddoes; the result’s type follows x’s (round without digits gives an Integer).Float.roundnames a Float only.
9.3 String
A String is a sequence of characters with an encoding, and it is mutable. An operation whose name ends in ! changes the subject in place; the others return a new String. The first ones you use are String.split, String.sub/gsub, String.include?, and + to concatenate. Details: String.
s = "Hello, World"
p(String.length(s)) # => 12
p(String.upcase(s)) # => "HELLO, WORLD"
p(String.split(s, ", ")) # => ["Hello", "World"]
p(String.sub(s, "World", "Sake")) # => "Hello, Sake"
p(String.include?(s, "World")) # => true
p(String.index(s, "x")) # => nil
p(s + "!") # => "Hello, World!"
| Purpose | Operation | Result |
|---|---|---|
| concatenate, repeat, format | +(a, b), *(s, n), % |
String |
| compare | == != < <= > >= |
true/false |
| length and numbers | length, size, count(s, chars), to_i(s, base = 10) · to_f |
Integer · Float |
| transform | upcase, downcase, capitalize, swapcase, reverse, strip, lstrip, rstrip, chomp, chop, to_s |
String |
| replace | sub(s, pat, repl), gsub(...) |
String; sub!, gsub! in place |
| pad and swap characters | ljust(s, n, [pad]), rjust, center · tr(s, a, b), delete(s, chars), squeeze(s, [chars]), succ, next |
String |
| query | empty?, include?(s, t), start_with?, end_with?, match?(s, re), casecmp? |
true/false |
| split | chars, lines, bytes, split(s, [sep, [limit]]), scan(s, re) |
Array |
| positions | index(s, t, [start]), rindex, byteindex |
Integer or nil |
| regexp match | match(s, re, [pos]) |
MatchData or nil |
| to other types | ord · hex · oct · to_sym, intern |
Integer · Symbol |
| bytes and conversions | byteslice(s, i, [n]), b, force_encoding(s, enc), encode, encoding, valid_encoding?, unpack(s, fmt), unpack1 |
as Ruby’s |
| iterate | each_line(s) { }, each_char(s) { }, each_byte |
s (block) |
- The repl of a replacement. The repl of
subandgsubis a String (with\1), a Hash (match => replacement), or a block{ |m| ... }.
9.4 Array
An Array is a sequence whose length varies. Any[1, 2] makes one (the literal [1, 2] is a Tuple). The first ones you use are Array.push, Array.each, Array.map, and Array.select. Details: Array.
xs = Any[3, 1, 2]
Array.push(xs, 5)
p(xs) # => [3, 1, 2, 5]
p(Array.map(xs) { |x| x * 2 }) # => [6, 2, 4, 10]
p(Array.select(xs) { |x| x > 1 }) # => [3, 2, 5]
p(Array.sort(xs)) # => [1, 2, 3, 5]
p(Array.reduce(xs, 0) { |acc, x| acc + x }) # => 11
p(Array.first(xs)) # => 3
p(Array.join(xs, "-")) # => "3-1-2-5"
p(Array.tally(Any["a", "b", "a"])) # => {"a" => 2, "b" => 1}
| Purpose | Operation | Result |
|---|---|---|
| make | Any[...] · Array.new(n), Array.new(n, v), Array.new(n) { \|i\| } |
a new Array |
| length and query | length, size · empty?, include?(a, x) |
Integer · true/false |
| add and remove (in place) | push(a, *xs), append, unshift, concat(a, b), insert(a, i, *xs), delete_if, clear |
a |
| one element | first, last, min, max, pop, shift, at(a, i), sample, delete(a, x), delete_at(a, i) |
an element, or nil |
| a part | first(a, n), last(a, n), shift(a, n), take, drop, slice(a, i, n) |
a new Array |
| fail when missing | fetch(a, i, [default]) |
the element, the default, or IndexError |
| to a String | join(a, [sep]) |
String |
| sum | sum(a, [init]) |
the sum (see below) |
| reorder and the like | reverse, sort, sort_by, uniq, compact, flatten, rotate, shuffle, dup |
a new Array |
| iterate | each, each_with_index, each_slice(a, n), each_cons(a, n), cycle |
a (block) |
| map and filter | map, flat_map, select, filter, reject, partition, group_by, tally, to_h, zip, product |
a new Array / Hash / Tuple |
| test and count | any?, all?, none?, count |
true/false · Integer (block optional) |
| fold | reduce(a, init) { }, inject, each_with_object(a, memo) { } |
the accumulated value |
| search | find, detect, min_by, max_by, index(a, x), find_index, assoc, rassoc |
an element or nil · Integer or nil |
| rewrite (in place) | map!, select!, reject!, sort!, uniq!, compact!, flatten!, reverse!, slice!, fill, replace |
a |
| to bytes | pack(a, fmt) |
String |
sum. The elements are numbers, or of a type includingArithmetic. An empty Array givesinit(default 0).- Iteration without a block.
each_sliceand the like, called without a block, give an Array. - Rewriting in place.
map!and the others are for mappings within one type.
9.5 Enum
Enum is Ruby’s Enumerable under a short name, defined by the prelude (sakelib/prelude.sake), which is read before every program. Enum.map(x) { } and the others dispatch on the type of the first argument: an Array, Hash, Set or Range answers with its own operation (Enum.map(xs) is Array.map(xs) when xs is an Array). A class of your own joins with include Enum and def each(c) = ... yield(v) ..., and gets the rest (map, select, reject, find, reduce, sum, count, include?, any?/all?/none?/one?, min_by/max_by, sort_by, to_a, first, take/drop, group_by, partition, each_with_object, compact, filter_map, flat_map), which are written over each. Details: Enum.
p(Enum.map(Any[3, 1, 2]) { |v| v * 10 }) # => [30, 10, 20]
p(Enum.map(Hash.of(a: 1, b: 2)) { |k, v| "#{k}=#{v}" }) # => ["a=1", "b=2"]
p(Enum.count(1..4) { |v| v > 2 }) # => 2
class Deck
include Enum
attr_reader cards
def each(d) = Array.each(@cards) { |c| yield(c) }
end
d = Deck.new(Any["A", "K", "Q"])
p(Deck.select(d) { |c| c < "K" }) # => ["A"]
p(Enum.first(d), Deck.count(d)) # => "A"
# => 3
Tuple does not include Enum: its length and position types are fixed, so it is indexed and destructured instead.
9.6 Tuple, Range
A Tuple is a sequence with a fixed length and a type per position; the literal [1, "a"] makes one. A Range is a pair of ends: 1..5, 1...5, and the endless 1... Tuples have few operations, and the usual move is Tuple.to_a to get an Array. For Ranges, Range.each counts, and Range.to_a and Range.include? are the common ones. Details: Tuple, Range.
t = [1, "a"]
p(Tuple.size(t)) # => 2
p(Tuple.to_a(t)) # => [1, "a"]
p(Tuple.max([3, 7, 5])) # => 7
p(Range.to_a(1..5)) # => [1, 2, 3, 4, 5]
p(Range.sum(1..5)) # => 15
p(Range.include?(1...5, 5)) # => false
p(Range.map(1..3) { |i| i * i }) # => [1, 4, 9]
p(Range.first(1.., 3)) # => [1, 2, 3]
| Operation | Result |
|---|---|
Tuple.size, length · to_a · max, min · minmax |
Integer · Array · an element (never nil) · [min, max] |
Range.each, each_with_index, step(r, n) |
r (block) |
to_a, map, select, filter, reject, zip |
Array |
reduce, inject · sum · size · count |
accumulated value · Integer |
any?, all?, none? · find, detect · include?, cover?, member?, exclude_end? |
true/false · an element or nil · true/false |
first, last, min, max, begin, end |
an element or nil (first(r, n), last(r, n) give an Array) |
9.7 Hash, Set
A Hash maps keys to values; Hash.of("a" => 1) or Hash.new(default) makes one ({a: 1} is a Record). A Set is a collection without duplicates, made with Set.of(1, 2). The first ones you use are the index h[k] and h[k] = v, Hash.each, Hash.fetch, and Set.add and Set.include?. Details: Hash, Set.
h = Hash.of("a" => 1)
h["b"] = 2
p(h) # => {"a" => 1, "b" => 2}
p(h["c"]) # => nil
p(Hash.fetch(h, "a")) # => 1
p(Hash.map(h) { |k, v| [k, v * 10] }) # => [["a", 10], ["b", 20]]
counts = Hash.new(0)
counts["x"] += 1
p(counts) # => {"x" => 1}
s = Set.of(1, 2)
Set.add(s, 2)
p(s) # => Set[1, 2]
p(Set.include?(s, 2)) # => true
p(Set.union(s, Set.of(9))) # => Set[1, 2, 9]
| Purpose | Operation | Result |
|---|---|---|
| make | Hash.of(k => v, ...) · Hash.new([default]) · Set.of(...) |
a new Hash · Set |
| length and query | length, size, empty?, key?, has_key?, include?, member?, value? |
Integer · true/false |
| one entry | fetch(h, k, [default]) · dig(h, k) · delete(h, k) · key(h, v) |
the value · value or nil · removed value or nil · key or nil |
| rewrite (in place) | store(h, k, v), merge!, update, clear, transform_values!, transform_keys!, select!, reject!, compact! |
h |
| a new Hash | merge(a, b), invert, select, filter, reject, transform_values, transform_keys, compact, slice, except |
a new Hash |
| to an Array | keys · values · to_a · map · sort_by · group_by · partition |
Array |
| iterate | each, each_pair, each_key, each_value |
h (block) |
| test and search | any?, all?, none?, count, sum, find, detect, min_by, max_by |
with a block |
| rewrite a Set (in place) | Set.add(s, x), add?, delete, merge, subtract, clear |
s |
| Set to Array | to_a, each, map, select, filter, reject |
Array |
| set operations | union, intersection, difference, \|, &, - · subset?, superset?, disjoint?, intersect? |
a new Set · true/false |
9.8 Regexp, MatchData, Symbol, Time, Math
A Regexp is a regular expression, made by the literal /\d+/. The result of a match is a MatchData, or nil when nothing matched. A Symbol is a name as a value (:name), a Time is one instant, and Math is the set of elementary functions. The first ones you use are Regexp.match and m[1], String.scan, Time.now, and Math.sqrt. Details: Regexp, MatchData, Symbol, Time, Math.
m = Regexp.match(/(\d+)-(\d+)/, "tel 03-1234")
if m
p(m[1]) # => "03"
p(MatchData.captures(m)) # => ["03", "1234"]
end
p(String.scan("a1 b22", /\d+/)) # => ["1", "22"]
p(Symbol.to_s(:name)) # => "name"
t = Time.at(0, in: "UTC")
p(Time.year(t)) # => 1970
p(Time.strftime(t, "%Y-%m-%d")) # => "1970-01-01"
p(Math.sqrt(16)) # => 4.0
| Operation | Result |
|---|---|
Regexp.new(s, flags = "") · escape(s) · source(r) · match(r, s, [pos]) · match? |
Regexp · String · String · MatchData or nil · true/false (flags: letters of imx) |
MatchData.captures · named_captures · names · to_a · to_s, pre_match, post_match · begin(m, i), end(m, i) |
Array · Hash · Array · Array · String · Integer |
Symbol.to_s · length, size |
String · Integer |
Time.now · at(seconds, [in:]) · new(y, [m, d, h, min, s, zone]) · utc(t), getutc · getlocal(t, [zone]), localtime |
Time |
year, month, day, hour, min, sec, wday, yday, to_i, utc_offset · to_f · to_s, strftime(t, fmt) · zone · utc? |
Integer · Float · String · String or nil · true/false |
Math.sqrt, cbrt, sin, cos, tan, atan, exp, log, log2, log10, atan2(y, x), hypot(x, y) |
Float |
- No
Symbol.to_proc. A block is not a value, so&:namecannot be written (Functions and blocks).
9.9 Rational, Complex
A Rational is a rational number, always reduced (1/3r); a Complex is a complex number (1 + 2i). The arithmetic operators work on both, and Rational(1, 3) and Complex(1, 2) are Kernel operations. Details: Rational, Complex.
r = Rational(1, 3)
p(r + 1/6r) # => (1/2)
p(Rational.numerator(r)) # => 1
p(Rational.to_f(r)) # => 0.3333333333333333
c = Complex(1, 2)
p(c * c) # => (-3+4i)
p(Complex.abs(Complex(3, 4))) # => 5.0
p(Integer.fdiv(7, 2)) # => 3.5
| Operation | Result |
|---|---|
Rational(a, [b]) · Integer.to_r · Float.to_r · Float.rationalize |
Rational |
Rational.numerator, denominator, to_i, floor, ceil, round, truncate · to_f · abs |
Integer · Float · Rational |
Complex(re, [im]) · Complex.real, imaginary · abs, arg · conjugate · rectangular · polar |
Complex · a real number · Float · Complex · [re, im] · [abs, arg] |
Integer.fdiv(a, b) |
Float |
9.10 File, Dir, IO, ENV, Process
File and Dir are sets of operations on files and directories by path (a String); they have no values. IO is the type of open files and of the standard streams (IO.stdin, IO.stdout, IO.stderr). ENV is the environment, Process the process’s information and clocks, Open3 child processes, and Zlib compression. The first ones you use are File.read, File.write, File.open(path) { |io| }, and Dir.glob. Details: File, Dir, IO, ENV, Process, Open3, Zlib.
dir = Dir.mktmpdir
path = File.join(dir, "notes.txt")
p(File.write(path, "one\ntwo\n")) # => 8
p(File.readlines(path)) # => ["one\n", "two\n"]
File.open(path, "a") { |io| IO.puts(io, "three") }
p(Array.length(File.readlines(path))) # => 3
p(Dir.children(dir)) # => ["notes.txt"]
p(ENV.fetch("NO_SUCH_VAR", "default")) # => "default"
out, status = Open3.capture2("echo", "hi")
p(out) # => "hi\n"
File.delete(path)
Dir.rmdir(dir)
| Operation | Result |
|---|---|
File.read(path) · readlines · write(path, s) · exist?, file?, directory?, symlink?, zero?, empty?, readable?, writable?, executable?, absolute_path? |
String · Array · Integer · true/false |
File.size, mtime, atime, ftype · rename, symlink, link, readlink, unlink, delete, chmod, utime |
Integer · Time · String · … |
File.basename(p, [suffix]), dirname(p, [levels]), extname, join(*parts), expand_path(p, [dir]), absolute_path, realpath · split(p) |
String · [dir, base] |
File.open(path, mode = "r", [perm]) · File.open(...) { \|io\| } |
IO · the block’s value (the file is closed) |
Dir.children, entries · glob(pat, [base:]) · each_child(path) { } · exist?, empty? · mkdir(path, [mode]), rmdir · pwd, home, tmpdir · mktmpdir([prefix, [dir]]) |
Array of String · … |
IO.puts(io, ...), print, write(io, s) · gets(io), read(io, [n]), readlines, getc · each_line(io) { } · eof?, closed?, tty? · flush, close · seek(io, off, [whence]), pos, rewind, truncate, size |
nil · String or nil · … |
ENV.fetch(name, [default]), get(name), set(name, v), key?, delete, to_h, keys |
String · … |
Process.clock_gettime(Process.CLOCK_MONOTONIC), pid |
Float · Integer |
Open3.capture2(cmd, ...), capture2e, capture3 |
Tuples such as [out, status] |
Zlib.inflate, deflate, gzip, gunzip |
String |
- Closed IO. Reading from or writing to a closed IO, or one not opened for it, raises
IOError. - Order with stderr. Writing to
IO.stderrflushes stdout first, so their lines keep their order.
9.11 Threads and sockets
A Thread runs a block concurrently, a Mutex gives mutual exclusion, and a Queue passes values between threads. TCPServer and Socket are TCP listening and connections. The first ones you use are Thread.new { } and Thread.value, Mutex.synchronize, and Queue.push/pop. Details: Thread, Mutex, Queue, TCPServer, Socket.
count = 0
m = Mutex.new
ts = Array.map(Any[1, 2, 3]) { |w| Thread.new { Mutex.synchronize(m) { count += w }; w * 10 } }
p(Array.map(ts) { |t| Thread.value(t) }) # => [10, 20, 30]
p(count) # => 6
q = Queue.new
producer = Thread.new { Range.each(1..3) { |i| Queue.push(q, i) }; Queue.close(q) }
got = Any[]
loop do
x = Queue.pop(q)
break if x == nil
Array.push(got, x)
end
Thread.join(producer)
p(got) # => [1, 2, 3]
| Operation | Result |
|---|---|
Thread.new { ... } · Thread.value(t) · join(t, [limit]) · alive?(t) · current · kill(t) · raise(t, msg) |
a Thread · the block’s value (waits; an error in the thread is raised here) · t or nil · true/false |
Mutex.new · synchronize(m) { } · lock, unlock, try_lock, locked?, owned? |
a Mutex · the block’s value |
Queue.new · push(q, x) · pop(q, [timeout]) · close, size, empty?, closed? |
a Queue · q · the oldest element (waits; nil once closed and empty) |
TCPServer.new(host, port) · accept(s) · port(s) · close(s) |
a TCPServer (port 0 picks a free one) · a Socket (waits) · Integer · nil |
Socket.connect(host, port, [timeout]) · connect_ssl(host, port, [timeout]) · set_timeout(s, secs) |
a Socket (connect_ssl: TLS, the peer verified) |
Socket.gets(s) · read(s, n) · write(s, str) · close_write(s) · close(s) |
String or nil · String or nil · Integer · nil · nil |
- Variables in a thread. A block shares the variables around it, as every block does. For a thread, the variables of the blocks around
Thread.new(their parameters and locals), and the thread block’s own, are copied when the thread starts; the function’s variables stay shared. SoArray.map(xs) { |w| Thread.new { ... w ... } }in the example gives each thread its ownw, and the shared countercountis a function variable updated insideMutex.synchronize. - Leaving a thread block.
breakandreturnout of aThread.newblock (andreturnin a block inside it) are static errors, since the block runs after the call has returned;next vends it withv. - Errors. An error inside a thread is raised by
Thread.valueorThread.join. A thread that is never joined ends silently when it fails, and every thread stops when the main program ends. - Checking. The typer runs a thread’s block once, at
Thread.new: its value is the type ofThread.value; a Queue’s element type is the union of what is pushed (Queue.popadds nil). Interleavings are not analyzed; each operation still checks its arguments while running.
9.12 Typed arrays
Integer[...], Float[...], Rational[...], Complex[...], String[...], Symbol[...], Tuple[...], and D[...] for each class D create an Array whose element type is that type (Values and types). Every write is checked against the element type: a value of another type is a type problem statically and a TypeError at run time.
xs = Integer[1, 2]
Array.push(xs, 3)
p(xs) # => [1, 2, 3]
Point = Struct.new(:x, :y)
pts = Point[Point.new(0, 0)]
p(Array.length(pts)) # => 1
p(String[]) # => []
ruby error
xs = Integer[1, 2]
Array.push(xs, "a") # !> Array.push: an element must be Integer, but is String [type]
10. The library (sakelib)
The interpreter ships with sakelib/, Ruby’s standard library and the main gems rewritten in Sake. This chapter covers how a library is loaded, what has been ported, the test framework minitest, and how each port is checked. Each library’s API is in sakelib/notes/NAME.md; this chapter is the overview.
10.1 Loading and names
A library is loaded with require "json". The names of the libraries and of their operations follow Ruby’s, so the names you know from Ruby work as they are. Only the shape of the call differs.
- Where it looks.
require "name"first looks forname.sakein the directory of the requiring file. When there is none, it readssakelib/name.sake, shipped with the interpreter. The sakelib copy is also taken when thename.sakenext door is the requiring file itself, or a program rather than a library. From whatever directory the program runs,require "json"reads the samesakelib/json.sake. - Names with
/. Ruby’snet/httpandopen-uriare writtennet_httpandopen_uri. - Module functions as in Ruby.
JSON.parse(s)andBase64.encode64(s)are spelled as in Ruby. - Instance methods take the subject first. Ruby’s
ss.scan(/\w+/)isStringScanner.scan(ss, /\w+/)(Program structure and name resolution). - Nested names are Ruby’s. Namespaces nest (Program structure and name resolution), so
Net::HTTP,Concurrent::Future,JSON::ParserErrorandBenchmark::Tmsare spelled as in Ruby:Net::HTTP.get(http, path),rescue JSON::ParserError. Only a nested name whose outer name is a built-in type cannot exist (there is noEnumerable, so ActiveSupport’sEnumerable::SoleItemExpectedErroris the top-levelSoleItemExpectedError). Each library’s notes have the table.
require "base64"
require "strscan"
e = Base64.encode64("hello")
p(e) # => "aGVsbG8=\n"
p(Base64.decode64(e)) # => "hello"
ss = StringScanner.new("foo bar")
p(StringScanner.scan(ss, /\w+/)) # => "foo"
p(StringScanner.scan(ss, /\w+/)) # => nil
p(ss.StringScanner.scan(/\s+/)) # => " "
Each library’s notes are in sakelib/notes/NAME.md: what differs from Ruby and why, what is missing, and a table of Ruby’s API against Sake’s. The libraries not ported, with the reason, are in sakelib/notes/not-ported.md.
10.2 Ports of the standard library
The parts of Ruby’s standard library that can be written in Sake are ported. Most of what cannot rests on method_missing, define_method, special variables or first-class blocks, which Sake leaves out on purpose. What needs the OS or a C implementation, such as TLS, UDP and the terminal, is built into the interpreter rather than written as a library.
require "json"
doc = JSON.parse("{\"name\": \"ann\", \"tags\": [\"a\", \"b\"]}")
p(doc) # => {"name" => "ann", "tags" => ["a", "b"]}
if doc in Hash
p(doc["name"]) # => "ann"
puts(JSON.generate(doc)) # => {"name":"ann","tags":["a","b"]}
end
JSON.parse returns one of Hash, Array, String, Integer, Float, true/false and nil, so narrow it with if doc in Hash before indexing (Control flow and patterns). Written without the narrowing, doc["name"] makes the checker report that the receiver may be a type that cannot be indexed.
| Area | Libraries |
|---|---|
| Data formats | json, csv, yaml, toml, ini, base64, digest, zlib, xml, erb, mustache |
| Strings and parsing | strscan, optparse, getoptlong, shellwords, abbrev, prettyprint, pp, text, diff, terminal_table |
| Numbers | bigdecimal, matrix, prime, securerandom, units |
| Time | time, date |
| I/O and OS | fileutils, pathname, find, tempfile, stringio, logger, benchmark |
| Network | net_http, open_uri, uri, cgi, ipaddr, webrick |
| Concurrency | monitor, mutex_m, timeout, observer, event_emitter |
| Data structures | pqueue, lru_cache, trie, tsort, state_machine, semver |
10.2.1 Partial ports and notes
- yaml is a subset of Psych; xml a subset of REXML.
- digest computes MD5 and SHA in Sake (slowly). zlib has only the CRC-32 and Adler-32 checksums, no compression.
- net_http is
Net::HTTP,Net::HTTPResponse,Net::HTTP::Getand the rest, as in Ruby. https goes through the built-inSocket.connect_ssl. - timeout runs the block in a Thread of its own and, when the time is up, really stops it with the built-in
Thread.raise. - pp is the PP that wraps at a width, distinct from the built-in one-line
pp.
10.2.2 Built in, so not required
Some things Ruby requires are built-in operations in Sake. require "tmpdir" is a static error saying there is no file to read.
| Ruby’s require | Sake’s built-in |
|---|---|
tmpdir |
Dir.mktmpdir, Dir.tmpdir (the latter added by tempfile) |
open3 |
Open3.capture2, capture2e, capture3, Kernel.system |
set |
Set.of(...), Set.add, … (Values and types) |
socket |
TCPServer, Socket (TCP only) |
10.2.3 Small libraries after gems
In the table above, toml (toml-rb), ini (inifile), mustache, units (ruby-units), text, diff (diff-lcs), terminal_table, event_emitter (Node’s EventEmitter), pqueue, lru_cache (lru_redux), trie, state_machine (AASM-like) and semver are not Ruby’s standard library but were written after popular gems or libraries of other languages. The head of each one’s notes names its model.
10.2.4 Instead of what is not ported
- Instead of singleton,
once { T.new(...) }(Functions and blocks). - Instead of forwardable / delegate, write the delegating operations one by one.
- Instead of ostruct, a Record
{a: 1}or a Hash.
10.3 Ports of gems
Popular gems are ported too, as far as Sake can express them. Each port keeps the gem’s names where Sake can (ActiveSupport::Inflector.pluralize(s), Redis.get(r, k)).
| Area | Libraries |
|---|---|
| Terminal and CLI | colorize, ruby_progressbar, highline, thor, awesome_print |
| ActiveSupport | active_support_inflector, active_support_core_ext, active_support_number_helper |
| Text | kramdown, liquid, i18n, faker |
| Web | rack, rackup, webrick, httparty |
| Data | redis, dotenv, money, rubyzip, chronic, jwt |
| Concurrency and testing | concurrent_ruby, rspec, minitest |
10.3.1 Notes
- active_support_core_ext is the modules
Blank,StringExt,ArrayExt,HashExtand the classActiveSupport::Duration. - kramdown is the GFM dialect, redis a RESP2 client, jwt HS256/384/512.
- concurrent_ruby has
Concurrent::Future,Concurrent::Promise,Concurrent::Atom,Concurrent::Map,Concurrent::Semaphoreand more. - rspec is written in the shape of minitest, naming the example:
RSpec.expect(ex, x).To.eq(y).
10.3.2 Dropped features
The features dropped and why are tabulated in sakelib/notes/not-ported.md. The main three:
- Stored blocks, such as
then(rescuer) {}and registered callbacks. Blocks are second-class and cannot be stored (Functions and blocks). - Reflection:
constantize,send,define_method. There is no call by name. - APIs that pass a class as a value, such as
expect(x).to be_a(T). A type is not a value; writex in Tor one function per type.
10.4 minitest
sakelib/minitest.sake is a test framework in the style of Ruby’s Minitest, for programs written in Sake. Sake’s own test suite (test/sake/*_test.sake) is written with it.
10.4.1 Writing a test
Sake has no reflection, so a test is not a method found by name but a block given to Minitest.test. Every assertion names the test it belongs to (the block’s parameter t) as its first argument, as every Sake operation names its subject.
require "minitest"
suite = Minitest.suite("strings")
Minitest.test(suite, "upcase and split") do |t|
Minitest.assert_equal(t, "ABC", String.upcase("abc"))
Minitest.assert_equal(t, Any["a", "b"], String.split("a,b", ","))
Minitest.assert(t, String.empty?(""))
end
Minitest.run(suite) # prints the report; exits with 1 when a test failed
# Running strings:
.
Finished in 0.026718s
1 runs, 3 assertions, 0 failures, 0 errors
- Tests run on the spot.
Minitest.testruns the block at once and records the result. Tests run in the order defined. Minitest.run(suite)prints the report and exits with code 1 when there was a failure or an error.Minitest.report(suite)only prints, and returns true or false for whether everything passed.
10.4.2 Assertions
Functions of Minitest; each takes the test t first and an optional message last.
| Function | Checks |
|---|---|
assert(t, cond), refute(t, cond) |
truthy / falsy |
assert_equal(t, expected, actual), refute_equal |
== |
assert_nil(t, x), refute_nil |
nil |
assert_in_delta(t, expected, actual, delta = 0.001) |
difference within delta |
assert_includes(t, collection, x), refute_includes |
contains the element |
assert_empty(t, collection), refute_empty |
empty |
assert_match(t, pattern, s) |
matches the Regexp |
assert_raises(t) { ... } |
the block raises; gives the exception’s message |
There is no assert_output: a Sake program cannot capture its own output, so print the value and compare.
10.4.3 Failures and errors
A failed assertion raises Minitest::Assertion and is recorded as a failure (F). Any other exception is recorded as an error (E), as in Ruby’s Minitest.
require "minitest"
suite = Minitest.suite("numbers")
Minitest.test(suite, "integer division floors") do |t|
Minitest.assert_equal(t, 3, 7 / 2)
end
Minitest.test(suite, "divides by zero") do |t|
Minitest.assert_equal(t, 0, 1 / 0)
end
Minitest.test(suite, "wrong expectation") do |t|
Minitest.assert_equal(t, 4, 2 + 3)
end
Minitest.run(suite) # exit code 1
# Running numbers:
.EF
Finished in 0.035481s
1) Error:
divides by zero
divided by 0
2) Failure:
wrong expectation
expected 4, got 5
3 runs, 2 assertions, 1 failures, 1 errors
10.5 Tests
Each library port has a Ruby twin: test/sakelib/NAME.sake, the program written with Sake’s library, and test/sakelib/NAME.rb, the same program with Ruby’s library. The two must print the same output; that sameness is the definition of a correct port.
# the shape of test/sakelib/shellwords.sake
require "shellwords"
p(Shellwords.split("a 'b c' d")) # => ["a", "b c", "d"]
p(Shellwords.escape("it's")) # => "it\\'s"
# its twin shellwords.rb, printing the same two lines in Ruby
require "shellwords"
p(Shellwords.split("a 'b c' d"))
p(Shellwords.escape("it's"))
- Running them.
ruby test/test_sakelib.rbruns each.sakewithbin/sake --strict(level 2) and each.rbwith Ruby, insidetest/sakelib/, and compares the outputs. - When the gem is not installed. The twin of a gem that is not installed runs on
test/sakelib/ref/NAME.rb, a plain-Ruby reference with the gem’s names and output. - Notes. Where the twins differ, and where Sake departs from Ruby, is written in
sakelib/notes/NAME.md.
Built-in reference
One chapter per built-in type or module, listing every operation of that namespace. Section headings are the operation names; aliases share a section (find, detect). The first line of each section is the exact signature:
Array.first(x, [Integer]):xis a value of the namespace’s type (the subject),[T]an optional argument,*Tany number of arguments,[k: T]a keyword, ` { }a required block, and[{ }]` an optional block.T[...], such asInteger[*Any], is the constructor of an Array of T values (a typed Array).
The text of each section says what the operation does and returns, what the arguments mean, when the result is nil and at which strictness level the checker reports its unchecked use, which exceptions it raises, what the checker rejects statically, whether it changes the subject in place or returns a new value, and how it differs from Ruby’s method of the same name. The # => value annotations in the examples are real output: ruby tools/check_reference.rb runs every example under --strict=2 and compares, and also checks the signatures and that no operation is missing (an example marked ruby error is one confirmed to be rejected or to fail).
The chapters go: Kernel and the exceptions, numbers, strings and regular expressions, sequences and maps, time, input/output and the OS, threads and sockets. The rules of the operators (+, <, []) are in Operators and indexing; the nature of each type’s values in Values and types.
11. Kernel
Kernel is the set of operations that any part of a program calls by name alone: puts(x), Integer(s), loop { }, without writing Kernel.puts(x) (that form works too; a name resolves to the current namespace first, then to the top-level functions, then to Kernel, see Functions). As with every other namespace, the argument types are checked statically, and a value of the wrong type is a type problem.
Here are output and input (puts, print, p, pp, format, gets, warn), the strict numeric conversions (Integer, Float, Rational, Complex), showing and copying values (to_s, inspect, dup, equal?), control (loop, block_given?, once), random numbers and time (rand, srand, sleep), and the process (exit, at_exit, system, ARGV, PROGRAM_NAME). raise is syntax, not an operation: see Exceptions and errors. Math.PI and Float.INFINITY are read as operations too, but belong to the chapters of their namespaces (Sake has no value constants; a named value is made with once).
11.1 Kernel[]
Kernel[*Any]
Makes an Array of any values. Every type includes Kernel, so by the rule of M[...] for a module (an Array of the types that include it) this Array takes every value; the prelude names it Any = Kernel, and it is written Any[...]. The elements may be of any types, mixed, and any value can be added later (the checker adds the type of each added value to the element type). The empty Array is Any[]. The literal [1, 2] is a Tuple, so a sequence that grows is written Any[...]; a typed Array is made with the type’s name, Integer[1, 2], and Array[...] is an Array of Arrays.
xs = Any[1, "a"]
Array.push(xs, :sym)
p(xs) # => [1, "a", :sym]
e = Any[]
Array.push(e, 1)
p(e) # => [1]
p([1, 2] == Any[1, 2]) # => false
```ruby error
xs = [1, 2]
Array.push(xs, 3) # !> [...] is a Tuple with a fixed length; for a growable Array, write Any[...]
## puts
`Kernel.puts(*Any)`
Writes each argument on its own line to the output and returns nil (Ruby's `puts`). A String is written as is (a newline is added when it has none), nil is an empty line, any other value is written in its `to_s` form. Arrays and Tuples are written one element per line, nested ones flattened (an empty Array is an empty line). Without arguments it writes an empty line. A class instance is written with its type's own `to_s(x)` when there is one, otherwise in the `#<struct ...>` form that `p` uses ([to_s](#to_s)).
```ruby
puts("a", 1, :sym)
# => a
# => 1
# => sym
puts(Any[1, [2, 3]])
# => 1
# => 2
# => 3
puts(Hash.of("k" => 1), 1..3)
# => {"k" => 1}
# => 1..3
11.2 print
Kernel.print(*Any)
Writes each argument in its to_s form with no newline between them and returns nil (Ruby’s print). Arrays and Tuples are written as [1, 2] (not one element per line as puts does); nil is the empty string.
print("a", 1, nil, "\n") # => a1
print(Any[1, 2], [3, 4], "\n") # => [1, 2][3, 4]
11.3 p
Kernel.p(*Any)
Writes each argument in its inspect form, one per line (Ruby’s p): Strings quoted, Symbols as :sym, class instances as #<struct Point x=1, y=2>, exception values as #<KeyError: msg>. The result is Ruby’s: the value when there is one argument, a Tuple of the arguments when there are several, nil when there are none (the checker sees it the same way). A class instance whose type defines inspect(x) is written with that.
x = p(1, "a")
# => 1
# => "a"
p(x) # => [1, "a"]
p(p()) # => nil
Point = Struct.new(:x, :y)
p(Point.new(1, 2)) # => #<struct Point x=1, y=2>
p(Hash.of("a" => 1), Set.of(1), 2r)
# => {"a" => 1}
# => Set[1]
# => (2/1)
11.4 pp
Kernel.pp(Any)
Writes one value in the form p uses and returns the value. Ruby’s pp wraps long values; in Sake it is the same as p(x).
a = pp(Any[1, {k: 2}]) # => [1, {k: 2}]
p(a) # => [1, {k: 2}]
11.5 format, sprintf
Kernel.format(String, *Any)
Kernel.sprintf(String, *Any)
Makes a String with Ruby’s format directives: %d, %f, %e, %g, %x, %o, %b, %c, %s, %p, %%, with width, precision and the flags -, +, 0. %s uses a value’s to_s (a class’s own to_s(x) included), %p its inspect. %<name>d and %{name} take one Record ({a: 1}) or one Hash with Symbol keys (Hash.of(a: 1)) as the single argument; the same holds for the operator form fmt % x (String). Too few or too many arguments, a value that is not a number for a numeric directive, and a missing key (key<a> not found) are all ArgumentError.
p(format("%05.2f|%-4s|%p|%x", 3.14159, :ab, "q", 255)) # => "03.14|ab |\"q\"|ff"
p(sprintf("%+d %s %s", 3, nil, true)) # => "+3 true"
p(format("%<a>d-%<b>s", {a: 1, b: "x"})) # => "1-x"
p("%<a>05d" % {a: 42}) # => "00042"
p(format("%<n>d-%{s}", Hash.of(n: 1, s: "x"))) # => "1-x"
Point = Struct.new(:x, :y)
p(format("%s", Point.new(1, 2))) # => "#<struct Point x=1, y=2>"
```ruby error p(format(“%d %d”, 1)) # !> ArgumentError: Kernel.format: too few arguments
```ruby error
p(format("%<a>d", {b: 1})) # !> ArgumentError: Kernel.format: key<a> not found
11.6 gets
Kernel.gets()
Reads one line from standard input and returns it as a String including its trailing newline, or nil at the end of the input. The result is String | nil, so String.chomp(gets) without a check is stopped by --strict (level 2) as a nil problem; test it first, if (line = gets). Unlike Ruby’s gets it takes no separator argument.
line = gets
p(line) # => "3\n"
if (rest = gets)
p(String.split(String.chomp(rest), " ")) # => ["1", "2"]
end
p(gets) # => nil
```ruby error p(String.chomp(gets)) # !> argument 1 may be nil
## warn
`Kernel.warn(*Any)`
Writes each argument to the error output (stderr) in the form `puts` uses and returns nil. Without arguments it writes nothing. Reporting a usage mistake and then calling `exit` is the typical pair.
```ruby error
warn("usage: prog FILE") # !> usage: prog FILE
exit(2)
11.7 Integer
Kernel.Integer(String|Integer|Float)
Ruby’s strict Integer(). A String must be a decimal integer or one with a 0x, 0b, 0o or 0 prefix; _ separators and surrounding whitespace are allowed, anything else ("12abc", "", "1e3") is an ArgumentError. A Float is truncated, an Integer returned as is. String.to_i silently reads as far as it can; this operation fails unless the whole String is an integer. A NaN or infinite Float is a FloatDomainError (the message is the value’s name, NaN or Infinity), rescuable like the others.
p(Integer("42")) # => 42
p(Integer(" 0x1f ")) # => 31
p(Integer("1_000")) # => 1000
p(Integer(3.9)) # => 3
p(String.to_i("12abc")) # => 12
```ruby error p(Integer(“12abc”)) # !> ArgumentError: Kernel.Integer: invalid value for Integer(): “12abc”
```ruby error
p(Integer(Float.NAN)) # !> FloatDomainError: Kernel.Integer: NaN
11.8 Float
Kernel.Float(String|Integer|Float)
Ruby’s strict Float(). A String must be a decimal real ("1e3", "1_0.5", surrounding whitespace allowed), otherwise ArgumentError. An Integer becomes a Float, a Float is returned as is.
p(Float("1.5")) # => 1.5
p(Float("1e3")) # => 1000.0
p(Float(2)) # => 2.0
```ruby error p(Float(“abc”)) # !> ArgumentError: Kernel.Float: invalid value for Float(): “abc”
## Rational
`Kernel.Rational(Integer|Float|Rational|String, [Integer|Rational])`
Ruby's `Rational(a, b = 1)`. `a` is an Integer, a Float, a Rational, or a String of the form `"1/3"`, `"0.75"` or `"3"`; `b` is an Integer or a Rational; the result is `a / b` reduced. A Float is taken exactly, as `Float.to_r` takes it: `Rational(0.5)` is `(1/2)`, but `Rational(0.1)` is the long binary fraction `0.1` really stands for (for the short `(1/10)` use `Float.rationalize`). A String that cannot be read is an `ArgumentError`, a `b` of 0 a `ZeroDivisionError`, a NaN or infinite Float a `FloatDomainError`.
```ruby
p(Rational(3, 6)) # => (1/2)
p(Rational("0.75")) # => (3/4)
p(Rational("1/3")) # => (1/3)
p(Rational(1r/2, 1r/4)) # => (2/1)
p(Rational(0.5)) # => (1/2)
p(Rational(1.5, 1r/2)) # => (3/1)
p(Rational(0.1) == Float.to_r(0.1)) # => true
p(Float.rationalize(0.1)) # => (1/10)
```ruby error p(Rational(1, 0)) # !> ZeroDivisionError: Kernel.Rational: divided by 0
## Complex
`Kernel.Complex(Integer|Float|Rational, [Integer|Float|Rational])`
Ruby's `Complex(re, im = 0)`. The real and imaginary parts are real numbers; Strings are not accepted (use `String.to_c`).
```ruby
p(Complex(1, 2)) # => (1+2i)
p(Complex(1.5)) # => (1.5+0i)
p(Complex(1r, 2.5)) # => ((1/1)+2.5i)
11.9 to_s
Kernel.to_s(Any)
The String that puts, interpolation "#{x}" and format’s %s use for a value. A String is itself, nil is "", a Symbol is its name, numbers use Ruby’s to_s, and Arrays, Tuples, Hashes, Sets, Records and Ranges are written as inspect writes them. A class instance uses its type’s to_s(x) when the type defines one (also one that comes from an included module), otherwise #<struct T ...>. It corresponds to Ruby’s x.to_s; since Sake has no method calls on values, this one operation turns a value of any type into a String.
class Temp
attr_reader celsius
def to_s(t) = "#{@celsius}C"
end
t = Temp.new(21)
p(to_s(t)) # => "21C"
puts("now #{t}") # => now 21C
p(to_s(nil)) # => ""
p(to_s(Any[1, "a"])) # => "[1, \"a\"]"
p(to_s(:sym)) # => "sym"
11.10 inspect
Kernel.inspect(Any)
The String that p writes for a value (Ruby’s x.inspect): a String with quotes and escapes, a Symbol as :sym, nil as "nil", a container with its elements inspected. A class instance uses its type’s inspect(x) when the type defines one (also from an included module), otherwise #<struct T f=v, ...>; an exception value is #<E: message>. p, pp, format’s %p and the elements inside a container are shown with it.
module Pretty
def inspect(v) = "<pretty>"
end
class Box
include Pretty
attr_reader n
end
p(inspect("a\n")) # => "\"a\\n\""
p(inspect(nil)) # => "nil"
p(inspect(Box.new(1))) # => "<pretty>"
p(Any[Box.new(1)]) # => [<pretty>]
11.11 dup
Kernel.dup(Any)
A shallow copy of a value (Ruby’s obj.dup): an Array, Tuple, Hash, Set, Record, class instance or String becomes a new container holding the same elements (the elements’ containers are shared). An Integer, Symbol, nil, true/false or Range is returned as is. The type is the original’s. A class instance whose type defines dup(x) gets that function (the result has the function’s type). Inside a type’s functions, copy an element with Kernel.dup(@items): a bare dup resolves to the type’s own.
a = Any[Any[1], Any[2]]
b = dup(a)
Array.push(b, Any[3])
Array.push(b[0], 9)
p(a) # => [[1, 9], [2]]
p(b) # => [[1, 9], [2], [3]]
class Box
attr_accessor items
def dup(b) = Box.new(Kernel.dup(@items))
end
x = Box.new(Any[1])
y = dup(x)
Array.push(Box.items(y), 2)
p(Box.items(x)) # => [1]
p(Box.items(y)) # => [1, 2]
11.12 equal?
Kernel.equal?(Any, Any)
True when the two arguments are the same value (the same object), Ruby’s a.equal?(b). == compares contents; this is identity. The result of dup is == to the original but not equal?. Equal Integers, Symbols, nil and true/false are always identical; two String literals written separately are not.
a = Any[1]
b = a
p(equal?(a, b)) # => true
p(a == dup(a)) # => true
p(equal?(a, dup(a))) # => false
p(equal?("a", "a")) # => false
p(equal?(:a, :a)) # => true
11.13 loop
Kernel.loop() { }
Runs the block until a break (Ruby’s loop). The result is the v of break v (nil for a bare break); the checker sees the union of the values of every break in the block. next goes on to the next round. A loop without a break never returns (Sake has no StopIteration to end it).
i = 0
v = loop do
i += 1
next if i < 3
break i * 10
end
p(v) # => 30
p(loop { break }) # => nil
11.14 block_given?
Kernel.block_given?()
True when the current function was called with a block. A function that checks block_given? may be called without one (a function that only yields requires it, see Functions). The checker folds block_given? in a condition to its value for each call, so a branch that reaches yield is not run for a call without a block. At the top level it is false.
def each_or(x)
return yield(x) if block_given?
x
end
p(each_or(1)) # => 1
p(each_or(1) { |v| v + 10 }) # => 11
p(block_given?) # => false
11.15 once
Kernel.once() { }
The block’s value. The block runs once, the first time that place in the program is reached, and the value is kept for the rest of the program: every later evaluation (another call, another thread) gets the same value. Since Sake has no value constants, this is how a table or a named value is computed once: def table = once { ... }. What is kept is the value itself; an Array can still be changed afterwards (as Ruby’s constants can). A block that reaches its own once again while computing it ends the program with SystemStackError (not rescuable).
def table = once { puts("computing"); Any[1, 2, 3] }
p(table)
# => computing
# => [1, 2, 3]
p(table) # => [1, 2, 3]
p(equal?(table, table)) # => true
```ruby error def rec(n) = once { rec(n) } rec(1) # !> SystemStackError: once: the block reached its own once again while computing it
## rand
`Kernel.rand([Integer|Float])`
Ruby's `rand`, with a stricter argument. Without an argument, a Float at least 0.0 and below 1.0. With an Integer `n`, an Integer at least 0 and below `n`. With a Float `x`, a Float at least 0.0 and below `x`. The bound must be positive: 0, 0.0 and a negative number are an `ArgumentError` (`invalid argument - 0`), where Ruby takes `|n|` and gives a Float for 0. A Range is not accepted (a `type` problem statically).
```ruby
f = rand
p(f >= 0.0 && f < 1.0) # => true
i = rand(6)
p(i >= 0 && i < 6) # => true
g = rand(2.5)
p(g >= 0.0 && g < 2.5) # => true
```ruby error p(rand(0)) # !> ArgumentError: Kernel.rand: invalid argument - 0
## srand
`Kernel.srand([Integer])`
Sets the seed of the random numbers and returns the previous seed (an Integer), Ruby's `srand`. Without an argument it picks a new seed at random. The same seed gives the same sequence of `rand`.
```ruby
srand(7)
a = Array.map(Any[1, 2, 3]) { rand(1000) }
srand(7)
b = Array.map(Any[1, 2, 3]) { rand(1000) }
p(a == b) # => true
p(srand(1)) # => 7
11.16 sleep
Kernel.sleep([Integer|Float|Rational])
Pauses for the given number of seconds and returns the seconds slept, rounded to an Integer (Ruby’s sleep). Without an argument it pauses forever (until another thread wakes it).
p(sleep(0)) # => 0
p(sleep(1r/100)) # => 0
11.17 exit
Kernel.exit([Integer|Boolean])
Ends the program with the status status: an Integer as is, true as 0, false as 1, 0 when left out. No rescue catches it (rescue => e lets it through), while the ensure clauses on the way out and the at_exit blocks run. The checker treats the code after exit as unreachable (its type is []: it returns no value).
at_exit { puts("at_exit") }
begin
exit(true)
rescue => e
puts("not caught")
ensure
puts("ensure") # => ensure
end
puts("not reached") # => at_exit
```ruby error puts(“start”) # !> start exit(3) puts(“not reached”)
## at_exit
`Kernel.at_exit() { }`
Registers the block and returns nil. The registered blocks run when the program ends (normally, by `exit`, or by an uncaught exception), the last registered first (Ruby's `at_exit`).
```ruby
at_exit { puts("bye 1") }
at_exit { puts("bye 2") }
puts("main")
# => main
# => bye 2
# => bye 1
11.18 system
Kernel.system(String, *String)
Runs a command as a child process and waits for it (Ruby’s system). One argument goes through the shell; with several, the first is the command and the rest its arguments. The result is true for exit status 0, false for any other status, and nil when the command could not be started (Boolean | nil). The child’s output goes straight to the output; to get it as a String use Open3.capture2 or capture3.
p(system("echo", "hi"))
# => hi
# => true
p(system("false")) # => false
p(system("/no/such/command")) # => nil
11.19 ARGV
Kernel.ARGV()
The program’s command-line arguments, an Array of Strings. It is written like Ruby’s constant but is an operation, and every use gives the same Array, so shortening it with Array.shift(ARGV) or an OptionParser’s parse! shows in every later ARGV. Its Strings are frozen: an operation that changes one in place (String.concat, …) is a TypeError.
p(ARGV) # => []
Array.push(ARGV, "x")
p(ARGV) # => ["x"]
p(equal?(ARGV, ARGV)) # => true
11.20 PROGRAM_NAME
Kernel.PROGRAM_NAME()
The path of the running program (its first file), Ruby’s $0. Unlike the other Kernel operations, a bare PROGRAM_NAME is read as a type name and rejected (type PROGRAM_NAME cannot be used as a value), so write Kernel.PROGRAM_NAME.
p(String.end_with?(Kernel.PROGRAM_NAME, ".sake")) # => true
12. Exceptions
The built-in exception types. Built-in operations raise them when they fail at run time, and a program raises them itself with raise E, "msg". Each is declared as a class with the single field message (Classes), so the operations of any class apply:
E.new(message)makes an exception value (messageis usually a String; its type is not checked).raise E, "msg"is short forraise E.new("msg"), andraise Euses the type’s name as the message.raise "msg"raises aRuntimeError.raiseis syntax, described in Exceptions and errors.E.message(e)reads the message andE.set_message(e, s)replaces it (e.E.messageis the same read). The oldE.get_messageis rejected with a hint to useE.message.Exception.message(e)reads the message of an exception value of any type: use it where the type is a union, as afterrescue => e.Exception.backtrace(e)gives the calls in effect when it was raised (an Array of"FILE:LINE:in 'FUNCTION'", innermost first), andException.cause(e)the exception a rescue clause was handling then. Both are nil for a value never raised (Backtrace and cause).rescue E => ecatches the listed types. The built-in types keep Ruby’s parents:rescue IndexErroralso catchesKeyError,rescue IOErrorEOFError,rescue RangeErrorFloatDomainError,rescue ArgumentErrorMath::DomainError, and each also catches a class declared under it (class E < RangeError). A user typeclass B < Ais a copy, sorescue Adoes not catch B; a module that the types include groups them (rescue M). Several types arerescue A, B => e; every type isrescue => e(rescue StandardErrorandrescue Exceptionmean the same).p(e)prints#<E: message>, and==compares the fields.- The message of an exception raised by a built-in operation is Ruby’s text (
divided by 0); the report of one that ends the program also names the operation (ZeroDivisionError: Kernel.Rational: divided by 0). - A Ruby exception raised inside a built-in operation never escapes as a raw Ruby error with a backtrace: it becomes the Sake exception of the same kind, rescuable like any other, with Ruby’s message (
Tuplein place of Ruby’sSake::Tuple). Ruby’sEOFError,Errno::*andClosedQueueErrorbecomeIOError,FrozenErrorbecomesTypeError,Math::DomainErrorstaysMath::DomainError, and Ruby’s ownRuntimeError(String.undumpof a bad String) becomesArgumentError, so that Sake’sRuntimeErrorcomes only from a program’sraise.
Math::DomainError is a built-in exception type too: it can be written in rescue Math::DomainError => e and raise Math::DomainError, "msg", but being a nested name it cannot be written as Math::DomainError.new(...) or Math::DomainError.message(e) (read it with Exception.message(e)). Math.sqrt(-1), Math.log(-1), Integer.sqrt(-1), Integer.digits(-1) and x ** 0.5 with a negative Float x (-8.0 ** 0.5 is not a real number (a negative base with a fractional exponent); Ruby gives a Complex) raise it. SystemStackError (too deep a recursion, a once reaching itself), LocalJumpError and NotImplementedError are program errors: they cannot be rescued, and naming them in rescue is a static error.
Most type mistakes (TypeError) and pattern misses (NoMatchingPatternError) are stopped before running as type problems, so at run time they reach only the places the checker could not see (an operation taking Any, a program run with --strict=0).
begin
Integer("x")
rescue ArgumentError, TypeError => e
puts(Exception.message(e)) # => invalid value for Integer(): "x"
end
e = KeyError.new("missing")
p(e) # => #<KeyError: missing>
p(KeyError.message(e)) # => "missing"
KeyError.set_message(e, "other")
p(Exception.message(e)) # => "other"
p(e == KeyError.new("other")) # => true
12.1 RuntimeError
RuntimeError.new(message)
The type that raise "msg" (a raise without a type) raises. No built-in operation raises it on its own: only Thread.raise(t, msg) does, in the other thread, where Thread.value(t) or Thread.join(t) deliver it (a Ruby RuntimeError inside an operation, as from String.undump, becomes ArgumentError). It is the type for a program’s “other failures”. Accordingly the checker warns about a rescue RuntimeError whose body cannot raise one (the begin body never raises RuntimeError [unused-rescue], with the hint that Thread.value / Thread.join count only in a program that calls Thread.raise). In the example below the clause catches nothing, and the ArgumentError from String.undump stops the program.
begin
raise "plain"
rescue RuntimeError => e
p(e) # => #<RuntimeError: plain>
end
```ruby error begin String.undump(“"abc”) rescue RuntimeError => e # !> warning: rescue RuntimeError: the begin body never raises RuntimeError [unused-rescue] p(e) end
## ArgumentError
`ArgumentError.new(message)`
An argument's value is not acceptable. Raised by: `Integer`, `Float` and `Rational` for a String they cannot read; `format` for too few or too many arguments, a value that is not a number, or a missing key; `rand(0)` and a negative bound; a negative size (`Array.first(a, -1)`); `Integer ** negative Integer`; `Regexp.new(s, flags)` with bad flags; `Time.new`, `Time.at`, `Time.now` with a value out of range or a bad zone; `Integer.to_s(n, base)` with a bad base; `String.unpack` with a bad format; `String.ljust` with an empty pad, `tr` with a reversed range, `String.undump` of a String that is not a dumped one (`unterminated dumped string`, Ruby's RuntimeError); `ENV.set("", v)` and `Process.clock_gettime(999)` (an argument the OS refuses); `Zlib` on damaged data (`incorrect header check`); a block called with the wrong number of arguments. Elements that cannot be compared (`Array.sort`, `max`, `min`, `Tuple.max`, `Set.sort`, and the `*_by` forms through their block's results) are a static `type` problem when the checker sees the element types; the run-time `ArgumentError` is reached only where it cannot, as with a NaN.
```ruby
begin
Array.sort(Any[1.0, Float.NAN])
rescue ArgumentError => e
puts(Exception.message(e)) # => cannot compare elements of types Float
end
begin
2 ** -1
rescue ArgumentError => e
puts(Exception.message(e)) # => Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
end
begin
String.undump("\"abc")
rescue ArgumentError => e
puts(Exception.message(e)) # => unterminated dumped string
end
```ruby error Array.sort(Any[1, “a”]) # !> Array.sort: elements compared in order may be (Integer, String), which cannot be compared [type]
## TypeError
`TypeError.new(message)`
A value of the wrong type: an operation given an argument of another type, an operator the left operand's type does not support, a write of another type into a typed Array (`Integer[]`, ...), multiple assignment from a value that is neither a Tuple nor an Array, an in-place change of a frozen String (a Hash key, a Set element, a Symbol's name, an element of `ARGV`: `cannot change this String in place: it is a Hash key, a Set element, a Symbol's name, or a program argument`), `Exception.message` given a value that is not an exception. A Ruby `TypeError` or `NoMethodError` inside an operation is this type too, where the checker could not see the element types: `Array.pack(Any["a"], "C")` (`no implicit conversion of String into Integer`), `Range.first(1.0..2.0, 2)` (`can't iterate from Float`), `ENV.replace` with a non-String key or value under `--strict=0` (`keys and values must be String, got String => Integer`). The checks before running stop most of these statically; at run time only the places the checker let through reach it.
```ruby
begin
Array.pack(Any["a"], "C")
rescue TypeError => e
puts(Exception.message(e)) # => no implicit conversion of String into Integer
end
```ruby error Array.push(Integer[], “a”) # !> Array.push: an element must be Integer, but is String
## KeyError
`KeyError.new(message)`
A missing key: `Hash.fetch` (without a default and a block), `Hash.fetch_values`, `ENV.fetch`, a Record pattern naming a field the Record lacks. A missing key in `format`'s `%<name>` is an `ArgumentError`.
```ruby
h = Hash.of("a" => 1)
begin
Hash.fetch(h, "b")
rescue KeyError => e
puts(Exception.message(e)) # => key not found: "b"
end
p(Hash.fetch(h, "b", 0)) # => 0
12.2 IndexError
IndexError.new(message)
An index out of range: Array.fetch; an index outside a Tuple that reaches run time (a literal index is stopped statically); a write past the end of an Array of T (Integer[1, 2][5] = 1, since the gap would be nil); Array.insert past the end (index 3 is past the end of the Array (length 1); the gap would be nil, where Ruby fills the gap); MatchData.begin(m, 5) for a group the Regexp lacks (index 5 out of matches); Array.transpose with rows of different lengths; String.byteindex with an offset inside a character. x[k] gives nil outside the range and never raises.
def at(t, i) = t[i]
begin
at([1, 2], 5)
rescue IndexError => e
puts(Exception.message(e)) # => index 5 is outside a Tuple of length 2
end
begin
Array.fetch(Any[3, 1], 9)
rescue IndexError => e
puts(Exception.message(e)) # => index 9 outside of array bounds: -2...2
end
12.3 ZeroDivisionError
ZeroDivisionError.new(message)
Division of an Integer (or a Rational) by zero: /, %, Integer.divmod, div, modulo, remainder, ceildiv, Rational(a, 0), Rational.quo. A Float’s 1.0 / 0 is Infinity, as in Ruby, not an exception.
begin
7 % 0
rescue ZeroDivisionError => e
puts(Exception.message(e)) # => divided by 0
end
p(1.0 / 0) # => Infinity
12.4 RangeError
RangeError.new(message)
A value outside what can be represented: an endless Range given to an operation that needs a finite one (Range.to_a(1..), Range.last(1..), Range.sort, …), a beginless one to Range.first (cannot get the first element of beginless range), an Integer that is no character in Integer.chr (256 out of char range), a negative exponent in Integer.pow(2, -1, 7), a Complex initial value in Range.sum.
begin
Range.to_a(1..)
rescue RangeError => e
puts(Exception.message(e)) # => cannot do this on an endless Range 1..
end
begin
Integer.chr(256)
rescue RangeError => e
puts(Exception.message(e)) # => 256 out of char range
end
12.5 FloatDomainError
FloatDomainError.new(message)
NaN or Infinity turned into an Integer or a Rational: Float.to_i, Arithmetic.to_i, Arithmetic.round, floor, ceil, truncate, Float.to_r, Float.rationalize, Float.numerator, denominator, Kernel.Integer(x), Kernel.Rational(x). The message is the value’s name. All of them are rescuable.
begin
Float.to_i(Float.INFINITY)
rescue FloatDomainError => e
puts(Exception.message(e)) # => Infinity
end
begin
Float.to_r(Float.NAN)
rescue FloatDomainError => e
puts(Exception.message(e)) # => NaN
end
begin
Integer(Float.NAN)
rescue FloatDomainError => e
puts(Exception.message(e)) # => NaN
end
12.6 IOError
IOError.new(message)
A failure of input or output: the File and Dir operations (File.read, File.write, File.open, Dir.mkdir, Dir.children, …; Ruby’s Errno::ENOENT and company become this one type, with Ruby’s message), an operation on a closed IO, IO.size of a stream that is not a file (not a file), the terminal operations (IO.winsize, …) on an IO that is not a terminal (not a terminal), socket errors (connection refused, reset, a name that does not resolve, timeout), Queue.push to a closed Queue. A Ruby EOFError inside an operation becomes this type too, not Sake’s EOFError.
begin
File.read("no-such-file")
rescue IOError => e
puts(Exception.message(e)) # => No such file or directory @ rb_sysopen - no-such-file
end
begin
IO.size(IO.stdin)
rescue IOError => e
puts(Exception.message(e)) # => not a file
end
q = Queue.new
Queue.close(q)
begin
Queue.push(q, 1)
rescue IOError => e
puts(Exception.message(e)) # => push to a closed Queue
end
12.7 EOFError
EOFError.new(message)
A read at the end of the input. Ruby’s IO#readline and the like raise it, but Sake’s reading operations (IO.read(io, n), IO.gets, IO.getc, gets) return nil at the end, so no built-in operation raises it (a Ruby EOFError inside an operation becomes IOError). A program can use it for its own “no more input”. It is a type of its own, which rescue IOError does not catch.
def next_token(xs) = Array.shift(xs) || raise(EOFError, "no more tokens")
begin
next_token(Any[])
rescue EOFError => e
puts(Exception.message(e)) # => no more tokens
end
12.8 EncodingError
EncodingError.new(message)
A mistake in a String’s encoding: a byte that String.encode cannot convert, concatenating or comparing Strings of incompatible encodings, and whatever else makes Ruby raise an EncodingError (Encoding::UndefinedConversionError, …) inside an operation.
begin
String.encode("\xff", "UTF-16")
rescue EncodingError => e
puts(Exception.message(e)) # => "\xFF" on UTF-8
end
12.9 RegexpError
RegexpError.new(message)
An invalid regular expression: a literal with interpolation /#{s}/ whose pattern is invalid, Regexp.new(s) with an invalid pattern, a String operation given an invalid pattern String (String.match?("a+c", "+")). A pattern written directly in a literal is a syntax error, stopped before running.
s = "("
begin
r = /#{s}/
rescue RegexpError => e
puts(Exception.message(e)) # => end pattern with unmatched parenthesis: /(/
end
begin
Regexp.new("(")
rescue RegexpError => e
puts(Exception.message(e)) # => end pattern with unmatched parenthesis: /(/
end
begin
String.match?("a+c", "+")
rescue RegexpError => e
puts(Exception.message(e)) # => target of repeat operator is not specified: /+/
end
12.10 NoMatchingPatternError
NoMatchingPatternError.new(message)
No branch of a case/in matches the value and there is no else, or x => pattern does not match. The checker finds from the value’s type that no branch can match and stops the program as a type problem (the example below), so it is raised at run time only where the checker let the code through, for instance in a program run with --strict=0. The message describes the value that did not match.
```ruby error
def kind(x)
case x
in Integer then “int”
end
end
kind(“s”) # !> case/in: no in branch matches String
## ThreadError
`ThreadError.new(message)`
A mistake in the use of threads: `Mutex.lock` or `Mutex.synchronize` on a Mutex the thread already holds, `Mutex.unlock` on a Mutex it does not hold, `Thread.join` of the current thread. A deadlock, every thread waiting, also ends the program with Sake's `ThreadError` ([Exceptions and errors](../08-exceptions.md)).
```ruby
m = Mutex.new
Mutex.lock(m)
begin
Mutex.lock(m)
rescue ThreadError => e
puts(Exception.message(e)) # => deadlock; recursive locking
end
begin
Mutex.unlock(Mutex.new)
rescue ThreadError => e
puts(Exception.message(e)) # => Attempt to unlock a mutex which is not locked
end
begin
Thread.join(Thread.current)
rescue ThreadError => e
puts(Exception.message(e)) # => Target thread must not be current thread
end
13. Integer
An Integer is a whole number of any size, as Ruby’s: 42, -7, 1_000_000, 0x1f, 0b101, 0o17, 2 ** 100 (Values and types). The conversion Integer("42") and Integer(3.9) is an operation of Kernel, not of this namespace (Kernel.Integer; an ArgumentError on bad input). Integers are values: no operation here changes its subject, and every result is a new value.
The operators on Integers come from the modules Integer includes (Operators and indexing): Arithmetic gives + - * / % ** and unary -x, Comparable gives <=> < <= > >=, Bitwise gives & | ^ << >> and unary ~x, and == and != are Kernel’s. a + b dispatches on the type of a, so with an Integer on the left it runs Integer.+(a, b); the entries Integer.+(x, y) and so on below are those function forms. The right operand’s type comes from a closed table: Integer with Integer gives an Integer, with a Float a Float, with a Rational a Rational (except **, whose result with a Rational exponent is Rational | Float), with a Complex a Complex; < and the like compare with Integer, Float, and Rational; the bit operators take an Integer only. A right operand of another type (a String, nil, a Symbol) is a type problem statically (the operands are (Integer, String), which the left operand's type does not support) and a TypeError at run time. The function form also requires its first argument to be an Integer (Integer.+(1.0, 2) is a type problem: use Float.+, or the operator).
Two things differ from Ruby: Integer ** negative Integer raises ArgumentError instead of giving a Rational, so that the type of a ** b never depends on the value of b, and no method is called on a value (1.+(2) is rejected; write Integer.+(1, 2) or 1 + 2). / and % floor as Ruby’s do (-7 / 2 is -4), and dividing by zero raises ZeroDivisionError.
The rounding family round, floor, ceil, truncate, and abs, to_f, to_i, zero? also exist in the module Arithmetic (Arithmetic), where they take any real number (Arithmetic.round(x) for an Integer, Float, or Rational x, as Ruby’s x.round); the entries below are the Integer-only forms, which reject a Float subject with a hint to use Arithmetic. Operations whose result may be nil do not exist in this namespace: every result is a definite Integer, Float, Rational, String, Boolean, Tuple, or Array.
13.1 Integer[]
Integer[*Any]
Makes an Array of Integers: a typed Array whose every element must be an Integer, checked on construction and on every write (Array.push, a[i] = v, …). A literal element of another type is rejected before running (element 2 must be Integer, got String); a write of another type is a type problem statically and a TypeError at run time. The empty Array of Integers is Integer[]. Compare Any[1, 2], whose element type is inferred and widens with what is written into it.
a = Integer[1, 2, 3]
Array.push(a, 4)
p(a) # => [1, 2, 3, 4]
p(Array.size(Integer[])) # => 0
```ruby error Array.push(Integer[], 1.5) # !> Array.push: an element must be Integer, but is Float
## +, -, *
`Integer.+(x, Any)`
`Integer.-(x, Any)`
`Integer.*(x, Any)`
Addition, subtraction, and multiplication, exact at any size. The result's type follows the right operand: Integer with an Integer, Float with a Float, Rational with a Rational, Complex with a Complex. Any other right operand is a `type` problem. The unary minus `-x` is `Integer.-@`, written as the operator only.
```ruby
p(Integer.+(1, 2)) # => 3
p(Integer.-(1, 2)) # => -1
p(3 * 4) # => 12
p(2 + 1.5) # => 3.5
p(1 + 1r) # => (2/1)
p(2 ** 64 + 1) # => 18446744073709551617
```ruby error p(1 + “a”) # !> the operands are (Integer, String), which the left operand’s type does not support
## /, %
`Integer./(x, Any)`
`Integer.%(x, Any)`
Division and remainder. With two Integers both **floor**, as Ruby's: the quotient rounds toward minus infinity and the remainder takes the sign of the divisor (`-7 / 2` is -4, `-7 % 3` is 2, `7 % -3` is -2). An Integer divisor of 0 raises `ZeroDivisionError`. With a Float on the right the result is a Float and division by `0.0` gives `Infinity` (IEEE); with a Rational, a Rational. See also `div`, `modulo`, `remainder`, `divmod`, `fdiv`, and `ceildiv`.
```ruby
p(7 / 2) # => 3
p(-7 / 2) # => -4
p(7 % 3) # => 1
p(-7 % 3) # => 2
p(Integer./(7, 2.0)) # => 3.5
p(1 / 0.0) # => Infinity
```ruby error p(Integer./(1, 0)) # !> ZeroDivisionError: Integer./: divided by 0
## **
`Integer.**(x, Any)`
Exponentiation. Integer with a non-negative Integer exponent is an exact Integer; a Float exponent gives a Float. A **Rational exponent** gives a Rational when it is integral (`2 ** 2r` is `(4/1)`) and a Float otherwise (`2 ** (1r/2)` is `1.4142135623730951`), so the checker types that result `Rational | Float`: narrow it with `case`/`in` before using it as one of the two. Unlike Ruby, a **negative Integer exponent is an `ArgumentError`** (Ruby gives a Rational); the message tells how to ask for a Rational (`2r ** -1`). A negative base with a fractional exponent (`(-8) ** 0.5`) is a `Math::DomainError`, where Ruby gives a Complex. `**` binds tighter than unary minus, so `-2 ** 2` is -4, as in Ruby; it is right-associative (`2 ** 3 ** 2` is 512). See also `pow`.
```ruby
p(2 ** 10) # => 1024
p(Integer.**(2, 0)) # => 1
p(2 ** 0.5) # => 1.4142135623730951
p(2 ** 2r) # => (4/1)
p(2 ** (1r/2)) # => 1.4142135623730951
p(-2 ** 2) # => -4
x = 2 ** 2r
case x
in Rational then p(Rational.numerator(x)) # => 4
in Float then p(x)
end
```ruby error p(2 ** -1) # !> ArgumentError: Arithmetic.**: Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
```ruby error
p((-8) ** 0.5) # !> Math::DomainError: Arithmetic.**: -8 ** 0.5 is not a real number (a negative base with a fractional exponent)
```ruby error p(Rational.numerator(2 ** 2r)) # !> Rational.numerator: argument 1 must be Rational, but can be Float
## ==, !=
`Integer.==(x, Any)`
`Integer.!=(x, Any)`
Equality (`!=` is its negation). Numbers compare across types: `1 == 1.0`, `1 == 1r`, and `1 == Complex(1, 0)` are true. A value of another type is never equal (`1 == "1"` is false, `1 == nil` is false), as in Ruby; the function form `Integer.==(x, y)` gives the same false (and `!=` true) for a right operand of any other type, without an error.
```ruby
p(1 == 1) # => true
p(1 == 1.0) # => true
p(1 != 1) # => false
p(1 == "1") # => false
p(1 == nil) # => false
p(Integer.==(1, nil)) # => false
p(Integer.==(1, "a")) # => false
p(Integer.!=(1, "a")) # => true
13.2 <, <=, >, >=
Integer.<(x, Any)
Integer.<=(x, Any)
Integer.>(x, Any)
Integer.>=(x, Any)
Ordering. The right operand is an Integer, Float, or Rational; a Complex has no order, and a String or nil is a type problem (Comparable.<: the operands are (Integer, String) ...). The result is true or false.
p(1 < 2) # => true
p(1 <= 1) # => true
p(2 >= 3) # => false
p(1 < 1.5) # => true
p(Integer.<(1, 2r)) # => true
```ruby error p(1 < “a”) # !> Comparable.<: the operands are (Integer, String), which the left operand’s type does not support
## <=>
`Integer.<=>(x, Any)`
-1, 0, or 1 as the left operand is smaller than, equal to, or larger than the right (an Integer, Float, or Rational). Between numbers the result is never nil, so the checker types it as an Integer. Another right operand is a `type` problem.
```ruby
p(1 <=> 2) # => -1
p(2 <=> 1) # => 1
p(1 <=> 1) # => 0
p(Integer.<=>(1, 1.5)) # => -1
13.3 &, |, ^
Integer.&(x, Any)
Integer.|(x, Any)
Integer.^(x, Any)
Bitwise and, or, and exclusive or on the two’s-complement bits, as Ruby’s; negative numbers behave as if they had infinitely many 1 bits above. Both operands must be Integers: a Float on the right is a type problem (Bitwise.&: the operands are (Integer, Float) ...). The unary complement ~x is Integer.~, written as the operator only.
p(6 & 3) # => 2
p(6 | 3) # => 7
p(6 ^ 3) # => 5
p(~5) # => -6
p(Integer.&(6, 3)) # => 2
```ruby error p(6 & 1.5) # !> Bitwise.&: the operands are (Integer, Float), which the left operand’s type does not support
## <<, >>
`Integer.<<(x, Any)`
`Integer.>>(x, Any)`
Shift left and shift right by an Integer number of bits, as Ruby's: `x << n` is `x * 2**n` (exact, growing as needed), `x >> n` floors (`-1 >> 1` is -1). A negative count shifts the other way (`1 << -1` is 0). The right operand must be an Integer.
```ruby
p(1 << 4) # => 16
p(256 >> 4) # => 16
p(-1 >> 1) # => -1
p(1 << 100 == 2 ** 100) # => true
13.4 bit_length
Integer.bit_length(x)
The number of bits needed to represent the number in two’s complement, without the sign bit, as Ruby’s: 255 needs 8, 256 needs 9, 0 and -1 need 0, -256 needs 8.
p(Integer.bit_length(255)) # => 8
p(Integer.bit_length(256)) # => 9
p(Integer.bit_length(0)) # => 0
p(Integer.bit_length(-1)) # => 0
13.5 allbits?, anybits?, nobits?
Integer.allbits?(x, Integer)
Integer.anybits?(x, Integer)
Integer.nobits?(x, Integer)
Whether all, any, or none of the bits set in the mask are set in the number: x & mask == mask, x & mask != 0, x & mask == 0.
p(Integer.allbits?(6, 2)) # => true
p(Integer.allbits?(6, 3)) # => false
p(Integer.anybits?(6, 3)) # => true
p(Integer.nobits?(6, 1)) # => true
13.6 div, modulo, remainder
Integer.div(x, Integer|Float|Rational)
Integer.modulo(x, Integer)
Integer.remainder(x, Integer)
div is the floored quotient as an Integer even for a Float or Rational divisor (Integer.div(7, 2.0) is 3, where 7 / 2.0 is 3.5). modulo is %: the remainder with the sign of the divisor. remainder is the remainder with the sign of the dividend, as Ruby’s (Integer.remainder(-7, 3) is -1, where -7 % 3 is 2). A divisor of 0 (or 0.0) raises ZeroDivisionError.
p(Integer.div(-7, 2)) # => -4
p(Integer.div(7, 2.0)) # => 3
p(Integer.modulo(-7, 3)) # => 2
p(Integer.remainder(-7, 3)) # => -1
p(Integer.remainder(7, -3)) # => 1
```ruby error p(Integer.modulo(7, 0)) # !> ZeroDivisionError: Integer.modulo: divided by 0
## divmod
`Integer.divmod(x, Integer)`
The Tuple `[quotient, remainder]` of the floored division, so that `q * b + r == a`, with `r` taking the sign of the divisor. A divisor of 0 raises `ZeroDivisionError`. Unlike Ruby's, the divisor must be an Integer (`Float.divmod` takes a Float subject).
```ruby
p(Integer.divmod(7, 2)) # => [3, 1]
p(Integer.divmod(-7, 2)) # => [-4, 1]
q, r = Integer.divmod(17, 5)
p([q, r]) # => [3, 2]
```ruby error p(Integer.divmod(7, 0)) # !> ZeroDivisionError: Integer.divmod: divided by 0
## ceildiv
`Integer.ceildiv(x, Integer)`
The quotient rounded toward plus infinity (Ruby's `ceildiv`): `Integer.ceildiv(7, 2)` is 4, `Integer.ceildiv(-7, 2)` is -3. A divisor of 0 raises `ZeroDivisionError`.
```ruby
p(Integer.ceildiv(7, 2)) # => 4
p(Integer.ceildiv(-7, 2)) # => -3
p(Integer.ceildiv(6, 2)) # => 3
13.7 fdiv
Integer.fdiv(x, Integer)
The quotient as a Float, Integer.to_f(a) / b. A divisor of 0 does not raise: the result is Infinity, -Infinity, or NaN (for 0 / 0), as Ruby’s. Unlike Ruby’s, the divisor must be an Integer.
p(Integer.fdiv(7, 2)) # => 3.5
p(Integer.fdiv(1, 3)) # => 0.3333333333333333
p(Integer.fdiv(1, 0)) # => Infinity
13.8 pow
Integer.pow(x, Integer, [Integer])
Integer.pow(a, b) is a ** b for an Integer exponent, with the same ArgumentError for a negative one. Integer.pow(a, b, m) is a ** b modulo m, computed without building the large power, as Ruby’s; the result takes the sign of m. With a modulus, a negative exponent is a RangeError (Ruby’s message, which counts the exponent as the 1st argument) and a modulus of 0 a ZeroDivisionError; both can be rescued.
p(Integer.pow(2, 10)) # => 1024
p(Integer.pow(2, 10, 1000)) # => 24
p(Integer.pow(3, 100, 7)) # => 4
p(Integer.pow(2, 10, -7)) # => -5
begin
Integer.pow(2, 3, 0)
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
```ruby error p(Integer.pow(2, -1)) # !> ArgumentError: Integer.pow: Integer ** negative Integer (2 ** -1) is an error; for a Rational, write 2r ** -1
```ruby error
p(Integer.pow(2, -1, 7)) # !> RangeError: Integer.pow: Integer#pow() 1st argument cannot be negative when 2nd argument specified
13.9 gcd, lcm
Integer.gcd(x, Integer)
Integer.lcm(x, Integer)
The greatest common divisor and the least common multiple, always non-negative, as Ruby’s. gcd(0, n) is |n| and gcd(0, 0) is 0; lcm(0, n) is 0.
p(Integer.gcd(12, 18)) # => 6
p(Integer.gcd(-12, 18)) # => 6
p(Integer.gcd(0, 0)) # => 0
p(Integer.lcm(4, 6)) # => 12
p(Integer.lcm(0, 6)) # => 0
13.10 gcdlcm
Integer.gcdlcm(x, Integer)
The Tuple [gcd, lcm].
g, l = Integer.gcdlcm(4, 6)
p([g, l]) # => [2, 12]
13.11 sqrt
Integer.sqrt(x)
The integer square root: the largest Integer whose square does not exceed the number (Ruby’s Integer.sqrt), exact at any size, where Math.sqrt would give a Float. A negative number raises Math::DomainError (Ruby’s message), which can be rescued.
p(Integer.sqrt(16)) # => 4
p(Integer.sqrt(17)) # => 4
p(Integer.sqrt(10 ** 20)) # => 10000000000
```ruby error p(Integer.sqrt(-1)) # !> Math::DomainError: Integer.sqrt: Numerical argument is out of domain - “isqrt”
## abs, magnitude
`Integer.abs(x)`
`Integer.magnitude(x)`
The absolute value, an Integer. `Arithmetic.abs(x)` does the same for any real number.
```ruby
p(Integer.abs(-5)) # => 5
p(Integer.magnitude(5)) # => 5
13.12 even?, odd?
Integer.even?(x)
Integer.odd?(x)
Whether the number is even or odd; 0 is even, -3 is odd.
p(Integer.even?(4)) # => true
p(Integer.even?(-3)) # => false
p(Integer.odd?(-3)) # => true
13.13 zero?, positive?, negative?
Integer.zero?(x)
Integer.positive?(x)
Integer.negative?(x)
Whether the number is 0, above 0, or below 0. 0 is neither positive nor negative. Arithmetic.zero?(x) takes any real number.
p(Integer.zero?(0)) # => true
p(Integer.positive?(0)) # => false
p(Integer.negative?(-1)) # => true
13.14 succ, next
Integer.succ(x)
Integer.next(x)
The number plus one.
p(Integer.succ(5)) # => 6
p(Integer.next(-1)) # => 0
13.15 pred
Integer.pred(x)
The number minus one.
p(Integer.pred(5)) # => 4
p(Integer.pred(0)) # => -1
13.16 times
Integer.times(x) { }
Calls the block with 0, 1, …, n-1 and returns n (the subject), as Ruby’s. For n of 0 or less the block is not called. The block is required (Ruby’s times without a block gives an Enumerator); it takes one parameter. break v ends the loop and makes v the result; next goes to the next count; return inside a function returns from the function.
n = Integer.times(3) { |i| puts(i) } # => 0
# => 1
# => 2
p(n) # => 3
sum = 0
Integer.times(4) { |i| sum += i }
p(sum) # => 6
p(Integer.times(0) { |i| puts(i) }) # => 0
p(Integer.times(3) { |i| break i * 10 if i == 1 }) # => 10
```ruby error Integer.times(3) # !> Integer.times requires a block
## upto
`Integer.upto(x, Integer) { }`
Calls the block with a, a+1, ..., b and returns a (the subject), as Ruby's `a.upto(b)`. When a is above b the block is not called. The block is required.
```ruby
p(Integer.upto(1, 3) { |i| puts(i) }) # => 1
# => 2
# => 3
# => 1
p(Integer.upto(3, 1) { |i| puts(i) }) # => 3
13.17 downto
Integer.downto(x, Integer) { }
Calls the block with a, a-1, …, b and returns a, as Ruby’s a.downto(b). When a is below b the block is not called. The block is required.
p(Integer.downto(3, 1) { |i| puts(i) }) # => 3
# => 2
# => 1
# => 3
13.18 step
Integer.step(x, Integer, Integer) { }
Integer.step(a, limit, step) calls the block with a, a+step, a+2·step, … while the value does not pass the limit (in the direction of the step), and returns a, as Ruby’s a.step(limit, step). Both the limit and the step are required Integers (Ruby’s Float steps and keyword forms do not exist); a step of 0 is an ArgumentError. A negative step counts down.
p(Integer.step(1, 10, 3) { |i| puts(i) }) # => 1
# => 4
# => 7
# => 10
# => 1
p(Integer.step(10, 1, -3) { |i| print(i, " ") }) # => 10 7 4 1 10
```ruby error Integer.step(1, 10, 0) { |i| puts(i) } # !> ArgumentError: Integer.step: step can’t be 0
## clamp
`Integer.clamp(x, Integer, Integer)`
The number limited to the interval: `lo` when below it, `hi` when above it, otherwise itself. Unlike Ruby's, there is no Range form (`clamp(1..10)`). A `lo` above `hi` is an `ArgumentError` (`min argument must be less than or equal to max argument`).
```ruby
p(Integer.clamp(5, 1, 10)) # => 5
p(Integer.clamp(-5, 1, 10)) # => 1
p(Integer.clamp(50, 1, 10)) # => 10
```ruby error p(Integer.clamp(5, 10, 1)) # !> ArgumentError: Integer.clamp: min argument must be less than or equal to max argument
## between?
`Integer.between?(x, Integer, Integer)`
True when `lo <= x <= hi`. Both bounds are included; with `lo` above `hi` the result is false.
```ruby
p(Integer.between?(5, 1, 10)) # => true
p(Integer.between?(1, 1, 10)) # => true
p(Integer.between?(0, 1, 10)) # => false
13.19 round
Integer.round(x, [Integer])
Without digits, the number itself. With a negative digit count -d, the number rounded to a multiple of 10^d, halves away from zero, as Ruby’s (Integer.round(1250, -2) is 1300, Integer.round(-1250, -2) is -1300); a positive count leaves the number unchanged. The result is always an Integer. Only for an Integer subject: a Float is a type problem whose hint names Arithmetic.round(x), which rounds any real number.
p(Integer.round(1234)) # => 1234
p(Integer.round(1234, -2)) # => 1200
p(Integer.round(1250, -2)) # => 1300
p(Integer.round(-1250, -2)) # => -1300
p(Integer.round(1234, 2)) # => 1234
```ruby error p(Integer.round(2.5)) # !> Integer.round: argument 1 must be Integer, but is Float
## floor, ceil
`Integer.floor(x, [Integer])`
`Integer.ceil(x, [Integer])`
Without digits, the number itself. With a negative digit count -d, the number rounded down (`floor`, toward minus infinity) or up (`ceil`, toward plus infinity) to a multiple of 10^d, as Ruby's. Always an Integer. For a Float, use `Arithmetic.floor` or `Float.floor`.
```ruby
p(Integer.floor(1234, -2)) # => 1200
p(Integer.floor(-1234, -2)) # => -1300
p(Integer.ceil(1234, -2)) # => 1300
p(Integer.ceil(-1234, -2)) # => -1200
p(Integer.floor(5)) # => 5
13.20 truncate
Integer.truncate(x, [Integer])
Without digits, the number itself. With a negative digit count -d, the number rounded toward zero to a multiple of 10^d. Always an Integer.
p(Integer.truncate(1234, -2)) # => 1200
p(Integer.truncate(-1234, -2)) # => -1200
13.21 digits
Integer.digits(x)
A new Array of the decimal digits, least significant first, as Ruby’s digits without a base (Integer.digits(1234) is [4, 3, 2, 1]; 0 gives [0]). It is a typed Integer[]: pushing a value of another type is a type problem. A negative number raises Math::DomainError (out of domain). Unlike Ruby’s, there is no base argument: use to_s(n, base) and String.chars.
p(Integer.digits(1234)) # => [4, 3, 2, 1]
p(Integer.digits(0)) # => [0]
p(Array.sum(Integer.digits(999))) # => 27
d = Integer.digits(12)
Array.push(d, 9)
p(d) # => [2, 1, 9]
```ruby error Array.push(Integer.digits(12), “x”) # !> Array.push: an element must be Integer, but is String
```ruby error
p(Integer.digits(-1)) # !> Math::DomainError: Integer.digits: out of domain
13.22 to_s
Integer.to_s(x, [Integer])
The number written in the given base (2 to 36; default 10), as a String; lowercase letters stand for digits above 9, and a negative number gets a leading -. A base outside 2..36 is an ArgumentError (invalid radix 1). String.to_i(s, base) reads it back.
p(Integer.to_s(255)) # => "255"
p(Integer.to_s(255, 2)) # => "11111111"
p(Integer.to_s(-255, 16)) # => "-ff"
p(Integer.to_s(35, 36)) # => "z"
```ruby error p(Integer.to_s(255, 37)) # !> ArgumentError: Integer.to_s: invalid radix 37
## to_f
`Integer.to_f(x)`
The number as a Float (the nearest one for a number too large to be exact). `Arithmetic.to_f(x)` takes any real number.
```ruby
p(Integer.to_f(3)) # => 3.0
13.23 to_i, to_int
Integer.to_i(x)
Integer.to_int(x)
The number itself. They exist for code written for any real number; Arithmetic.to_i(x) truncates a Float or Rational to an Integer. Note that Integer(s), the conversion of a String, is Kernel’s, not this operation.
p(Integer.to_i(3)) # => 3
p(Integer.to_int(-3)) # => -3
13.24 to_r
Integer.to_r(x)
The number as a Rational with denominator 1.
p(Integer.to_r(3)) # => (3/1)
13.25 rationalize
Integer.rationalize(x, [Integer|Float|Rational])
The number as a Rational, as to_r; the optional tolerance is accepted for symmetry with Float.rationalize and makes no difference for an Integer.
p(Integer.rationalize(3)) # => (3/1)
p(Integer.rationalize(3, 0.5)) # => (3/1)
13.26 numerator, denominator
Integer.numerator(x)
Integer.denominator(x)
Seen as a fraction, the number is its own numerator over 1.
p(Integer.numerator(6)) # => 6
p(Integer.denominator(6)) # => 1
13.27 integer?
Integer.integer?(x)
Always true for an Integer (Ruby’s Numeric#integer?). Use x in Integer to test the type of a value that may be something else.
p(Integer.integer?(6)) # => true
13.28 size
Integer.size(x)
The number of bytes in the machine representation, as Ruby’s: 8 for a number that fits a machine word, more for larger ones. For the number of significant bits use bit_length.
p(Integer.size(1)) # => 8
p(Integer.size(2 ** 100)) # => 13
13.29 chr
Integer.chr(x)
A one-character String whose byte is the number, which must be 0 to 255; outside that it raises RangeError (256 out of char range), which can be rescued. As Ruby’s chr without an encoding, 0 to 127 give an ASCII String and 128 to 255 a one-byte binary String, which raises EncodingError when joined with UTF-8 text. String.ord is the reverse. For a code point above 255 build the String another way (format("%c", n)).
p(Integer.chr(65)) # => "A"
puts(Integer.chr(97)) # => a
p(Integer.chr(10)) # => "\n"
p(String.ord(Integer.chr(200))) # => 200
begin
Integer.chr(200) + "é"
rescue EncodingError => e
p(Exception.message(e)) # => "incompatible character encodings: BINARY (ASCII-8BIT) and UTF-8"
end
```ruby error p(Integer.chr(256)) # !> RangeError: Integer.chr: 256 out of char range
## ord
`Integer.ord(x)`
The number itself (Ruby's `Integer#ord`), the counterpart of `String.ord`, which gives the code point of a String's first character.
```ruby
p(Integer.ord(65)) # => 65
p(String.ord("A")) # => 65
14. Float
A Float is an IEEE 754 double-precision floating-point number. The literals are 1.5, 2.0, 1e-3 (Values and types). The conversion from a String, Float("1.5"), is a Kernel operation, and from an Integer it is Integer.to_f(n). There is no implicit conversion of an Integer where a Float is expected: Float[1] and Float.clamp(x, 1, 2) are type problems statically.
The operators on Floats are +, -, *, /, %, ** and ==, !=, <, <=, >, >=, <=>. The right operand may be an Integer, a Float, a Rational or a Complex, and the result type follows the closed table: with an Integer or a Rational the result is a Float, with a Complex it is a Complex (Operators and indexing). The entries Float.+(x, y) and so on below are the function forms of those operators; since the left operand is known to be a Float, the checker is stricter about the right one.
As in Ruby, dividing by zero gives Infinity, -Infinity or NaN rather than an exception (%, modulo and divmod are the exceptions: ZeroDivisionError), and the operations that turn a NaN or an Infinity into an Integer (to_i, floor, ceil, round, truncate, to_r, numerator, denominator) raise FloatDomainError. Ruby’s constants Float::INFINITY and so on are read in Sake as operations without arguments, Float.INFINITY (Sake has no value constants). The main differences from Ruby: round(f, 2) with a digit count always returns a Float, a negative base with a fractional exponent ((-8.0) ** 0.5) is a Math::DomainError instead of a Complex, divmod returns a Tuple, and the checker follows the nil of <=> and infinite?.
14.1 Float[]
Float[*Any]
Makes an Array of Floats (not a Float). It is a typed Array like Integer[]: every element must be a Float, and every write (Array.push, …) is checked. An Integer is not converted: Float[1.5, 2] is a type problem statically and a TypeError at run time. The empty Array of Floats is Float[].
fs = Float[1.5, 2.0]
Array.push(fs, 3.5)
p(fs) # => [1.5, 2.0, 3.5]
p(Array.size(Float[])) # => 0
p(Array.sum(fs)) # => 7.0
```ruby error Array.push(Float[], 1) # !> Array.push: an element must be Float, but is Integer
## INFINITY, NAN, EPSILON, MAX, MIN
`Float.INFINITY()`
`Float.NAN()`
`Float.EPSILON()`
`Float.MAX()`
`Float.MIN()`
Operations without arguments that return the values of Ruby's `Float::INFINITY` and so on (Sake has no value constants, so they are written `Float.INFINITY` or `Float.INFINITY()`). `INFINITY` is positive infinity (the negative one is `-Float.INFINITY`), `NAN` is not-a-number, `EPSILON` is the difference between 1.0 and the smallest Float above it, `MAX` is the largest finite value, and `MIN` is the smallest positive normalized value. All are Floats. A NaN is `==` to nothing, not even itself.
```ruby
p(Float.INFINITY) # => Infinity
p(-Float.INFINITY) # => -Infinity
p(Float.NAN == Float.NAN) # => false
p(Float.EPSILON) # => 2.220446049250313e-16
p(1.0 + Float.EPSILON > 1.0) # => true
p(Float.MAX) # => 1.7976931348623157e+308
p(Float.MAX * 2) # => Infinity
p(Float.MIN) # => 2.2250738585072014e-308
14.2 +, -, *, /, %, **
Float.+(x, Any)
Float.-(x, Any)
Float.*(x, Any)
Float./(x, Any)
Float.%(x, Any)
Float.**(x, Any)
The function forms of x + y and the others with a Float on the left. The right operand y is an Integer, a Float, a Rational or a Complex. With an Integer or a Rational the result is a Float, with a Complex a Complex. Any other type (a String, nil) is a type problem statically (the operands are (Float, String), which the left operand's type does not support) and a TypeError at run time.
Division follows IEEE: 1.0 / 0 is Infinity and 0.0 / 0 is NaN, without an exception. % is Ruby’s Float#% (the result takes the sign of the right operand), but dividing by zero with it, the Integer 0 or the Float 0.0, is a ZeroDivisionError (rescuable; Ruby 4 raises it too). ** with an Integer, Float or Rational exponent is a Float; unlike Ruby, a negative base with a fractional exponent ((-8.0) ** 0.5) is a Math::DomainError rather than a Complex, so the result is always a Float.
p(1.5 + 2) # => 3.5
p(Float.+(1.5, 2r)) # => 3.5
p(Float.+(1.5, Complex(1, 2))) # => (2.5+2i)
p(Float.*(1.5, 2)) # => 3.0
p(Float./(1.0, 0)) # => Infinity
p(Float./(0.0, 0)) # => NaN
p(Float.%(7.5, 2)) # => 1.5
p(-7.5 % 2) # => 0.5
p(Float.**(2.0, 3)) # => 8.0
p(Float.**(2.0, 0.5)) # => 1.4142135623730951
p(Float.**(2.0, 2r)) # => 4.0
p((-8.0) ** 2) # => 64.0
begin
7.5 % 0.0
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
```ruby error p(Float.+(1.5, “a”)) # !> the operands are (Float, String), which the left operand’s type does not support
```ruby error
p(Float.%(7.5, 0)) # !> ZeroDivisionError: Float.%: divided by 0
```ruby error p(Float.(-8.0, 0.5)) # !> Math::DomainError: Float.: -8.0 ** 0.5 is not a real number (a negative base with a fractional exponent)
## ==, !=
`Float.==(x, Any)`
`Float.!=(x, Any)`
The function form of `x == y`. A Float compares by value with an Integer, a Rational and a Complex: `1.0 == 1` is true. A NaN equals nothing, itself included. A value of another type is never equal (`1.0 == "1"` is false), and the function form `Float.==(x, y)` gives the same false (and `!=` true) for any right operand of another type, as the operator does, without an error.
```ruby
p(Float.==(1.0, 1)) # => true
p(Float.==(1.0, 1r)) # => true
p(1.5 != 1.5) # => false
p(Float.!=(1.0, Float.NAN)) # => true
p(1.0 == "1") # => false
p(Float.==(1.0, "1")) # => false
p(Float.!=(1.0, nil)) # => true
14.3 <, <=, >, >=
Float.<(x, Any)
Float.<=(x, Any)
Float.>(x, Any)
Float.>=(x, Any)
The function forms of x < y and the others. The right operand is an Integer, a Float or a Rational (a Complex has no order; it is a type problem statically). Every comparison with a NaN is false.
p(1.5 < 2) # => true
p(Float.<=(1.5, 2r)) # => true
p(Float.>(1.5, 2)) # => false
p(Float.>=(1.5, 1.5)) # => true
p(Float.NAN < 1.0) # => false
```ruby error p(Float.<(1.0, Complex(1, 0))) # !> the operands are (Float, Complex), which the left operand’s type does not support
## <=>
`Float.<=>(x, Any)`
The function form of `x <=> y`: -1, 0 or 1. The right operand is an Integer, a Float or a Rational. When either side is a NaN the result is **nil**, so its type is `Integer | nil`, and `--strict` (the `nil` item of level 2) reports using it in arithmetic unchecked as `the operands may be nil`. Test it first with `if c`.
```ruby
p(Float.<=>(1.5, 2)) # => -1
p(1.5 <=> 1.5) # => 0
p(Float.<=>(Float.NAN, 1.0)) # => nil
c = 2.0 <=> 1
if c
p(c + 1) # => 2
end
```ruby error c = Float.<=>(1.5, 2.0) p(c + 1) # !> the operands may be nil
## abs, magnitude
`Float.abs(x)`
`Float.magnitude(x)`
The absolute value (a Float). `-0.0` gives `0.0` and `-Infinity` gives `Infinity`.
```ruby
p(Float.abs(-1.5)) # => 1.5
p(Float.magnitude(-1.5)) # => 1.5
p(Float.abs(-0.0)) # => 0.0
p(Float.abs(-Float.INFINITY)) # => Infinity
14.4 negative?, positive?, zero?
Float.negative?(x)
Float.positive?(x)
Float.zero?(x)
x < 0, x > 0 and x == 0 as true/false. For -0.0, zero? is true and both negative? and positive? are false. All three are false for a NaN.
p(Float.negative?(-1.5)) # => true
p(Float.negative?(-0.0)) # => false
p(Float.positive?(0.0)) # => false
p(Float.zero?(-0.0)) # => true
p(Float.zero?(1.5)) # => false
14.5 nan?, finite?, infinite?
Float.nan?(x)
Float.finite?(x)
Float.infinite?(x)
nan? is true for a NaN. finite? is true when x is neither a NaN nor infinite. infinite? returns, as Ruby’s does, 1 for positive infinity, -1 for negative infinity and nil otherwise (a NaN included), so its type is Integer | nil. It is usually used as a condition, if Float.infinite?(x); to use the value in arithmetic, check it for nil first (--strict’s nil item reports the unchecked use).
p(Float.nan?(0.0 / 0)) # => true
p(Float.nan?(1.5)) # => false
p(Float.finite?(1.5)) # => true
p(Float.finite?(Float.INFINITY)) # => false
p(Float.infinite?(1.5)) # => nil
p(Float.infinite?(Float.INFINITY)) # => 1
p(Float.infinite?(-Float.INFINITY)) # => -1
14.6 to_i, to_int, truncate
Float.to_i(x)
Float.to_int(x)
Float.truncate(x)
Drop the fraction, giving the Integer nearest to zero (Ruby’s Float#to_i). The three give the same result. A NaN or an Infinity cannot become an Integer: FloatDomainError, whose message is the value (NaN, Infinity). Ruby’s digit-count argument of truncate(digits) does not exist on Float; use Arithmetic.truncate(x, n) (Arithmetic).
p(Float.to_i(1.9)) # => 1
p(Float.to_int(-1.9)) # => -1
p(Float.truncate(-1.8)) # => -1
begin
Float.to_i(Float.NAN)
rescue FloatDomainError => e
p(Exception.message(e)) # => "NaN"
end
```ruby error p(Float.truncate(Float.INFINITY)) # !> FloatDomainError: Float.truncate: Infinity
## floor, ceil
`Float.floor(x)`
`Float.ceil(x)`
`floor` is the largest Integer not above x, `ceil` the smallest Integer not below it. A NaN or an Infinity is a `FloatDomainError`. There is no digit-count argument; `Arithmetic.floor(x, n)` and `Arithmetic.ceil(x, n)` take one.
```ruby
p(Float.floor(1.8)) # => 1
p(Float.floor(-1.2)) # => -2
p(Float.ceil(1.2)) # => 2
p(Float.ceil(-1.2)) # => -1
```ruby error p(Float.floor(Float.INFINITY)) # !> FloatDomainError: Float.floor: Infinity
## round
`Float.round(x, [Integer], [half: Symbol])`
Rounds. Without a digit count it returns the nearest Integer, rounding halves away from zero by default (`Float.round(2.5)` is 3 and `-2.5` gives -3, as Ruby's `round`). With a digit count `n` it returns a **Float** rounded to n decimal places. A negative count rounds to a power of ten, but the result is still a Float (Ruby's `1234.5.round(-2)` is the Integer 1200; Sake gives `1200.0`). The result type depends on whether a count is given because the checker looks at the number of arguments. The keyword `half:` chooses how an exact half is rounded, as Ruby's: `:up` (the default, away from zero), `:even` (to the even neighbour, banker's rounding), `:down` (toward zero); another Symbol is an `ArgumentError` (`invalid rounding mode: foo`), and a non-Symbol is a `type` problem. A NaN or an Infinity without a count is a `FloatDomainError`; with a count it is returned unchanged.
```ruby
p(Float.round(1.5)) # => 2
p(Float.round(2.5)) # => 3
p(Float.round(-2.5)) # => -3
p(Float.round(1.2345, 2)) # => 1.23
p(Float.round(3.14159, 3)) # => 3.142
p(Float.round(1234.5, -2)) # => 1200.0
p(Float.round(1.5, 0)) # => 2.0
p(Float.round(Float.NAN, 2)) # => NaN
p(Float.round(2.5, half: :even)) # => 2
p(Float.round(3.5, half: :even)) # => 4
p(Float.round(2.5, half: :down)) # => 2
p(Float.round(2.5, 0, half: :even)) # => 2.0
p(Float.round(1.25, 1, half: :even)) # => 1.2
p(Float.round(1250.0, -2, half: :even)) # => 1200.0
```ruby error p(Float.round(-Float.INFINITY)) # !> FloatDomainError: Float.round: -Infinity
```ruby error
p(Float.round(2.5, half: :foo)) # !> ArgumentError: Float.round: invalid rounding mode: foo
14.7 divmod
Float.divmod(x, Any)
The Tuple [q, r] of quotient and remainder: q is x / y floored, an Integer, and r is x - q * y, a Float with the sign of y (as Ruby’s 7.5.divmod(2) is [3, 1.5]). y is a Float or an Integer; any other type, a Rational included, is a type problem statically. Dividing by zero, the Integer 0 or the Float 0.0, is a ZeroDivisionError; an x that is a NaN or an Infinity is a FloatDomainError (its message is the value). Both can be rescued.
p(Float.divmod(7.5, 2)) # => [3, 1.5]
p(Float.divmod(-7.5, 2.0)) # => [-4, 0.5]
q, r = Float.divmod(7.5, 2)
p(q + 1) # => 4
p(r + 0.5) # => 2.0
begin
Float.divmod(Float.NAN, 2)
rescue FloatDomainError => e
p(Exception.message(e)) # => "NaN"
end
```ruby error p(Float.divmod(7.5, 0)) # !> ZeroDivisionError: Float.divmod: divided by 0
```ruby error
p(Float.divmod(7.5, 0.0)) # !> ZeroDivisionError: Float.divmod: divided by 0
14.8 fdiv, quo
Float.fdiv(x, Integer|Float|Rational)
Float.quo(x, Integer|Float|Rational)
Floating-point division x / y (a Float). The result is that of /: dividing by zero gives Infinity or NaN, without an exception. The two are the same operation.
p(Float.fdiv(7.5, 2)) # => 3.75
p(Float.quo(1.0, 4r)) # => 0.25
p(Float.fdiv(1.0, 0)) # => Infinity
14.9 modulo
Float.modulo(x, Integer|Float|Rational)
The remainder of x % y (a Float with the sign of y), the same operation as %. Dividing by zero (the Integer 0 or 0.0) raises ZeroDivisionError.
p(Float.modulo(7.5, 2)) # => 1.5
p(Float.modulo(-7.5, 2)) # => 0.5
p(Float.modulo(5.5, 2.5)) # => 0.5
begin
Float.modulo(7.5, 0)
rescue ZeroDivisionError => e
p(Exception.message(e)) # => "divided by 0"
end
14.10 between?
Float.between?(x, Float, Float)
True when lo <= x <= hi. lo and hi must be Floats; an Integer is a type problem statically (argument 2 must be Float, but is Integer).
p(Float.between?(1.5, 1.0, 2.0)) # => true
p(Float.between?(2.5, 1.0, 2.0)) # => false
```ruby error p(Float.between?(1.5, 1, 2)) # !> Float.between?: argument 2 must be Float, but is Integer
## clamp
`Float.clamp(x, Float, Float)`
The Float `x` confined to `lo..hi` (`lo` when x is below it, `hi` when above). `lo` and `hi` are Floats only. `lo > hi` is an `ArgumentError` (`min argument must be less than or equal to max argument`), which can be rescued.
```ruby
p(Float.clamp(2.5, 1.0, 2.0)) # => 2.0
p(Float.clamp(0.5, 1.0, 2.0)) # => 1.0
p(Float.clamp(1.5, 1.0, 2.0)) # => 1.5
```ruby error p(Float.clamp(1.5, 1, 2)) # !> Float.clamp: argument 2 must be Float, but is Integer
```ruby error
p(Float.clamp(1.5, 2.0, 1.0)) # !> ArgumentError: Float.clamp: min argument must be less than or equal to max argument
14.11 next_float, prev_float
Float.next_float(x)
Float.prev_float(x)
The next representable Float above or below x. next_float(1.0) - 1.0 is Float.EPSILON. The Float after Float.MAX is Infinity; the one after a NaN is a NaN.
p(Float.next_float(1.0)) # => 1.0000000000000002
p(Float.prev_float(1.0)) # => 0.9999999999999999
p(Float.next_float(0.0)) # => 5.0e-324
p(Float.next_float(1.0) - 1.0 == Float.EPSILON) # => true
p(Float.next_float(Float.MAX)) # => Infinity
14.12 to_r, rationalize
Float.to_r(x)
Float.rationalize(x)
Convert to a Rational. to_r takes the binary value as it is, so 0.1 becomes 3602879701896397/36028797018963968. rationalize picks the simplest fraction that rounds back to the same Float, so 0.1 becomes 1/10. A NaN or an Infinity is a FloatDomainError. Ruby’s tolerance argument rationalize(eps) does not exist on Float; Rational.rationalize(r, eps) takes one (Rational).
p(Float.to_r(0.75)) # => (3/4)
p(Float.to_r(0.1)) # => (3602879701896397/36028797018963968)
p(Float.rationalize(0.1)) # => (1/10)
p(Float.rationalize(0.333)) # => (333/1000)
p(Float.to_r(-1.5)) # => (-3/2)
```ruby error p(Float.to_r(Float.NAN)) # !> FloatDomainError: Float.to_r: NaN
## numerator, denominator
`Float.numerator(x)`
`Float.denominator(x)`
The numerator and denominator of `Float.to_r(x)` (Integers). The denominator of `0.1` is `36028797018963968`, the value of the binary representation. Unlike Ruby's, where `numerator` of a NaN is the NaN itself and `denominator` is 1, a NaN or an Infinity is a `FloatDomainError`, so the result is always an Integer.
```ruby
p(Float.numerator(0.75)) # => 3
p(Float.denominator(0.75)) # => 4
p(Float.numerator(1.5)) # => 3
p(Float.denominator(0.1)) # => 36028797018963968
```ruby error p(Float.numerator(Float.NAN)) # !> FloatDomainError: Float.numerator: NaN
## angle, arg, phase
`Float.angle(x)`
`Float.arg(x)`
`Float.phase(x)`
The argument in the complex plane: the Integer `0` for a positive number (and `0.0`), the Float `π` for a negative one (and `-0.0`), so the type is `Integer | Float`. A NaN gives a NaN. The three are the same operation.
```ruby
p(Float.angle(1.5)) # => 0
p(Float.arg(-1.5)) # => 3.141592653589793
p(Float.phase(-0.0)) # => 3.141592653589793
p(Float.angle(Float.NAN)) # => NaN
14.13 to_f, to_s
Float.to_f(x)
Float.to_s(x)
to_f returns x itself (to turn a value of type Integer | Float into a Float, use Arithmetic.to_f(x)). to_s is Ruby’s notation: an integral value keeps its .0, and values from about 1e16 up or below 1e-4 use the exponent form 1.0e+20. Infinity, -Infinity and NaN are spelled out. puts(1.5) prints the same notation.
p(Float.to_f(1.5)) # => 1.5
p(Float.to_s(1.0)) # => "1.0"
p(Float.to_s(0.1 + 0.2)) # => "0.30000000000000004"
p(Float.to_s(1e20)) # => "1.0e+20"
p(Float.to_s(1e-5)) # => "1.0e-05"
p(Float.to_s(Float.INFINITY)) # => "Infinity"
p(Float.to_s(-0.0)) # => "-0.0"
15. Rational
A Rational is a rational number with an Integer numerator and denominator, always kept in lowest terms. The literals are Ruby’s: 2r, 1/3r (the division 1 / 3r), 0.5r (1/2); Rational(1, 3), Rational("1/3") and Rational(0.5) (which is (1/2), as in Ruby) are Kernel operations; Float.to_r and Float.rationalize also convert a Float (see Values and types). p shows a Rational as (1/3).
The operators on Rationals are +, -, *, /, %, ** and ==, !=, <, <=, >, >=, <=>. The right operand may be an Integer, a Float, a Rational or a Complex, and the result type follows the closed table: with an Integer the result is a Rational, with a Float a Float, with a Complex a Complex (Operators and indexing). 1 + 1r/2, with the Integer on the left, is a Rational too. An Integer raised to a negative Integer, 2 ** -1, is a Rational in Ruby but an ArgumentError in Sake: write 2r ** -1. The entries Rational.+(x, y) and so on below are the function forms of those operators.
As in Ruby, dividing a Rational by zero is a ZeroDivisionError (unlike a Float, it does not become infinite). The differences from Ruby: round, floor, ceil and truncate take no digit count (use Arithmetic.round(r, n), see Arithmetic), and the arguments of quo and rationalize do not accept a Float.
15.1 Rational[]
Rational[*Any]
Makes an Array of Rationals (not a Rational). Every element must be a Rational, and every write (Array.push, …) is checked. An Integer is not converted: it is a type problem statically and a TypeError at run time. The empty Array of Rationals is Rational[].
rs = Rational[1r/2, 2r]
Array.push(rs, Rational(1, 3))
p(rs) # => [(1/2), (2/1), (1/3)]
p(Array.size(Rational[])) # => 0
```ruby error Array.push(Rational[], 1) # !> Array.push: an element must be Rational, but is Integer
## +, -, *, /, %, **
`Rational.+(x, Any)`
`Rational.-(x, Any)`
`Rational.*(x, Any)`
`Rational./(x, Any)`
`Rational.%(x, Any)`
`Rational.**(x, Any)`
The function forms of `x + y` and the others with a Rational on the left. The right operand `y` is an Integer, a Float, a Rational or a Complex. With an Integer or a Rational the result is a Rational, with a Float a Float, with a Complex a Complex. Any other type is a `type` problem statically and a `TypeError` at run time. A right operand of zero (`0`, `0r`) in `/` or `%` is a `ZeroDivisionError` (`0.0` follows the Float rules and gives `Infinity`). `%` is Ruby's `Rational#%`: the result takes the sign of the right operand. `**` with an Integer exponent is a Rational (a negative one too: `2r ** -1` is `(1/2)`) and with a Float exponent a Float. With a **Rational exponent** the result is a Rational when the exponent is integral (`4r ** 2r` is `(16/1)`) and a Float otherwise (`4r ** (1r/2)` is `2.0`), so the checker types it `Rational | Float`: narrow it with `case`/`in` before using it as one of the two (`Rational.numerator(4r ** 2r)` is a `type` problem). A negative base with a fractional exponent is a `Math::DomainError` (Ruby gives a Complex).
```ruby
p(1r/3 + 1r/6) # => (1/2)
p(Rational.+(1r/3, 1)) # => (4/3)
p(Rational.+(1r/3, 0.5)) # => 0.8333333333333333
p(Rational.+(1r/3, Complex(1, 1))) # => ((4/3)+1i)
p(Rational.-(1r, 1r/3)) # => (2/3)
p(Rational.*(2r/3, 3r/4)) # => (1/2)
p(Rational./(1r/3, 2)) # => (1/6)
p(Rational.%(7r/3, 1r/2)) # => (1/3)
p(Rational.**(2r, -1)) # => (1/2)
p(Rational.**(1r/2, 3)) # => (1/8)
p(Rational.**(4r, 2r)) # => (16/1)
p(Rational.**(2r, 1r/2)) # => 1.4142135623730951
x = 4r ** (1r/2)
case x
in Rational then p(Rational.numerator(x))
in Float then p(x) # => 2.0
end
```ruby error p(Rational./(1r, 0)) # !> ZeroDivisionError: Rational./: divided by 0
```ruby error
p(Rational.numerator(4r ** 2r)) # !> Rational.numerator: argument 1 must be Rational, but can be Float
```ruby error p((-8r) ** (1r/2)) # !> Math::DomainError: Arithmetic.**: -8/1 ** 1/2 is not a real number (a negative base with a fractional exponent)
## ==, !=
`Rational.==(x, Any)`
`Rational.!=(x, Any)`
The function form of `x == y`. A Rational compares by value with an Integer, a Float and a Complex: `1r/2 == 0.5` and `2r == 2` are true. A value of another type is never equal, and the function form `Rational.==(x, y)` gives the same false (and `!=` true) for any right operand of another type, as the operator does, without an error.
```ruby
p(Rational.==(1r/2, 0.5)) # => true
p(Rational.==(2r, 2)) # => true
p(Rational.!=(1r/2, 1r/3)) # => true
p(1r == nil) # => false
p(Rational.==(1r/2, "x")) # => false
p(Rational.!=(1r/2, "x")) # => true
15.2 <, <=, >, >=
Rational.<(x, Any)
Rational.<=(x, Any)
Rational.>(x, Any)
Rational.>=(x, Any)
The function forms of x < y and the others. The right operand is an Integer, a Float or a Rational (a Complex has no order; it is a type problem statically). Comparisons with a Rational or an Integer are exact; with a Float the Float’s value is used.
p(1r/3 < 1r/2) # => true
p(Rational.<(1r/3, 0.5)) # => true
p(Rational.<=(1r/2, 0.5)) # => true
p(Rational.>(1r/3, 0.3)) # => true
p(Rational.>=(1r, 1)) # => true
```ruby error p(Rational.<(1r, Complex(1, 0))) # !> the operands are (Rational, Complex), which the left operand’s type does not support
## <=>
`Rational.<=>(x, Any)`
The function form of `x <=> y`: -1, 0 or 1. With an Integer or a Rational on the right the result is always an Integer. With a Float on the right it is **nil** for a NaN, so the type is `Integer | nil`, and `--strict` (the `nil` item of level 2) reports using it in arithmetic unchecked.
```ruby
p(Rational.<=>(1r/3, 1r/2)) # => -1
p(Rational.<=>(1r/2, 1r/2)) # => 0
p(Rational.<=>(1r, 1r/2)) # => 1
p(Rational.<=>(1r/2, Float.NAN)) # => nil
c = Rational.<=>(1r/3, 1r/2)
p(c + 1) # => 0
```ruby error c = Rational.<=>(1r/3, 0.5) p(c + 1) # !> the operands may be nil
## abs, magnitude
`Rational.abs(x)`
`Rational.magnitude(x)`
The absolute value (a Rational). The two are the same operation.
```ruby
p(Rational.abs(-1r/3)) # => (1/3)
p(Rational.magnitude(-1r/3)) # => (1/3)
p(Rational.abs(-5r/2)) # => (5/2)
15.3 negative?, positive?, zero?
Rational.negative?(x)
Rational.positive?(x)
Rational.zero?(x)
x < 0, x > 0 and x == 0 as true/false.
p(Rational.positive?(1r/3)) # => true
p(Rational.positive?(0r)) # => false
p(Rational.negative?(-1r/3)) # => true
p(Rational.zero?(0r)) # => true
p(Rational.zero?(1r/3)) # => false
15.4 numerator, denominator
Rational.numerator(x)
Rational.denominator(x)
The numerator and denominator in lowest terms (Integers). The numerator carries the sign; the denominator is always positive, and 1 for an integral value.
p(Rational.numerator(-6r/4)) # => -3
p(Rational.denominator(-6r/4)) # => 2
p(Rational.numerator(1r/2 + 1r/2)) # => 1
p(Rational.denominator(1r/2 + 1r/2)) # => 1
15.5 to_i, truncate, floor, ceil, round
Rational.to_i(x)
Rational.truncate(x)
Rational.floor(x)
Rational.ceil(x)
Rational.round(x)
Convert to an Integer: to_i and truncate toward zero, floor downward, ceil upward, round to the nearest Integer (an exact half goes away from zero: Rational.round(5r/2) is 3 and -5r/2 gives -3). Ruby’s digit count, round(digits) and the like, does not exist on these; to round to a number of digits use Arithmetic.round(x, n), whose result is a Rational.
p(Rational.to_i(-7r/2)) # => -3
p(Rational.truncate(7r/3)) # => 2
p(Rational.floor(-7r/2)) # => -4
p(Rational.ceil(7r/2)) # => 4
p(Rational.round(7r/2)) # => 4
p(Rational.round(5r/2)) # => 3
p(Rational.round(-1r/2)) # => -1
```ruby error p(Rational.round(7r/3, 1)) # !> wrong number of arguments for Rational.round (given 2, expected 1)
## fdiv
`Rational.fdiv(x, Integer|Float|Rational)`
The floating-point division `x / y` as a **Float**. Dividing by zero gives `Infinity`, without an exception.
```ruby
p(Rational.fdiv(1r/3, 2)) # => 0.16666666666666666
p(Rational.fdiv(2r/3, 1r/3)) # => 2.0
p(Rational.fdiv(1r, 0)) # => Infinity
15.6 quo
Rational.quo(x, Integer|Rational)
The exact division x / y (a Rational). y is an Integer or a Rational; a Float is a type problem statically (to divide by a Float use /). Dividing by zero is a ZeroDivisionError.
p(Rational.quo(1r/3, 2)) # => (1/6)
p(Rational.quo(1r/3, 2r/3)) # => (1/2)
```ruby error p(Rational.quo(1r/3, 0)) # !> ZeroDivisionError: Rational.quo: divided by 0
## rationalize
`Rational.rationalize(x, [Rational])`
The simplest fraction within the tolerance `eps` (a Rational), that is, in `x - eps .. x + eps` (Ruby's `Rational#rationalize`). Without `eps` it returns x itself. `eps` must be a Rational; a Float is a `type` problem statically.
```ruby
pi = 3141592653589793r/1000000000000000
p(Rational.rationalize(pi, 1r/100)) # => (22/7)
p(Rational.rationalize(pi, 1r/1000000)) # => (355/113)
p(Rational.rationalize(1r/3)) # => (1/3)
```ruby error p(Rational.rationalize(1r/3, 0.1)) # !> Rational.rationalize: argument 2 must be Rational, but is Float
## to_f
`Rational.to_f(x)`
The nearest Float.
```ruby
p(Rational.to_f(1r/4)) # => 0.25
p(Rational.to_f(2r/3)) # => 0.6666666666666666
15.7 to_r
Rational.to_r(x)
Returns x itself (for uniform handling of a value of type Integer | Rational).
p(Rational.to_r(1r/3)) # => (1/3)
15.8 to_s
Rational.to_s(x)
The String "numerator/denominator" (as Ruby’s). An integral value keeps its denominator: "2/1". It differs from p’s (1/3) only by the parentheses.
p(Rational.to_s(1r/3)) # => "1/3"
p(Rational.to_s(2r)) # => "2/1"
p(Rational.to_s(-6r/4)) # => "-3/2"
16. Complex
A Complex is a complex number with a real and an imaginary part, each an Integer, a Float or a Rational. The literals are Ruby’s imaginary units 2i, 3.5i and expressions with them such as 1 + 2i; Complex(1, 2) (and Complex(1.5) without an imaginary part) is a Kernel operation whose arguments are real numbers only (Values and types). p shows a Complex as (1+2i).
The operators on Complexes are +, -, *, /, ** and ==, !=. A Complex has no order, so there is no < and the like, and no % (both are type problems statically). The right operand may be an Integer, a Float, a Rational or a Complex, and the result is always a Complex; with an Integer, a Float or a Rational on the left and a Complex on the right the result is a Complex as well (Operators and indexing). The entries Complex.+(x, y) and so on below are the function forms of those operators.
The parts keep their types inside the value: Complex(1, 2) / 2 is ((1/2)+1i), with a Rational real part (as in Ruby). That is why real and imag have the result type Integer | Float | Rational; to use a part as an Integer, narrow it with case/in. The difference from Ruby: a part cannot be a Complex.
16.1 Complex[]
Complex[*Any]
Makes an Array of Complexes (not a Complex). Every element must be a Complex, and every write (Array.push, …) is checked. A real number is not converted: it is a type problem statically and a TypeError at run time. The empty Array of Complexes is Complex[].
cs = Complex[Complex(1, 2), 3i]
Array.push(cs, Complex(0, 0))
p(cs) # => [(1+2i), (0+3i), (0+0i)]
p(Array.size(Complex[])) # => 0
```ruby error Array.push(Complex[], 1) # !> Array.push: an element must be Complex, but is Integer
## +, -, *, /, **
`Complex.+(x, Any)`
`Complex.-(x, Any)`
`Complex.*(x, Any)`
`Complex./(x, Any)`
`Complex.**(x, Any)`
The function forms of `x + y` and the others with a Complex on the left. The right operand `y` is an Integer, a Float, a Rational or a Complex, and the result is a Complex. Any other type is a `type` problem statically and a `TypeError` at run time. `/` computes exactly according to the parts' types, so Integer parts become Rationals. Dividing a Complex whose parts are all exact (Integer, Rational) by 0 or by `Complex(0, 0)` is a `ZeroDivisionError`; so is dividing one with Float parts by the Integer 0, while dividing by `0.0` follows the Float rules and gives `Infinity` parts (as in Ruby). `**` is exact for an integral power and gives Float parts otherwise. There is no `%` on Complex.
```ruby
c = Complex(1, 2)
p(c + Complex(3, 4)) # => (4+6i)
p(Complex.+(c, 1.5)) # => (2.5+2i)
p(Complex.-(Complex(5, 3), 2)) # => (3+3i)
p(Complex.*(c, c)) # => (-3+4i)
p(Complex.*(Complex(1, 1), Complex(1, -1))) # => (2+0i)
p(Complex./(c, 2)) # => ((1/2)+1i)
p(Complex./(Complex(1.0, 2), 2)) # => (0.5+1i)
p(Complex./(Complex(1, 2), 0.0)) # => (Infinity+Infinity*i)
p(Complex.**(Complex(0, 1), 2)) # => (-1+0i)
p(Complex.**(c, -1)) # => ((1/5)-(2/5)*i)
p(1.5 * c) # => (1.5+3.0i)
```ruby error p(Complex./(Complex(1, 2), 0)) # !> ZeroDivisionError: Complex./: divided by 0
## ==, !=
`Complex.==(x, Any)`
`Complex.!=(x, Any)`
The function form of `x == y`. True when the real parts and the imaginary parts are each `==`; `Complex(1, 0) == 1` and `Complex(1.0, 0) == 1` are true. A value of another type is never equal (`Complex(1, 2) == "x"` is false), and the function form `Complex.==(x, y)` gives the same false (and `!=` true) for any right operand of another type, as the operator does, without an error.
```ruby
p(Complex.==(Complex(1, 2), Complex(1, 2))) # => true
p(Complex.==(Complex(1, 0), 1)) # => true
p(Complex.==(Complex(2, 0), 2.0)) # => true
p(Complex.!=(Complex(1, 2), Complex(2, 1))) # => true
p(Complex(1, 2) == "x") # => false
p(Complex.==(Complex(1, 2), "x")) # => false
p(Complex.==(Complex(1, 2), nil)) # => false
16.2 real, imag, imaginary
Complex.real(x)
Complex.imag(x)
Complex.imaginary(x)
The real and the imaginary part. The type is that of the part as constructed (Integer | Float | Rational). imag and imaginary are the same operation.
c = Complex(1, 2.5)
p(Complex.real(c)) # => 1
p(Complex.imag(c)) # => 2.5
p(Complex.imaginary(Complex(3, 4))) # => 4
p(Complex.real(Complex(1r/2, 1))) # => (1/2)
r = Complex.real(Complex(1, 2))
p(r + 1) # => 2
16.3 rect, rectangular
Complex.rect(x)
Complex.rectangular(x)
The Tuple [re, im] of the two parts (each position Integer | Float | Rational). Multiple assignment takes both. The two are the same operation.
p(Complex.rect(Complex(1, 2))) # => [1, 2]
p(Complex.rectangular(Complex(1.5, 2r))) # => [1.5, (2/1)]
re, im = Complex.rectangular(Complex(1, 2))
p(re + im) # => 3
16.4 polar
Complex.polar(x)
The polar form as the Tuple [r, θ]: r is the absolute value exactly as abs gives it (Integer | Float; a Rational magnitude becomes a Float, Complex.polar(Complex(3r, 0)) is [3.0, 0.0]) and θ the argument (as arg, a Float; 0.0 or π on the real axis).
p(Complex.polar(Complex(3, 4))) # => [5.0, 0.9272952180016122]
p(Complex.polar(Complex(0, 2))) # => [2, 1.5707963267948966]
p(Complex.polar(Complex(-2, 0))) # => [2, 3.141592653589793]
p(Complex.polar(Complex(3r, 0))) # => [3.0, 0.0]
r, th = Complex.polar(Complex(3, 4))
p(r) # => 5.0
16.5 abs, magnitude
Complex.abs(x)
Complex.magnitude(x)
The absolute value sqrt(re² + im²). Usually a Float, but as in Ruby, when one part is an exact 0 the absolute value of the other part is returned as it is, so Complex(3, 0) gives the Integer 3. Unlike Ruby’s, a Rational magnitude becomes a Float (Complex.abs(Complex(3r, 0)) is 3.0), so the type is exactly Integer | Float. The two are the same operation.
p(Complex.abs(Complex(3, 4))) # => 5.0
p(Complex.abs(Complex(3, 0))) # => 3
p(Complex.abs(Complex(0, -2))) # => 2
p(Complex.abs(Complex(3r, 0))) # => 3.0
p(Complex.magnitude(Complex(1, 1))) # => 1.4142135623730951
a = Complex.abs(Complex(3, 4))
p(a + 1) # => 6.0
16.6 abs2
Complex.abs2(x)
The square of the absolute value, re² + im². No square root is taken, so it is exact, and the type follows the parts: Integer | Float | Rational.
p(Complex.abs2(Complex(3, 4))) # => 25
p(Complex.abs2(Complex(1.5, 0))) # => 2.25
p(Complex.abs2(Complex(1r/2, 1r/2))) # => (1/2)
16.7 arg, angle, phase
Complex.arg(x)
Complex.angle(x)
Complex.phase(x)
The argument (a Float from -π to π), that is, Math.atan2(im, re): 0.0 for Complex(0, 0), π on the negative real axis. The three are the same operation.
p(Complex.arg(Complex(1, 1))) # => 0.7853981633974483
p(Complex.angle(Complex(0, 1))) # => 1.5707963267948966
p(Complex.phase(Complex(-1, 0))) # => 3.141592653589793
p(Complex.arg(Complex(0, -1))) # => -1.5707963267948966
p(Complex.arg(Complex(0, 0))) # => 0.0
16.8 conj, conjugate
Complex.conj(x)
Complex.conjugate(x)
The complex conjugate (the Complex with the sign of the imaginary part flipped). The two are the same operation.
p(Complex.conj(Complex(1, 2))) # => (1-2i)
p(Complex.conjugate(Complex(1.5, -2))) # => (1.5+2i)
p(Complex.conjugate(Complex(3, 0))) # => (3+0i)
16.9 real?
Complex.real?(x)
Always false (as Ruby’s Complex#real?: a value of type Complex is not a real number even when its imaginary part is 0). To ask whether the value is real, use Complex.imag(x) == 0.
p(Complex.real?(Complex(1, 2))) # => false
p(Complex.real?(Complex(1, 0))) # => false
p(Complex.imag(Complex(1, 0)) == 0) # => true
16.10 finite?, infinite?
Complex.finite?(x)
Complex.infinite?(x)
finite? is true when both parts are finite (neither a NaN nor infinite). infinite? returns, as Ruby’s does, 1 when either part is infinite and nil otherwise (a NaN included), so its type is Integer | nil. It is usually used as a condition; to use the value in arithmetic, check it for nil first (--strict’s nil item reports the unchecked use).
p(Complex.finite?(Complex(1, 2))) # => true
p(Complex.finite?(Complex(Float.INFINITY, 2))) # => false
p(Complex.finite?(Complex(Float.NAN, 2))) # => false
p(Complex.infinite?(Complex(1, 2))) # => nil
p(Complex.infinite?(Complex(1, -Float.INFINITY))) # => 1
p(Complex.infinite?(Complex(Float.NAN, 2))) # => nil
```ruby error x = Complex.infinite?(Complex(1, 2)) p(x + 1) # !> the operands may be nil
## fdiv
`Complex.fdiv(x, Integer|Float|Rational)`
Divides by the real number `y` after turning the parts into Floats: a Complex with Float parts. `y` cannot be a Complex (a `type` problem statically; to divide by a Complex use `/` or `quo`). Dividing by zero gives `Infinity` parts, without an exception.
```ruby
p(Complex.fdiv(Complex(3, 4), 2)) # => (1.5+2.0i)
p(Complex.fdiv(Complex(1, 2), 2r)) # => (0.5+1.0i)
p(Complex.fdiv(Complex(1, 2), 0)) # => (Infinity+Infinity*i)
```ruby error p(Complex.fdiv(Complex(1, 2), Complex(1, 1))) # !> Complex.fdiv: argument 2 must be Integer|Float|Rational, but is Complex
## quo
`Complex.quo(x, Integer|Float|Rational|Complex)`
The division `x / y` (a Complex), the same as `/`: Integer parts become Rationals. Dividing by an exact zero is a `ZeroDivisionError`.
```ruby
p(Complex.quo(Complex(3, 4), 2)) # => ((3/2)+2i)
p(Complex.quo(Complex(3, 4), 2.0)) # => (1.5+2.0i)
p(Complex.quo(Complex(3, 4), Complex(1, 1))) # => ((7/2)+(1/2)*i)
```ruby error p(Complex.quo(Complex(1, 2), 0)) # !> ZeroDivisionError: Complex.quo: divided by 0
## numerator, denominator
`Complex.numerator(x)`
`Complex.denominator(x)`
`denominator` is the least common multiple of the parts' denominators (an Integer); `numerator` is `x * denominator`, a Complex with integral parts. An Integer part has denominator 1.
```ruby
c = Complex(1r/2, 1r/4)
p(Complex.denominator(c)) # => 4
p(Complex.numerator(c)) # => (2+1i)
p(Complex.denominator(Complex(3, 4))) # => 1
p(Complex.numerator(Complex(3, 4))) # => (3+4i)
16.11 to_i, to_f, to_r, rationalize
Complex.to_i(x)
Complex.to_f(x)
Complex.to_r(x)
Complex.rationalize(x, [Rational])
Convert the real part to an Integer, a Float or a Rational (rationalize is the real part’s rationalize, with a Rational tolerance eps), but only when the imaginary part is an exact 0 (the Integer 0 or 0r). When it is not 0, or is 0.0 (the Float zero), a RangeError is raised (Ruby’s message: can't convert 1+2i into Float), which can be rescued.
p(Complex.to_i(Complex(1.9, 0))) # => 1
p(Complex.to_f(Complex(3, 0))) # => 3.0
p(Complex.to_r(Complex(1.5, 0))) # => (3/2)
p(Complex.rationalize(Complex(0.333, 0))) # => (333/1000)
p(Complex.rationalize(Complex(0.333, 0), 1r/100)) # => (1/3)
begin
Complex.to_f(Complex(1, 2))
rescue RangeError => e
p(Exception.message(e)) # => "can't convert 1+2i into Float"
end
```ruby error p(Complex.to_f(Complex(1, 0.0))) # !> RangeError: Complex.to_f: can’t convert 1+0.0i into Float
## to_c
`Complex.to_c(x)`
Returns x itself (a real number becomes a Complex through Kernel's `Complex(x)`).
```ruby
p(Complex.to_c(Complex(3, 4))) # => (3+4i)
16.12 to_s
Complex.to_s(x)
The String "re+imi" (as Ruby’s): p’s (3+4i) without the parentheses, and a Rational part is written without its parentheses too, 1/2+3i.
p(Complex.to_s(Complex(3, 4))) # => "3+4i"
p(Complex.to_s(Complex(-1, -1))) # => "-1-1i"
p(Complex.to_s(Complex(0.5, 2))) # => "0.5+2i"
p(Complex.to_s(Complex(1r/2, 3))) # => "1/2+3i"
17. Arithmetic
Arithmetic is the module of the arithmetic operators +, -, *, /, %, ** and the unary -x, +x. a + b is shorthand for Arithmetic.+(a, b), which dispatches to the + of the left operand’s type (Integer, Float, Rational, Complex, String, Time, Set, or a class that does include Arithmetic), see Operators and indexing and Program structure.
The eight operations in this chapter are not operators: they are rounding and conversion for any real number (Integer, Float, Rational). A Sake operation normally names its type, Float.round(f); these are for values whose type is not settled, such as Integer | Float (the result of Array.sum, a number read from JSON), and they dispatch on the value’s type as Ruby’s x.round does. The result type follows the argument’s: round, floor, ceil, truncate without a digit count and to_i give an Integer; a rounding with a digit count and abs give the argument’s type (a union when the argument’s type is one); to_f gives a Float; zero? gives true/false. The checker applies this rule to each type of the argument.
A Complex or a String argument is a type problem statically (argument 1 must be Integer|Float|Rational, but is Complex). Turning a NaN or an Infinity into an Integer raises FloatDomainError, which rescue FloatDomainError => e catches.
17.1 round, floor, ceil, truncate
Arithmetic.round(Integer|Float|Rational, [Integer], [half: Symbol])
Arithmetic.floor(Integer|Float|Rational, [Integer])
Arithmetic.ceil(Integer|Float|Rational, [Integer])
Arithmetic.truncate(Integer|Float|Rational, [Integer])
Ruby’s x.round(n), x.floor(n), x.ceil(n) and x.truncate(n), with one difference in the result type. round goes to the nearest value (a half away from zero by default), floor downward, ceil upward, truncate toward zero.
- Without the digit count
nthe result is an Integer (an Integer argument stays one). - With a count the result has the argument’s type: a Float for a Float, a Rational for a Rational, an Integer for an Integer (a positive count leaves the value as it is). A negative count rounds to a power of ten. Unlike Ruby, this holds for a count of 0 or less too:
Arithmetic.round(1234.5, -2)is1200.0where Ruby’s1234.5.round(-2)is the Integer 1200, andArithmetic.round(5r/2, -1)is(0/1). So the result’s type depends only on the argument’s type and on whether a count is given. roundtakes the keywordhalf:, asFloat.rounddoes, for any argument type::up(the default, away from zero),:even(to the even neighbour),:down(toward zero); another Symbol is anArgumentError(invalid rounding mode: foo).floor,ceilandtruncatedo not take it (a static error).- A NaN or an Infinity without a count is a
FloatDomainError(rescuable); with a count it is returned unchanged.
p(Arithmetic.round(1.5)) # => 2
p(Arithmetic.round(7r/2)) # => 4
p(Arithmetic.round(3)) # => 3
p(Arithmetic.round(1.2345, 2)) # => 1.23
p(Arithmetic.round(7r/3, 2)) # => (233/100)
p(Arithmetic.round(1234, -2)) # => 1200
p(Arithmetic.round(1234.5, -2)) # => 1200.0
p(Arithmetic.round(5r/2, -1)) # => (0/1)
p(Arithmetic.round(2.5, half: :even)) # => 2
p(Arithmetic.round(5r/2, half: :even)) # => 2
p(Arithmetic.round(25, -1, half: :even)) # => 20
p(Arithmetic.round(1.25, 1, half: :even)) # => 1.2
p(Arithmetic.floor(-1.5)) # => -2
p(Arithmetic.floor(1.567, 1)) # => 1.5
p(Arithmetic.floor(1234.5, -2)) # => 1200.0
p(Arithmetic.ceil(7r/2)) # => 4
p(Arithmetic.ceil(1234, -2)) # => 1300
p(Arithmetic.truncate(-7r/2)) # => -3
p(Arithmetic.truncate(1.999, 2)) # => 1.99
p(Arithmetic.round(Float.NAN, 2)) # => NaN
begin
Arithmetic.round(Float.NAN)
rescue FloatDomainError => e
p(Exception.message(e)) # => "NaN"
end
One operation serves a value whose type is not settled:
def tenth(x)
Arithmetic.round(x, 1)
end
p(tenth(1.25)) # => 1.3
p(tenth(3)) # => 3
p(tenth(1r/3)) # => (3/10)
```ruby error p(Arithmetic.round(“1.5”)) # !> Arithmetic.round: argument 1 must be Integer|Float|Rational, but is String
```ruby error
p(Arithmetic.round(2.5, half: :foo)) # !> ArgumentError: Arithmetic.round: invalid rounding mode: foo
```ruby error p(Arithmetic.round(Float.INFINITY)) # !> FloatDomainError: Arithmetic.round: Infinity
## abs
`Arithmetic.abs(Integer|Float|Rational)`
The absolute value, of the argument's type (Ruby's `x.abs`).
```ruby
p(Arithmetic.abs(-3)) # => 3
p(Arithmetic.abs(-1.5)) # => 1.5
p(Arithmetic.abs(-1r/3)) # => (1/3)
```ruby error p(Arithmetic.abs(Complex(3, 4))) # !> Arithmetic.abs: argument 1 must be Integer|Float|Rational, but is Complex
## to_i, to_f
`Arithmetic.to_i(Integer|Float|Rational)`
`Arithmetic.to_f(Integer|Float|Rational)`
`to_i` is the Integer truncated toward zero; `to_f` the nearest Float. The typical use is `to_f` to settle an `Integer | Float` value on Float (when `Float.floor(x)` is given an `Integer | Float`, the checker suggests `Arithmetic.floor(x)`). `to_i` of a NaN or an Infinity is a `FloatDomainError`, which can be rescued.
```ruby
p(Arithmetic.to_i(1.9)) # => 1
p(Arithmetic.to_i(7r/2)) # => 3
p(Arithmetic.to_i(7)) # => 7
p(Arithmetic.to_f(3)) # => 3.0
p(Arithmetic.to_f(1r/4)) # => 0.25
p(Arithmetic.to_f(1.5)) # => 1.5
```ruby error p(Arithmetic.to_i(Float.NAN)) # !> FloatDomainError: Arithmetic.to_i: NaN
## zero?
`Arithmetic.zero?(Integer|Float|Rational)`
True when `x == 0` (`0.0` and `-0.0` included).
```ruby
p(Arithmetic.zero?(0)) # => true
p(Arithmetic.zero?(0.0)) # => true
p(Arithmetic.zero?(0r)) # => true
p(Arithmetic.zero?(1)) # => false
18. Math
Math is the collection of elementary functions of Ruby’s Math module. It is a namespace, not a type: there are no Math values. Every function takes a real number (Integer, Float, Rational) and the result is always a Float (Math.sqrt(4) is 2.0; a Complex is not accepted). Only the two-argument functions atan2 and hypot, and the exponent of ldexp, do not take a Rational: they take an Integer or a Float.
Ruby’s Math::PI and Math::E are the operations without arguments Math.PI and Math.E in Sake (Sake has no value constants); Math::PI is also read in Ruby’s form, as the same operation. Math.log(x, base) takes a base as Ruby’s does (an Integer or a Float).
An argument outside the domain (sqrt(-1), log(-1), acos(2), …) raises Math::DomainError, as in Ruby, which rescue Math::DomainError => e catches (Exceptions and errors). A NaN argument gives a NaN, and infinities follow IEEE (log(0) is -Infinity, exp(1000) is Infinity; no exception).
18.1 PI, E
Math.PI()
Math.E()
The circle constant π and the base of natural logarithms e (Floats). Written Math.PI or Math.PI().
p(Math.PI) # => 3.141592653589793
p(Math.E) # => 2.718281828459045
p(Math.cos(Math.PI)) # => -1.0
p(Math.exp(1) == Math.E) # => true
18.2 sqrt, cbrt
Math.sqrt(Integer|Float|Rational)
Math.cbrt(Integer|Float|Rational)
The square root and the cube root (Floats). A negative argument to sqrt is a Math::DomainError. cbrt accepts negative numbers (cbrt(-8) is -2.0). For the exact integer square root use Integer.sqrt(n).
p(Math.sqrt(4)) # => 2.0
p(Math.sqrt(2.0)) # => 1.4142135623730951
p(Math.sqrt(9r/4)) # => 1.5
p(Math.cbrt(-8)) # => -2.0
p(Math.sqrt(Float.INFINITY)) # => Infinity
begin
Math.sqrt(-1)
rescue Math::DomainError => e
p(Exception.message(e)) # => "Numerical argument is out of domain - sqrt"
end
```ruby error p(Math.sqrt(-1)) # !> Math::DomainError: Math.sqrt: Numerical argument is out of domain - sqrt
## sin, cos, tan
`Math.sin(Integer|Float|Rational)`
`Math.cos(Integer|Float|Rational)`
`Math.tan(Integer|Float|Rational)`
The trigonometric functions; the argument is in radians. The result is a Float, so a value such as `sin(π)` is not an exact 0 but carries rounding error.
```ruby
p(Math.sin(0)) # => 0.0
p(Math.cos(0)) # => 1.0
p(Math.sin(Math.PI / 2)) # => 1.0
p(Math.cos(Math.PI / 3)) # => 0.5000000000000001
p(Math.tan(Math.PI / 4)) # => 0.9999999999999999
18.3 asin, acos, atan
Math.asin(Integer|Float|Rational)
Math.acos(Integer|Float|Rational)
Math.atan(Integer|Float|Rational)
The inverse trigonometric functions (Floats in radians). The argument of asin and acos must lie between -1 and 1; outside it is a Math::DomainError. atan takes any real number and returns a value from -π/2 to π/2.
p(Math.asin(1)) # => 1.5707963267948966
p(Math.acos(1)) # => 0.0
p(Math.asin(0.5)) # => 0.5235987755982989
p(Math.atan(1) * 4 == Math.PI) # => true
p(Math.atan(Float.INFINITY)) # => 1.5707963267948966
```ruby error p(Math.acos(2)) # !> Math::DomainError: Math.acos: Numerical argument is out of domain - acos
## atan2
`Math.atan2(Integer|Float, Integer|Float)`
`atan2(y, x)`: the angle of the point `(x, y)` in radians from `-π` to `π` (a Float). Unlike `atan(y / x)` it tells the quadrants apart and works when `x` is 0. The arguments are Integers or Floats; a Rational is a `type` problem statically.
```ruby
p(Math.atan2(1, 1)) # => 0.7853981633974483
p(Math.atan2(1.0, -1.0)) # => 2.356194490192345
p(Math.atan2(1, 0)) # => 1.5707963267948966
p(Math.atan2(0, -1)) # => 3.141592653589793
p(Math.atan2(-1, -1)) # => -2.356194490192345
```ruby error p(Math.atan2(1r, 1)) # !> Math.atan2: argument 1 must be Integer|Float, but is Rational
## hypot
`Math.hypot(Integer|Float, Integer|Float)`
`sqrt(x² + y²)` (a Float), computed without overflowing in the intermediate squares. The arguments are Integers or Floats; a Rational is a `type` problem statically.
```ruby
p(Math.hypot(3, 4)) # => 5.0
p(Math.hypot(5, 12)) # => 13.0
p(Math.hypot(1.0, 1.0)) # => 1.4142135623730951
```ruby error p(Math.hypot(1r, 1)) # !> Math.hypot: argument 1 must be Integer|Float, but is Rational
## sinh, cosh, tanh
`Math.sinh(Integer|Float|Rational)`
`Math.cosh(Integer|Float|Rational)`
`Math.tanh(Integer|Float|Rational)`
The hyperbolic functions (Floats).
```ruby
p(Math.sinh(0)) # => 0.0
p(Math.cosh(0)) # => 1.0
p(Math.sinh(1)) # => 1.1752011936438014
p(Math.cosh(1)) # => 1.5430806348152437
p(Math.tanh(Float.INFINITY)) # => 1.0
18.4 asinh, acosh, atanh
Math.asinh(Integer|Float|Rational)
Math.acosh(Integer|Float|Rational)
Math.atanh(Integer|Float|Rational)
The inverse hyperbolic functions (Floats). The argument of acosh must be at least 1 and that of atanh between -1 and 1; outside it is a Math::DomainError. atanh(1) is Infinity and atanh(-1) is -Infinity.
p(Math.asinh(1)) # => 0.881373587019543
p(Math.acosh(2)) # => 1.3169578969248168
p(Math.atanh(0.5)) # => 0.5493061443340549
p(Math.atanh(1)) # => Infinity
```ruby error p(Math.acosh(0)) # !> Math::DomainError: Math.acosh: Numerical argument is out of domain - acosh
## exp
`Math.exp(Integer|Float|Rational)`
`e` to the power x (a Float). A large argument gives `Infinity`, without an exception.
```ruby
p(Math.exp(0)) # => 1.0
p(Math.exp(2)) # => 7.38905609893065
p(Math.exp(0.5r)) # => 1.6487212707001282
p(Math.exp(1000)) # => Infinity
p(Math.exp(-Float.INFINITY)) # => 0.0
18.5 log, log2, log10
Math.log(Integer|Float|Rational, [Integer|Float])
Math.log2(Integer|Float|Rational)
Math.log10(Integer|Float|Rational)
The natural logarithm and the logarithms to base 2 and base 10 (Floats). log(0) is -Infinity; a negative argument is a Math::DomainError. Math.log(x, base) is the logarithm to the given base, as Ruby’s: Math.log(8, 2) is 3.0. The base is an Integer or a Float (a Rational is a type problem statically); a negative base is a Math::DomainError, and as in Ruby a base of 0 gives -0.0 and a base of 1 NaN. A large Integer, even one too large for a Float, has a logarithm (as in Ruby).
p(Math.log(Math.E)) # => 1.0
p(Math.log(1)) # => 0.0
p(Math.log(0)) # => -Infinity
p(Math.log2(8)) # => 3.0
p(Math.log10(0.001)) # => -3.0
p(Math.log(8r)) # => 2.0794415416798357
p(Math.log(8, 2)) # => 3.0
p(Math.log(100, 10)) # => 2.0
p(Math.log(8r, 2.0)) # => 3.0
p(Math.log(10 ** 400)) # => 921.0340371976183
p(Math.log(10 ** 400, 10)) # => 400.0
```ruby error p(Math.log(8, 2r)) # !> Math.log: argument 2 must be Integer|Float, but is Rational
```ruby error
p(Math.log(8, -2)) # !> Math::DomainError: Math.log: Numerical argument is out of domain - log
18.6 erf, erfc
Math.erf(Integer|Float|Rational)
Math.erfc(Integer|Float|Rational)
The error function and the complementary error function 1 - erf(x) (Floats).
p(Math.erf(0)) # => 0.0
p(Math.erf(1)) # => 0.8427007929497149
p(Math.erfc(1)) # => 0.15729920705028513
p(Math.erf(Float.INFINITY)) # => 1.0
18.7 gamma, lgamma
Math.gamma(Integer|Float|Rational)
Math.lgamma(Integer|Float|Rational)
gamma is the gamma function Γ(x) (a Float; (n-1)! for a positive integer n). gamma(0) is Infinity, a negative integer and -Infinity are a Math::DomainError, and 172 and above give Infinity. lgamma returns the Tuple [log|Γ(x)|, sign]: the natural logarithm of the absolute value (a Float) and the sign of Γ(x) (the Integer 1 or -1). It works for large x where gamma overflows.
p(Math.gamma(5)) # => 24.0
p(Math.gamma(0.5)) # => 1.772453850905516
p(Math.gamma(0)) # => Infinity
p(Math.gamma(172)) # => Infinity
p(Math.lgamma(6)) # => [4.787491742782046, 1]
p(Math.lgamma(-0.5)) # => [1.2655121234846454, -1]
v, sign = Math.lgamma(-0.5)
p(sign) # => -1
```ruby error p(Math.gamma(-1)) # !> Math::DomainError: Math.gamma: Numerical argument is out of domain - gamma
## frexp, ldexp
`Math.frexp(Integer|Float|Rational)`
`Math.ldexp(Integer|Float|Rational, Integer)`
`frexp(x)` splits x into the Tuple `[fraction, exponent]` (`[Float, Integer]`), where the absolute value of `fraction` is at least 0.5 and below 1 and `x == fraction * 2 ** exponent`; 0 gives `[0.0, 0]`. `ldexp(fraction, exponent)` is the inverse: `fraction * 2 ** exponent` as a Float. The exponent must be an Integer; a Float is a `type` problem statically.
```ruby
p(Math.frexp(8.0)) # => [0.5, 4]
p(Math.frexp(0.75)) # => [0.75, 0]
p(Math.frexp(10)) # => [0.625, 4]
p(Math.frexp(0)) # => [0.0, 0]
f, e = Math.frexp(8.0)
p(Math.ldexp(f, e)) # => 8.0
p(Math.ldexp(1.0, -2)) # => 0.25
p(Math.ldexp(3, 2)) # => 12.0
ruby error
p(Math.ldexp(0.5, 4.0)) # !> Math.ldexp: argument 2 must be Integer, but is Float
19. String
A String is a sequence of characters with an encoding, as Ruby’s String. The literals are "abc", 'abc' and "a#{x}" (interpolation shows the value with to_s, see Values and types); a literal is UTF-8, and every evaluation of a literal makes a new String. Strings are mutable: the operations whose names end in !, and concat, append_as_bytes, prepend, insert, replace, clear, setbyte, bytesplice, force_encoding, change their subject in place; every other operation leaves the subject alone and returns a new String. The Strings a program cannot change are Hash keys, Set elements, the names of Symbols (Symbol.name), the String a MatchData holds (MatchData.string) and the program’s arguments (ARGV): changing one of them in place is a TypeError at run time (cannot change this String in place: it is a Hash key, a Set element, a Symbol's name, or a program argument).
Every operation is written with its type: String.upcase(s), never s.upcase. The operators on Strings are + (String, String), * (String, Integer), % (the format operator), ==, !=, <, <=, >, >=, <=> (String, String), =~, !~ (String, Regexp), and the index s[i], s[i, n], s[range] (Operators and indexing). The entries String.+(x, y) and so on below are the function forms of those operators. There is no s[i] = v: a String cannot be indexed for writing (a type problem statically); change it with String.sub!, String.insert, String.bytesplice or String.replace.
Many operations give nil for a miss: index, rindex, byteindex, byterindex, match, getbyte, casecmp, unpack1, slice!, sub!, gsub!, and the in-place forms that give nil when nothing changed (upcase!, strip!, chomp!, tr!, …). Using such a result unchecked is a nil problem at --strict level 2. Only the index forms s[i], s[i, n], s[range], String.slice and String.byteslice give the nil of a miss (index-nil), which is reported only at level 3 (Overview).
Where Sake differs from Ruby: sub and gsub need a replacement or a block; a MatchData is read with m[i] (there are no $~, $1); scan, partition and rpartition give Tuples where Ruby gives Arrays; count, start_with?, end_with?, include? take one argument each (chomp, delete, squeeze take what their ! forms take: a separator, several sets); iteration (each_char, …) needs a block. The table in Built-in operations lists the operations in short.
19.1 String[]
String[*Any]
Makes an Array of Strings (not a String). It is a typed Array like Integer[] and Tuple[]: every element must be a String, and every write (Array.push, …) is checked. An element that is not a String is a type problem statically and a TypeError at run time. The empty Array of Strings is String[].
names = String["a", "b"]
Array.push(names, "c")
p(names) # => ["a", "b", "c"]
p(Array.size(String[])) # => 0
```ruby error Array.push(String[], 1) # !> Array.push: an element must be String, but is Integer
## length, size
`String.length(x)`
`String.size(x)`
The number of characters (an Integer), not of bytes: `"héllo"` has 5 characters and 6 bytes (`bytesize`).
```ruby
p(String.length("hello")) # => 5
p(String.size("héllo")) # => 5
p(String.size("")) # => 0
19.2 bytesize
String.bytesize(x)
The number of bytes (an Integer) in the String’s encoding.
p(String.bytesize("héllo")) # => 6
p(String.bytesize("")) # => 0
19.3 empty?
String.empty?(x)
True when the String has no characters.
p(String.empty?("")) # => true
p(String.empty?(" ")) # => false
19.4 +
String.+(x, Any)
The function form of a + b: a new String with the characters of a followed by those of b. Both operands must be Strings; "a" + 1 is a type problem statically (Ruby raises TypeError). Neither operand changes.
s = "abc"
p(s + "d") # => "abcd"
p(String.+("a", "b")) # => "ab"
p(s) # => "abc"
```ruby error p(“a” + 1) # !> the operands are (String, Integer)
## *
`String.*(x, Integer)`
The function form of `s * n`: a new String with `s` repeated `n` times (`""` for 0). A negative `n` is an `ArgumentError` (`negative argument`), from the operator as from the function form; it can be rescued.
```ruby
p("ab" * 3) # => "ababab"
p(String.*("-", 0)) # => ""
begin
p("ab" * -1)
rescue ArgumentError => e
puts(Exception.message(e)) # => negative argument
end
```ruby error p(String.(“ab”, -1)) # !> ArgumentError: String.: negative argument -1
## %
`String.%(x, Any)`
The function form of `fmt % value`, Ruby's `format` with `fmt` as the format: `"%d items" % 3`. The right operand is one value (Integer, Float, String, Symbol, nil, true or false), a Tuple of values for several directives (`"%s-%s" % [a, b]`), or a Record or a Hash with Symbol keys for the named directives `%<name>d` and `%{name}` (`"%<a>05d" % {a: 42}`, `"%<a>05d" % Hash.of(a: 42)`); an Array is a `type` problem statically. The directives are Ruby's (`%d`, `%s`, `%f`, `%x`, `%05d`, `%-4s`, `%.2f`, `%p`, ...). A value that does not fit its directive (`"%d" % "x"`) or too few values is an `ArgumentError`; a name the Record or Hash does not have is a `KeyError` (`key<b> not found`); extra values are ignored.
```ruby
p("%05d|%-4s|%.2f|%x" % [42, "ab", 3.14159, 255]) # => "00042|ab |3.14|ff"
p("%d items" % 3) # => "3 items"
p("%s" % :sym) # => "sym"
p("%p" % nil) # => "nil"
p(String.%("%03d", 7)) # => "007"
p("%<a>05d" % {a: 42}) # => "00042"
p("%{a}-%<b>s" % {a: 1, b: "x"}) # => "1-x"
p("%<a>05d" % Hash.of(a: 42)) # => "00042"
```ruby error p(“%d” % {a: 42}) # !> KeyError: Arithmetic.%: key not found
```ruby error
p("%d %d" % 1) # !> ArgumentError: Arithmetic.%: too few arguments
19.5 ==, !=
String.==(x, Any)
String.!=(x, Any)
True when both are Strings with the same characters (!= is the negation); comparison is by content, not identity. A right operand of another type (an Integer, a Symbol, nil) is simply not equal, with the function forms as with the operators: String.==("a", 1) is false and String.!=("a", 1) is true (no error).
p("a" == "a") # => true
p("a" != "b") # => true
p("1" == 1) # => false
p("a" == nil) # => false
p(String.==("a", nil)) # => false
p(String.==("a", 1)) # => false
p(String.!=("a", 1)) # => true
19.6 <, <=, >, >=
String.<(x, Any)
String.<=(x, Any)
String.>(x, Any)
String.>=(x, Any)
Compare two Strings byte by byte, as Ruby’s: "B" < "a" because uppercase letters come first, "10" < "9", and a prefix is smaller than the longer String. Both operands must be Strings; a String compared with another type is a type problem statically.
p("a" < "b") # => true
p("B" >= "a") # => false
p("10" < "9") # => true
p("ab" < "abc") # => true
p(String.<=("a", "a")) # => true
```ruby error p(“a” < 1) # !> the operands are (String, Integer)
## <=>
`String.<=>(x, Any)`
The result of that comparison as -1, 0, or 1. Both operands must be Strings, so the result is never nil (Ruby gives nil for a non-String).
```ruby
p("a" <=> "b") # => -1
p("a" <=> "a") # => 0
p(String.<=>("b", "a")) # => 1
19.7 between?
String.between?(x, String, String)
True when lo <= x <= hi in the order of <.
p(String.between?("b", "a", "c")) # => true
p(String.between?("z", "a", "c")) # => false
19.8 clamp
String.clamp(x, String, String)
x when it lies between lo and hi, otherwise the nearer bound. lo > hi is an ArgumentError.
p(String.clamp("b", "a", "c")) # => "b"
p(String.clamp("z", "a", "c")) # => "c"
```ruby error p(String.clamp(“b”, “c”, “a”)) # !> ArgumentError: String.clamp: min argument must be less than or equal to max argument
## casecmp
`String.casecmp(x, String)`
Compares the two Strings ignoring the case of ASCII letters: -1, 0, or 1 (an Integer). It is nil when the encodings of the two Strings are incompatible; using the result unchecked is a `nil` problem at `--strict` level 2.
```ruby
p(String.casecmp("a", "A")) # => 0
p(String.casecmp("a", "B")) # => -1
p(String.casecmp("a", String.encode("a", "UTF-16LE"))) # => nil
```ruby error p(String.casecmp(“a”, “b”) + 1) # !> the operands may be nil
## casecmp?
`String.casecmp?(x, String)`
True when the two Strings are equal after Unicode case folding. The result is a Boolean, never nil: with incompatible encodings (where Ruby's method returns nil and `casecmp` gives nil) it is **false**.
```ruby
p(String.casecmp?("a", "A")) # => true
p(String.casecmp?("a", "b")) # => false
p(String.casecmp?("a", String.encode("a", "UTF-16LE"))) # => false
19.9 =~
String.=~(x, Any)
The function form of s =~ re: the position (an Integer) of the first match of the Regexp re in s, or nil when there is none. The right operand must be a Regexp; a String is a type problem statically. The nil is a nil problem at --strict level 2 when used unchecked. There is no $~: use String.match to read the groups.
p("hello" =~ /l+/) # => 2
p("hello" =~ /z/) # => nil
p(String.=~("ab", /b/)) # => 1
```ruby error p((“abc” =~ /b/) + 1) # !> the operands may be nil
## !~
`String.!~(x, Any)`
The function form of `s !~ re`: true when the Regexp does not match.
```ruby
p("hello" !~ /z/) # => true
p(String.!~("ab", /b/)) # => false
19.10 []
String.[](x, Any, [Integer])
The function form of s[i], s[i, n] and s[range]. s[i] is the one-character String at position i (an Integer; a negative one counts from the end), or nil outside the String. s[i, n] is the substring of up to n characters starting at i: nil when i is past the end or n is negative, "" when i equals the length. s[range] is the characters in the Range, nil when its start is past the end. As in Ruby, a String or a Regexp is an index too: s["b"] is its first occurrence (a new String) or nil, s[/re/] the first match, and s[/re/, 1] that group (the same as String.slice; since 2026-10-11). Another index type such as a Float is a type problem statically, and a Range with a count is a TypeError. The result is String | nil; the nil is the nil of a miss (index-nil), reported only at --strict level 3.
s = "hello"
p(s[1]) # => "e"
p(s[-1]) # => "o"
p(s[10]) # => nil
p(s[1, 3]) # => "ell"
p(s[5, 1]) # => ""
p(s[6, 1]) # => nil
p(s[1..2]) # => "el"
p(s[2..]) # => "llo"
p(String.[](s, 0)) # => "h"
s = "hello world"
p(s["wor"]) # => "wor"
p(s["xyz"]) # => nil
p(s[/o w/]) # => "o w"
p(s[/(\w+) (\w+)/, 2]) # => "world"
```ruby error p(“abc”[1.5]) # !> the index must be Integer, but is Float
## slice
`String.slice(x, Integer|Range|String|Regexp, [Integer])`
`s[i]`, `s[i, n]` or `s[range]` written as an operation: the same results and the same nil (`index-nil`, level 3). It also takes what `slice!` takes, as Ruby's `slice`: a String gives its first occurrence (a new String) or nil, and a Regexp gives the first match, or, with a group number as the second argument, that group's String (nil when the group did not take part). A count after a String or a Range is a `TypeError`.
```ruby
s = "hello"
p(String.slice(s, 1)) # => "e"
p(String.slice(s, 1, 2)) # => "el"
p(String.slice(s, 1..2)) # => "el"
p(String.slice(s, 9)) # => nil
p(String.slice(s, "ll")) # => "ll"
p(String.slice(s, "zz")) # => nil
p(String.slice(s, /l+/)) # => "ll"
p(String.slice(s, /(l)(o)/, 2)) # => "o"
```ruby error p(String.slice(“hello”, “ll”, 1)) # !> TypeError: String.slice: no implicit conversion of String into Integer
## slice!
`String.slice!(x, Integer|Range|String|Regexp, [Integer])`
Removes the part `s[i]`, `s[i, n]` or `s[range]` from the subject **in place** and returns it. A String argument removes its first occurrence, a Regexp its first match. The result is nil when there is nothing to remove (an index past the end, a String or Regexp that does not occur), and the subject is then unchanged; using the result unchecked is a `nil` problem at `--strict` level 2.
```ruby
s = "hello"
p(String.slice!(s, 0)) # => "h"
p(String.slice!(s, /l+/)) # => "ll"
p(String.slice!(s, "zz")) # => nil
p(s) # => "eo"
```ruby error s = “hello” c = String.slice!(s, 1) p(String.upcase(c)) # !> argument 1 may be nil
## byteslice
`String.byteslice(x, Integer|Range, [Integer])`
The byte at byte offset `i` (one-byte String), the `n` bytes from `i`, or the bytes in a Range of byte offsets, in the subject's encoding; the result may be an invalid String when it cuts a multibyte character. nil when `i` (or the Range's start) is past the end or `n` is negative (`index-nil`, level 3). As Ruby's.
```ruby
p(String.byteslice("héllo", 1)) # => "\xC3"
p(String.byteslice("héllo", 1, 2)) # => "é"
p(String.byteslice("héllo", 1..2)) # => "é"
p(String.byteslice("hello", 9..10)) # => nil
p(String.byteslice("héllo", 9)) # => nil
19.11 getbyte
String.getbyte(x, Integer)
The byte (an Integer 0..255) at byte offset i, negative from the end, or nil outside the String. Using the result unchecked is a nil problem at --strict level 2.
s = "héllo"
p(String.getbyte(s, 0)) # => 104
p(String.getbyte(s, 1)) # => 195
p(String.getbyte(s, -1)) # => 111
p(String.getbyte(s, 99)) # => nil
```ruby error p(String.getbyte(“a”, 5) + 1) # !> the operands may be nil
## setbyte
`String.setbyte(x, Integer, Integer)`
Stores the byte `b` at byte offset `i` **in place** (the low 8 bits of `b`, as Ruby) and returns `b`. An offset outside the String is an `IndexError`.
```ruby
s = "abc"
p(String.setbyte(s, 0, 65)) # => 65
p(s) # => "Abc"
```ruby error p(String.setbyte(“abc”, 10, 1)) # !> IndexError: String.setbyte: index 10 out of string
## bytesplice
`String.bytesplice(x, Integer, Integer, String)`
Replaces the `n` bytes from byte offset `i` with the String `str`, **in place**, and returns the subject. An offset outside the String is an `IndexError`; the offsets must fall on character boundaries.
```ruby
s = "hello"
p(String.bytesplice(s, 0, 2, "J")) # => "Jllo"
p(s) # => "Jllo"
```ruby error p(String.bytesplice(“abc”, 10, 1, “x”)) # !> IndexError: String.bytesplice: index 10 out of string
## upcase, downcase, capitalize, swapcase
`String.upcase(x)`
`String.downcase(x)`
`String.capitalize(x)`
`String.swapcase(x)`
A new String with the letters in upper case, in lower case, with the first character in upper case and the rest in lower case, or with each letter's case swapped. Case mapping is Unicode's, as Ruby's (`"straße"` upcases to `"STRASSE"`).
```ruby
p(String.upcase("abc")) # => "ABC"
p(String.downcase("ABC")) # => "abc"
p(String.capitalize("hELLO")) # => "Hello"
p(String.swapcase("Ab")) # => "aB"
p(String.upcase("straße")) # => "STRASSE"
19.12 upcase!, downcase!, capitalize!, swapcase!
String.upcase!(x)
String.downcase!(x)
String.capitalize!(x)
String.swapcase!(x)
The same conversions in place. They return the subject when it changed, and nil when it was already in that form; using the result unchecked is a nil problem at --strict level 2.
s = "Hello"
p(String.upcase!(s)) # => "HELLO"
p(s) # => "HELLO"
p(String.upcase!(s)) # => nil
p(String.capitalize!("Abc")) # => nil
```ruby error s = “Hello” r = String.upcase!(s) p(String.size(r)) # !> argument 1 may be nil
## strip, lstrip, rstrip
`String.strip(x)`
`String.lstrip(x)`
`String.rstrip(x)`
A new String without the leading and trailing whitespace (`strip`), the leading only (`lstrip`), or the trailing only (`rstrip`). Whitespace is Ruby's: spaces, tabs, newlines, and NUL.
```ruby
p(String.strip(" a ")) # => "a"
p(String.lstrip(" a ")) # => "a "
p(String.rstrip(" a ")) # => " a"
19.13 strip!, lstrip!, rstrip!
String.strip!(x)
String.lstrip!(x)
String.rstrip!(x)
The same in place: the subject when something was removed, nil when there was nothing to remove (a nil problem at --strict level 2 when used unchecked).
s = " a "
p(String.strip!(s)) # => "a"
p(String.strip!(s)) # => nil
p(String.lstrip!("a")) # => nil
19.14 chomp
String.chomp(x, [String])
A new String without one trailing line end ("\n", "\r\n" or "\r"); a String without one is returned unchanged. With suffix, that suffix is removed instead (as Ruby’s: "" removes every trailing newline).
p(String.chomp("a\n")) # => "a"
p(String.chomp("a\r\n")) # => "a"
p(String.chomp("a")) # => "a"
p(String.chomp("abc!", "!")) # => "abc"
p(String.chomp("abc", "x")) # => "abc"
p(String.chomp("abc\n\n", "")) # => "abc"
19.15 chomp!
String.chomp!(x, [String])
Removes one trailing line end in place, or, with suffix, removes that suffix. Returns the subject when something was removed and nil otherwise (a nil problem at --strict level 2 when used unchecked).
s = "a\n"
p(String.chomp!(s)) # => "a"
p(String.chomp!(s)) # => nil
p(String.chomp!("abc", "bc")) # => "a"
p(String.chomp!("abc", "x")) # => nil
```ruby error p(String.chomp!(“abc”) + “!”) # !> the operands may be nil
## chop
`String.chop(x)`
A new String without its last character (`"\r\n"` counts as one). `""` gives `""`.
```ruby
p(String.chop("abc")) # => "ab"
p(String.chop("a\r\n")) # => "a"
p(String.chop("")) # => ""
19.16 chop!
String.chop!(x)
Removes the last character in place: the subject, or nil when it was empty (a nil problem at --strict level 2 when used unchecked).
s = "abc"
p(String.chop!(s)) # => "ab"
p(String.chop!("")) # => nil
19.17 chr
String.chr(x)
The first character as a String, "" for an empty String (Ruby’s chr).
p(String.chr("abc")) # => "a"
p(String.chr("")) # => ""
19.18 delete_prefix, delete_suffix
String.delete_prefix(x, String)
String.delete_suffix(x, String)
A new String without the leading prefix or the trailing suffix; when the String does not start (end) with it, an unchanged copy.
p(String.delete_prefix("foobar", "foo")) # => "bar"
p(String.delete_suffix("foobar", "bar")) # => "foo"
p(String.delete_prefix("foobar", "x")) # => "foobar"
19.19 delete_prefix!, delete_suffix!
String.delete_prefix!(x, String)
String.delete_suffix!(x, String)
The same in place: the subject when the prefix (suffix) was there and removed, nil otherwise (a nil problem at --strict level 2 when used unchecked).
s = "foobar"
p(String.delete_prefix!(s, "foo")) # => "bar"
p(String.delete_prefix!(s, "foo")) # => nil
p(String.delete_suffix!(s, "ar")) # => "b"
p(s) # => "b"
19.20 include?, start_with?, end_with?
String.include?(x, String)
String.start_with?(x, String)
String.end_with?(x, String)
True when t occurs in the String, when the String starts with t, or when it ends with t. Each takes exactly one String (Ruby’s start_with? also takes several arguments and Regexps).
s = "hello world"
p(String.include?(s, "wor")) # => true
p(String.start_with?(s, "he")) # => true
p(String.end_with?(s, "x")) # => false
19.21 index
String.index(x, String|Regexp, [Integer])
The character position (an Integer) of the first occurrence of the String t, or of the first match of the Regexp, at or after position pos (default 0; a negative pos counts from the end). nil when there is none; using it unchecked is a nil problem at --strict level 2. "" is found at pos.
s = "hello world"
p(String.index(s, "o")) # => 4
p(String.index(s, "o", 5)) # => 7
p(String.index(s, /w./)) # => 6
p(String.index(s, "l", -3)) # => 9
p(String.index(s, "z")) # => nil
```ruby error s = “hello” i = String.index(s, “o”) p(i + 1) # !> the operands may be nil
## rindex
`String.rindex(x, String|Regexp, [Integer])`
The position of the last occurrence of `t` (or last match of the Regexp) that starts at or before `pos` (default: the end). nil when there is none (a `nil` problem at `--strict` level 2 when used unchecked).
```ruby
s = "hello world"
p(String.rindex(s, "o")) # => 7
p(String.rindex(s, "o", 5)) # => 4
p(String.rindex(s, "z")) # => nil
```ruby error p(String.rindex(“abc”, “b”) + 1) # !> the operands may be nil
## byteindex
`String.byteindex(x, String|Regexp, [Integer])`
As `index`, but the result and `pos` are **byte** offsets. nil when there is no occurrence (a `nil` problem at `--strict` level 2 when used unchecked). `pos` must fall on a character boundary: an offset inside a multibyte character is an `IndexError` (`offset 2 does not land on character boundary`), which can be rescued.
```ruby
p(String.index("héllo", "l")) # => 2
p(String.byteindex("héllo", "l")) # => 3
p(String.byteindex("héllo", "z")) # => nil
p(String.byteindex("héllo", "l", 3)) # => 3
```ruby error p(String.byteindex(“héllo”, “l”, 2)) # !> IndexError: String.byteindex: offset 2 does not land on character boundary
## byterindex
`String.byterindex(x, String|Regexp, [Integer])`
As `rindex`, with byte offsets: the last occurrence starting at or before byte `pos`. nil when there is none (a `nil` problem at `--strict` level 2 when used unchecked).
```ruby
p(String.byterindex("héllo", "l")) # => 4
p(String.byterindex("héllo", "l", 3)) # => 3
```ruby error p(String.byterindex(“abc”, “b”) + 1) # !> the operands may be nil
## match
`String.match(x, String|Regexp, [Integer])`
The `MatchData` of the first match of the Regexp at or after position `pos` (default 0), or nil when it does not match; using the result unchecked (`m[1]` without an `if m`) is a `nil` problem at `--strict` level 2. A String pattern is compiled as a Regexp, as Ruby's (`"."` matches any character; an invalid pattern such as `"+"` is a `RegexpError`, which can be rescued). There are no `$~` and `$1`: read the groups from the MatchData with `m[0]`, `m[1]`, `m["name"]`.
```ruby
m = String.match("hello world", /(w)(o)/)
if m
p(m[0]) # => "wo"
p(m[2]) # => "o"
end
p(String.match("hello", "zzz")) # => nil
```ruby error p(String.match(“a+c”, “+”)) # !> RegexpError: String.match: target of repeat operator is not specified: /+/
```ruby error
m = String.match("abc", /b/)
p(m[0]) # !> the operands may be nil (MatchData | nil)
19.22 match?
String.match?(x, String|Regexp, [Integer])
True when the pattern matches at or after position pos (default 0), without building a MatchData. A String pattern is compiled as a Regexp; an invalid one is a RegexpError, as with match.
p(String.match?("hello", /l+/)) # => true
p(String.match?("hello", "o", 8)) # => false
p(String.match?("abc", "b.")) # => true
begin
p(String.match?("a+c", "+"))
rescue RegexpError => e
puts(Exception.message(e)) # => target of repeat operator is not specified: /+/
end
19.23 scan
String.scan(x, String|Regexp)
Every non-overlapping match, from left to right, as an Array. Without groups, an Array of the matched Strings. With groups, an Array of Tuples with one String per group (Ruby gives Arrays); a group that did not take part is nil in its position, the nil of a miss (index-nil, level 3).
p(String.scan("a1b22", /\d+/)) # => ["1", "22"]
p(String.scan("abab", "ab")) # => ["ab", "ab"]
p(String.scan("a1b22", /(\w)(\d)/)) # => [["a", "1"], ["b", "2"]]
p(String.scan("a1 b", /(\w)(\d)?/)) # => [["a", "1"], ["b", nil]]
19.24 count
String.count(x, String)
The number of characters (an Integer) that belong to the set chars, written as in Ruby’s tr: "lo" lists characters, "a-z" is a range, a leading ^ negates. Only one set is accepted (Ruby intersects several). A reversed range such as "z-a" is an ArgumentError (invalid range "z-a" in string transliteration), here as in tr, delete and squeeze.
s = "hello world"
p(String.count(s, "lo")) # => 5
p(String.count(s, "a-z")) # => 10
p(String.count(s, "^l")) # => 8
```ruby error p(String.count(“abc”, “z-a”)) # !> ArgumentError: String.count: invalid range “z-a” in string transliteration
## partition, rpartition
`String.partition(x, String|Regexp)`
`String.rpartition(x, String|Regexp)`
Splits around the first (`partition`) or last (`rpartition`) occurrence of `sep`, a String or a Regexp, into the **Tuple** `[before, separator, after]` (Ruby gives an Array). When `sep` does not occur, `partition` gives `[s, "", ""]` and `rpartition` gives `["", "", s]`.
```ruby
p(String.partition("a=b=c", "=")) # => ["a", "=", "b=c"]
p(String.rpartition("a=b=c", "=")) # => ["a=b", "=", "c"]
p(String.partition("hello", /l+/)) # => ["he", "ll", "o"]
p(String.partition("abc", "x")) # => ["abc", "", ""]
p(String.rpartition("abc", "x")) # => ["", "", "abc"]
19.25 sub, gsub
String.sub(x, String|Regexp, [String|Hash]) [{ }]
String.gsub(x, String|Regexp, [String|Hash]) [{ }]
A new String with the first match (sub) or every match (gsub) of pattern replaced. A String pattern is matched literally ("." is a period, not any character). The replacement is one of three: a String, in which \0 is the whole match, \1 the first group and \k<name> a named group; a Hash whose keys are matched Strings and whose values (shown with to_s) replace them, a match that is not a key being removed; or a block that receives the matched String (not a MatchData) and whose result, shown with to_s, is the replacement. Giving neither a replacement nor a block is an ArgumentError at run time (Ruby’s enumerator form does not exist).
s = "hello"
p(String.sub(s, "l", "L")) # => "heLlo"
p(String.gsub(s, "l", "L")) # => "heLLo"
p(String.gsub(s, /l+/, "<\\0>")) # => "he<ll>o"
p(String.gsub(s, /(l)(o)/, "\\2\\1")) # => "helol"
p(String.gsub(s, /[el]/, Hash.of("e" => "3", "l" => 1))) # => "h311o"
p(String.gsub(s, /l/) { |m| String.upcase(m) }) # => "heLLo"
p(String.sub(s, /l/) { |m| 7 }) # => "he7lo"
p(s) # => "hello"
```ruby error p(String.sub(“abc”, “b”)) # !> ArgumentError: String.sub: sub needs a replacement or a block
## sub!, gsub!
`String.sub!(x, String|Regexp, [String|Hash]) [{ }]`
`String.gsub!(x, String|Regexp, [String|Hash]) [{ }]`
The same replacement **in place**. They return the subject when something was replaced and nil when the pattern did not match (the subject unchanged); using the result unchecked is a `nil` problem at `--strict` level 2. The replacement forms are those of `sub` and `gsub`.
```ruby
s = "hello"
p(String.sub!(s, "l", "L")) # => "heLlo"
p(s) # => "heLlo"
p(String.gsub!(s, "zz", "y")) # => nil
String.gsub!(s, /l/i) { |m| "_" }
p(s) # => "he__o"
```ruby error s = “hello” p(String.sub!(s, “x”, “y”) + “!”) # !> the operands may be nil
## tr
`String.tr(x, String, String)`
A new String in which each character of the set `from` is replaced by the character at the same position in `to` (Ruby's `tr`): `"a-y"` is a range, a leading `^` in `from` negates, a `to` shorter than `from` repeats its last character, and an empty `to` deletes. A reversed range is an `ArgumentError` (`invalid range "z-a" in string transliteration`).
```ruby
p(String.tr("hello", "el", "ip")) # => "hippo"
p(String.tr("hello", "a-y", "b-z")) # => "ifmmp"
p(String.tr("hello", "^l", "*")) # => "**ll*"
p(String.tr("hello", "lo", "")) # => "he"
```ruby error p(String.tr(“abc”, “z-a”, “x”)) # !> ArgumentError: String.tr: invalid range “z-a” in string transliteration
## tr!
`String.tr!(x, String, String)`
`tr` **in place**: the subject when something changed, nil otherwise (a `nil` problem at `--strict` level 2 when used unchecked).
```ruby
s = "hello"
p(String.tr!(s, "l", "L")) # => "heLLo"
p(String.tr!(s, "z", "Z")) # => nil
19.26 tr_s
String.tr_s(x, String, String)
As tr, and then squeezes runs of the replaced characters into one (Ruby’s tr_s).
p(String.tr_s("hello", "l", "r")) # => "hero"
p(String.tr_s("aabbcc", "a-c", "x")) # => "x"
19.27 tr_s!
String.tr_s!(x, String, String)
tr_s in place: the subject when something changed, nil otherwise (a nil problem at --strict level 2 when used unchecked).
s = "aabb"
p(String.tr_s!(s, "a", "x")) # => "xbb"
p(String.tr_s!(s, "z", "x")) # => nil
19.28 delete
String.delete(x, String, *String)
A new String without the characters that belong to every set given (tr syntax: "a-k", "^l"). Several sets intersect, as Ruby’s: delete("hello", "l", "o") deletes nothing, since no character is in both sets. A reversed range is an ArgumentError.
p(String.delete("hello", "l")) # => "heo"
p(String.delete("hello", "a-k")) # => "llo"
p(String.delete("hello", "^l")) # => "ll"
p(String.delete("hello", "l", "o")) # => "hello"
p(String.delete("hello", "a-z", "^l")) # => "ll"
19.29 delete!
String.delete!(x, String, *String)
Deletes in place the characters that belong to every set given (several sets intersect, as Ruby’s). The subject when something was deleted, nil otherwise (a nil problem at --strict level 2 when used unchecked).
s = "hello"
p(String.delete!(s, "l")) # => "heo"
p(String.delete!(s, "z")) # => nil
p(String.delete!("hello", "l", "^l")) # => nil
```ruby error p(String.delete!(“a”, “z-a”)) # !> ArgumentError: String.delete!: invalid range “z-a”
## squeeze
`String.squeeze(x, *String)`
A new String in which runs of the same character are reduced to one; with sets, only runs of characters that belong to every set (several sets intersect, as Ruby's).
```ruby
p(String.squeeze("aaabbb c")) # => "ab c"
p(String.squeeze("aaabbb c", "a")) # => "abbb c"
p(String.squeeze("aaabbb c", "a-b")) # => "ab c"
p(String.squeeze("aaabbb c", "a-b", "b")) # => "aaab c"
19.30 squeeze!
String.squeeze!(x, *String)
squeeze in place, with any number of sets (their intersection, as Ruby’s). The subject when something changed, nil otherwise (a nil problem at --strict level 2 when used unchecked).
s = "aaabbb"
p(String.squeeze!(s)) # => "ab"
p(String.squeeze!(s)) # => nil
p(String.squeeze!("aabb", "a")) # => "abb"
19.31 split
String.split(x, [String|Regexp], [Integer])
Splits into an Array of Strings. Without sep or with " ", it splits on runs of whitespace and ignores leading whitespace (awk style); with another String, on each occurrence of it; with "", into characters; with a Regexp, on each match, and the Regexp’s groups are included in the result (Ruby’s rule). limit (default 0) bounds the number of pieces: a positive limit gives at most that many, the last holding the rest; 0 drops trailing empty Strings; a negative limit keeps them. There is no block form.
p(String.split("a b c")) # => ["a", "b", "c"]
p(String.split("a,b,,c", ",")) # => ["a", "b", "", "c"]
p(String.split("a,b,,c", ",", 2)) # => ["a", "b,,c"]
p(String.split("a,b,,", ",")) # => ["a", "b"]
p(String.split("a,b,,", ",", -1)) # => ["a", "b", "", ""]
p(String.split("a1b2c", /\d/)) # => ["a", "b", "c"]
p(String.split("a1b", /(\d)/)) # => ["a", "1", "b"]
p(String.split("abc", "")) # => ["a", "b", "c"]
p(String.split("", ",")) # => []
19.32 chars
String.chars(x)
An Array of the characters, each a one-character String.
p(String.chars("aé")) # => ["a", "é"]
p(String.chars("")) # => []
19.33 lines
String.lines(x)
An Array of the lines, each keeping its "\n" (the last one may lack it).
p(String.lines("a\nb\n")) # => ["a\n", "b\n"]
p(String.lines("a\nb")) # => ["a\n", "b"]
p(String.lines("")) # => []
19.34 bytes
String.bytes(x)
An Array of the bytes as Integers (0..255).
p(String.bytes("aé")) # => [97, 195, 169]
19.35 codepoints
String.codepoints(x)
An Array of the Unicode code points as Integers.
p(String.codepoints("aé")) # => [97, 233]
19.36 grapheme_clusters
String.grapheme_clusters(x)
An Array of the grapheme clusters (what a reader sees as one character: a base character with its combining marks), each a String.
s = "éa"
p(String.length(s)) # => 3
p(Array.size(String.grapheme_clusters(s))) # => 2
p(Array.map(String.grapheme_clusters(s)) { |g| String.length(g) }) # => [2, 1]
19.37 each_char
String.each_char(x) { }
Calls the block with each character (a one-character String) in order and returns the subject. The block is required: a call without one is a static error (Ruby’s enumerator form does not exist).
r = String.each_char("ab") { |c| p(c) }
# => "a"
# => "b"
p(r) # => "ab"
```ruby error String.each_char(“ab”) # !> String.each_char requires a block
## each_line
`String.each_line(x) { }`
Calls the block with each line, keeping its `"\n"`, and returns the subject. The block is required; there is no separator argument (Ruby's `each_line(sep)`).
```ruby
String.each_line("a\nb") { |l| p(l) }
# => "a\n"
# => "b"
19.38 each_byte
String.each_byte(x) { }
Calls the block with each byte (an Integer) and returns the subject. The block is required.
String.each_byte("aé") { |b| p(b) }
# => 97
# => 195
# => 169
19.39 each_codepoint
String.each_codepoint(x) { }
Calls the block with each code point (an Integer) and returns the subject. The block is required.
String.each_codepoint("aé") { |c| p(c) }
# => 97
# => 233
19.40 each_grapheme_cluster
String.each_grapheme_cluster(x) { }
Calls the block with each grapheme cluster (a String) and returns the subject. The block is required.
String.each_grapheme_cluster("éa") { |g| p(String.length(g)) }
# => 2
# => 1
19.41 upto
String.upto(x, String) { }
Calls the block with x, then String.succ of it, and so on up to and including last, as Ruby’s upto ("a".upto("e")); the walk stops when a value gets longer than last, so nothing is yielded when x is already past it. Strings of digits count as numbers ("9" up to "11"). Returns the subject.
String.upto("a", "c") { |s| p(s) }
# => "a"
# => "b"
# => "c"
String.upto("az", "bb") { |s| p(s) }
# => "az"
# => "ba"
# => "bb"
String.upto("9", "11") { |s| p(s) }
# => "9"
# => "10"
# => "11"
String.upto("b", "a") { |s| p(s) }
19.42 succ, next
String.succ(x)
String.next(x)
A new String that is the successor of x in Ruby’s sense: the rightmost letter or digit is incremented, with a carry to the left ("az" to "ba", "a9" to "b0", "Zz" to "AAa"). "" gives "".
p(String.succ("az")) # => "ba"
p(String.next("Zz")) # => "AAa"
p(String.succ("a9")) # => "b0"
p(String.succ("")) # => ""
19.43 succ!, next!
String.succ!(x)
String.next!(x)
Replace the subject by its successor in place and return the subject (never nil).
s = "az"
p(String.succ!(s)) # => "ba"
p(String.next!(s)) # => "bb"
p(s) # => "bb"
19.44 reverse
String.reverse(x)
A new String with the characters in reverse order.
p(String.reverse("abc")) # => "cba"
19.45 reverse!
String.reverse!(x)
Reverses the subject in place and returns it (never nil).
s = "abc"
p(String.reverse!(s)) # => "cba"
p(s) # => "cba"
19.46 ljust, rjust, center
String.ljust(x, Integer, [String])
String.rjust(x, Integer, [String])
String.center(x, Integer, [String])
A new String of width characters: the subject padded on the right (ljust), on the left (rjust), or on both sides (center, the extra padding going right) with repetitions of pad (default " "), cut to fit. A width not greater than the length gives an unchanged copy. An empty pad is an ArgumentError (zero width padding).
p(String.ljust("ab", 5)) # => "ab "
p(String.rjust("ab", 5, "0")) # => "000ab"
p(String.center("ab", 6, "*")) # => "**ab**"
p(String.ljust("ab", 5, "xy")) # => "abxyx"
p(String.center("ab", 1)) # => "ab"
```ruby error p(String.ljust(“a”, 3, “”)) # !> ArgumentError: String.ljust: zero width padding
## to_s
`String.to_s(x)`
The String itself. It exists so that `T.to_s(x)` can be written for every type.
```ruby
p(String.to_s("x")) # => "x"
19.47 to_i
String.to_i(x, [Integer])
The Integer written at the start of the String in base base (2..36, default 10), as Ruby’s to_i: leading whitespace, a sign and underscores between digits are allowed, reading stops at the first other character, and a String with no digits gives 0 (never an error; Integer(s) in Built-in operations raises instead). Base 0 reads a prefix: 0x (16), 0b (2), 0o or a leading 0 (8). With base 16, a 0x prefix is accepted. A base outside 2..36 (other than 0) is an ArgumentError.
p(String.to_i("42")) # => 42
p(String.to_i(" -12abc")) # => -12
p(String.to_i("abc")) # => 0
p(String.to_i("ff", 16)) # => 255
p(String.to_i("0x1f", 16)) # => 31
p(String.to_i("0b101", 0)) # => 5
p(String.to_i("012", 0)) # => 10
p(String.to_i("z", 36)) # => 35
```ruby error p(String.to_i(“1”, 37)) # !> ArgumentError: String.to_i: invalid radix 37
## to_f
`String.to_f(x)`
The Float written at the start of the String (`"3.5e2"`, underscores allowed), 0.0 when there is none (never an error; `Float(s)` raises). A value too large for a Float is `Infinity`.
```ruby
p(String.to_f("3.5e2")) # => 350.0
p(String.to_f("1_000.5")) # => 1000.5
p(String.to_f("abc")) # => 0.0
19.48 to_r
String.to_r(x)
The Rational written at the start of the String: "3/4", "0.75", "1e2". A String with no number gives (0/1).
p(String.to_r("3/4")) # => (3/4)
p(String.to_r("0.75")) # => (3/4)
p(String.to_r("abc")) # => (0/1)
19.49 to_c
String.to_c(x)
The Complex written at the start of the String ("1+2i"); (0+0i) when there is none.
p(String.to_c("1+2i")) # => (1+2i)
p(String.to_c("x")) # => (0+0i)
19.50 to_sym, intern
String.to_sym(x)
String.intern(x)
The Symbol with this name. Any String can become a Symbol; one that is not an identifier is shown quoted.
p(String.to_sym("abc")) # => :abc
p(String.intern("a b")) # => :"a b"
19.51 hex
String.hex(x)
The String read as a hexadecimal Integer, as Ruby’s hex: an optional sign and 0x prefix, reading stops at the first non-hex character, and 0 when there are no digits.
p(String.hex("ff")) # => 255
p(String.hex("0x1F")) # => 31
p(String.hex("-a")) # => -10
p(String.hex("zz")) # => 0
19.52 oct
String.oct(x)
The String read as an octal Integer, as Ruby’s oct; a prefix 0x, 0b or 0o switches the base, as Ruby’s. 0 when there are no digits.
p(String.oct("17")) # => 15
p(String.oct("0x1f")) # => 31
p(String.oct("0b11")) # => 3
p(String.oct("9")) # => 0
19.53 ord
String.ord(x)
The code point (an Integer) of the first character. An empty String is an ArgumentError.
p(String.ord("a")) # => 97
p(String.ord("é")) # => 233
```ruby error p(String.ord(“”)) # !> ArgumentError: String.ord: empty string
## sum
`String.sum(x, [Integer])`
The sum of the bytes modulo 2 to the power `bits` (default 16), Ruby's simple checksum.
```ruby
p(String.sum("abc")) # => 294
p(String.sum("abc", 8)) # => 38
19.54 crypt
String.crypt(x, String)
The one-way hash of the String with salt by the system’s crypt(3), as Ruby’s. A salt shorter than two bytes is an ArgumentError. The result depends on the platform’s libc.
p(String.size(String.crypt("abc", "ab")) > 2) # => true
```ruby error p(String.crypt(“abc”, “a”)) # !> ArgumentError: String.crypt: salt too short (need >=2 bytes)
## concat
`String.concat(x, *String|Integer)`
Appends each argument to the subject **in place** and returns the subject. An Integer is appended as the character with that code point.
```ruby
s = "a"
p(String.concat(s, "b", 99, "d")) # => "abcd"
p(s) # => "abcd"
19.55 append_as_bytes
String.append_as_bytes(x, *String|Integer)
Appends the bytes of each String argument, and each Integer as one byte (its low 8 bits), in place without converting encodings or checking validity, as Ruby’s; the result may be an invalid String. Returns the subject.
s = "a"
p(String.append_as_bytes(s, "é", 255)) # => "aé\xFF"
p(String.bytesize(s)) # => 4
p(String.valid_encoding?(s)) # => false
19.56 prepend
String.prepend(x, *String)
Inserts the arguments, in order, at the front of the subject in place and returns the subject.
s = "c"
p(String.prepend(s, "a", "b")) # => "abc"
p(s) # => "abc"
19.57 insert
String.insert(x, Integer, String)
Inserts str in place before the character at position i; a negative i inserts after the character counted from the end, so -1 appends. Returns the subject. A position outside the String is an IndexError.
s = "ac"
p(String.insert(s, 1, "b")) # => "abc"
p(String.insert(s, -1, "d")) # => "abcd"
p(String.insert(s, 0, "_")) # => "_abcd"
```ruby error p(String.insert(“abc”, 10, “x”)) # !> IndexError: String.insert: index 10 out of string
## replace
`String.replace(x, String)`
Replaces the contents of the subject with those of `str`, **in place**, and returns the subject. Every variable holding the subject sees the new contents.
```ruby
s = "old"
t = s
String.replace(s, "new")
p(t) # => "new"
19.58 clear
String.clear(x)
Removes every character in place and returns the subject (now "").
s = "hello"
p(String.clear(s)) # => ""
p(String.empty?(s)) # => true
19.59 encoding
String.encoding(x)
The name of the String’s encoding as a String ("UTF-8", "ASCII-8BIT", …). Ruby gives an Encoding object; Sake has no such type.
p(String.encoding("é")) # => "UTF-8"
p(String.encoding(String.b("é"))) # => "ASCII-8BIT"
19.60 force_encoding
String.force_encoding(x, String)
Relabels the subject’s bytes as encoding enc in place and returns the subject, as Ruby’s force_encoding. The bytes are not converted; valid_encoding? tells whether they are valid in the new encoding. An unknown encoding name is an ArgumentError; a String that cannot be changed (a Hash key, a Symbol’s name, …) is a TypeError.
s = String.b("\xC3\xA9")
p(String.encoding(s)) # => "ASCII-8BIT"
t = String.force_encoding(s, "UTF-8")
p(String.encoding(s)) # => "UTF-8"
p(Kernel.equal?(s, t)) # => true
p(s) # => "é"
p(String.valid_encoding?(String.force_encoding("\xff", "UTF-8"))) # => false
```ruby error p(String.force_encoding(“a”, “bogus”)) # !> ArgumentError: String.force_encoding: unknown encoding name - bogus
## encode
`String.encode(x, String, [String])`
A new String with the characters converted to the encoding `to`; `from` (default: the subject's encoding) says how to read the bytes. A character that `to` cannot represent, an invalid byte in the subject, or an unknown encoding name is an `EncodingError`.
```ruby
u = String.encode("é", "UTF-16LE")
p(String.encoding(u)) # => "UTF-16LE"
p(String.bytesize(u)) # => 2
p(String.encode("\xE9", "UTF-8", "ISO-8859-1")) # => "é"
begin
String.encode("é", "US-ASCII")
rescue EncodingError => e
p(Exception.message(e)) # => "U+00E9 from UTF-8 to US-ASCII"
end
19.61 valid_encoding?
String.valid_encoding?(x)
True when the bytes are valid in the String’s encoding.
p(String.valid_encoding?("é")) # => true
p(String.valid_encoding?("\xff")) # => false
19.62 ascii_only?
String.ascii_only?(x)
True when every character is ASCII (an empty String is).
p(String.ascii_only?("abc")) # => true
p(String.ascii_only?("é")) # => false
19.63 b
String.b(x)
A copy of the String with the same bytes labelled ASCII-8BIT (binary), so that its length is its byte count.
b = String.b("é")
p(b) # => "\xC3\xA9"
p(String.length(b)) # => 2
19.64 scrub
String.scrub(x, [String])
A new String in which every invalid byte sequence is replaced by repl (default: the replacement character "�").
p(String.scrub("a\xffb", "?")) # => "a?b"
p(String.valid_encoding?(String.scrub("a\xffb"))) # => true
19.65 scrub!
String.scrub!(x, [String])
scrub in place. Returns the subject, also when there was nothing to replace (unlike the other ! forms it never gives nil, as Ruby’s).
s = "a\xffb"
p(String.scrub!(s, "!")) # => "a!b"
p(String.scrub!("ok")) # => "ok"
19.66 unicode_normalize
String.unicode_normalize(x, [Symbol])
A new String in Unicode normalization form form: :nfc (default), :nfd, :nfkc or :nfkd. Another Symbol is an ArgumentError; a String in a non-Unicode encoding (a binary String) is an EncodingError.
d = String.unicode_normalize("é", :nfd)
p(String.length(d)) # => 2
p(String.length(String.unicode_normalize(d))) # => 1
```ruby error p(String.unicode_normalize(“a”, :bogus)) # !> ArgumentError: String.unicode_normalize: Invalid normalization form bogus
## unicode_normalize!
`String.unicode_normalize!(x, [Symbol])`
`unicode_normalize` **in place**; returns the subject (never nil).
```ruby
s = "é"
p(String.unicode_normalize!(s)) # => "é"
p(String.length(s)) # => 1
19.67 unicode_normalized?
String.unicode_normalized?(x, [Symbol])
True when the String is already in the form form (default :nfc).
p(String.unicode_normalized?("é")) # => true
p(String.unicode_normalized?("é", :nfd)) # => false
19.68 dump
String.dump(x)
A new String that is a quoted, ASCII-only source form of the subject: non-printing and non-ASCII characters become escapes such as \n and é, and " and \ are escaped. undump reverses it.
p(String.dump("a\né")) # => "\"a\\n\\u00E9\""
19.69 undump
String.undump(x)
The String that dump produced the subject from: the quotes are removed and the escapes read. A String that is not in dump’s form is an ArgumentError with Ruby’s message (Ruby’s method raises RuntimeError; in Sake a RuntimeError comes only from raise "msg" and Thread.raise).
p(String.undump("\"a\\n\"")) # => "a\n"
p(String.undump(String.dump("é"))) # => "é"
begin
p(String.undump("abc"))
rescue ArgumentError => e
puts(Exception.message(e)) # => invalid dumped string; not wrapped with '"' nor '"...".force_encoding("...")' form
end
```ruby error p(String.undump(“abc”)) # !> ArgumentError: String.undump: invalid dumped string
## unpack
`String.unpack(x, String)`
Decodes the bytes according to the `Array.pack` format `fmt` into an Array, as Ruby's `unpack`: `C` an unsigned byte, `v` a little-endian 16-bit Integer, `e` a little-endian Float, `a`/`A`/`Z` Strings, `*` repeating. The element type follows the directives (Integer, String, Float); a directive with no bytes left gives nil. An unknown directive is an `ArgumentError`.
```ruby
p(String.unpack("AB", "C*")) # => [65, 66]
p(String.unpack("\x01\x00", "v")) # => [1]
p(String.unpack("ab", "a1a1")) # => ["a", "b"]
p(String.unpack("\x00\x00\x80\x3f", "e")) # => [1.0]
p(String.unpack("", "C")) # => [nil]
```ruby error p(String.unpack(“abc”, “Z%”)) # !> ArgumentError: String.unpack: % is not supported
## unpack1
`String.unpack1(x, String)`
The first element of `unpack(x, fmt)`, or nil when there are no bytes for it; using the result unchecked is a `nil` problem at `--strict` level 2.
```ruby
p(String.unpack1("\x01\x00", "v")) # => 1
p(String.unpack1("abc", "a*")) # => "abc"
p(String.unpack1("", "C")) # => nil
ruby error
p(String.unpack1("ab", "C") + 1) # !> the operands may be nil
20. Symbol
A Symbol is a name as a value, made by the literal :name (a name with spaces or other characters is :"hello world"; see Values and types). Symbols of the same name are always one and the same value; they serve as Hash keys (the labels of Hash.of(name: 1) are Symbol keys) and as the marks of case/in branches. String.to_sym(s) (String.intern) makes one from a String, and Symbol.to_s turns it back. Ruby’s %i[a b] is not available yet (rejected statically); an Array of Symbols is written Symbol[:a, :b].
There is no Symbol#to_proc (&:name). Blocks are not values, so Array.map(xs, &:to_s) is rejected statically; write { |x| Integer.to_s(x) } (Functions and blocks).
When branching on literals, case s in :a ... in :b ... end, and s is a Symbol that did not come from a literal (the result of String.to_sym, say), --strict=3 reports that some values fall through, as exhaustive (add an else; see Control).
The operators on Symbols are ==, !=, <, <=, >, >= and <=>, and they compare Symbols with Symbols only (with a String or an Integer on the right, < and the others are a type problem statically). :a == "a" is false, and so is the function form Symbol.==(:a, "a"). Symbol.<(x, y) and so on are the function forms of those operators (Operators and indexing).
The string-like operations (upcase, slice, match, start_with?, …) give the result of treating the name as a String. Some return a Symbol (upcase and friends), some a String (slice). None changes the Symbol itself (a Symbol is immutable).
20.1 Symbol[]
Symbol[*Any]
Makes an Array of Symbols. It is a typed Array like Integer[]: every element must be a Symbol, and every write (Array.push, …) is checked. Anything but a Symbol is a type problem statically and a TypeError at run time. The empty Array of Symbols is Symbol[].
syms = Symbol[:a, :b]
Array.push(syms, :c)
p(syms) # => [:a, :b, :c]
p(Array.size(Symbol[])) # => 0
```ruby error Array.push(Symbol[:a], “x”) # !> Array.push: an element must be Symbol, but is String
## to_s, id2name, name
`Symbol.to_s(x)`
`Symbol.id2name(x)`
`Symbol.name(x)`
The name as a String. `to_s` and `id2name` return a new (writable) String each time; `name` returns the name the Symbol holds, which is frozen as in Ruby, so changing it with `String.upcase!` or the like is a `TypeError`.
```ruby
p(Symbol.to_s(:hello)) # => "hello"
p(Symbol.id2name(:"with space")) # => "with space"
s = Symbol.to_s(:abc)
String.upcase!(s)
p(s) # => "ABC"
p(Symbol.name(:abc)) # => "abc"
```ruby error s = Symbol.name(:abc) String.upcase!(s) # !> TypeError: String.upcase!: cannot change this String in place
## to_sym, intern
`Symbol.to_sym(x)`
`Symbol.intern(x)`
The Symbol itself (as Ruby's). A Symbol is made from a String by `String.to_sym` and `String.intern`.
```ruby
p(Symbol.to_sym(:a)) # => :a
p(Symbol.intern(:a) == :a) # => true
p(String.to_sym("a") == :a) # => true
20.2 length, size
Symbol.length(x)
Symbol.size(x)
The number of characters in the name (an Integer; not bytes).
p(Symbol.length(:hello)) # => 5
p(Symbol.size(:日本)) # => 2
20.3 empty?
Symbol.empty?(x)
True when the name is empty (:"").
p(Symbol.empty?(:"")) # => true
p(Symbol.empty?(:a)) # => false
20.4 encoding
Symbol.encoding(x)
The name of the name’s encoding, as a String (Ruby returns an Encoding object). An ASCII-only name is "US-ASCII", any other "UTF-8".
p(Symbol.encoding(:hello)) # => "US-ASCII"
p(Symbol.encoding(:日本)) # => "UTF-8"
20.5 upcase, downcase, capitalize, swapcase
Symbol.upcase(x)
Symbol.downcase(x)
Symbol.capitalize(x)
Symbol.swapcase(x)
A new Symbol with the name in upper case, in lower case, with only the first character upper case (the rest lower), or with the cases swapped. As Ruby’s String operations of the same names; Unicode characters are converted too. The option arguments (:ascii, …) do not exist.
p(Symbol.upcase(:hello)) # => :HELLO
p(Symbol.downcase(:HeLLo)) # => :hello
p(Symbol.capitalize(:hello_world)) # => :Hello_world
p(Symbol.swapcase(:HeLLo)) # => :hEllO
p(Symbol.downcase(:ÀB)) # => :àb
20.6 succ, next
Symbol.succ(x)
Symbol.next(x)
A new Symbol with the name advanced by Ruby’s String#succ (:a → :b, :az → :ba, :zz → :aaa, :a9 → :b0).
p(Symbol.succ(:a)) # => :b
p(Symbol.next(:az)) # => :ba
p(Symbol.succ(:zz)) # => :aaa
p(Symbol.succ(:a9)) # => :b0
20.7 start_with?, end_with?
Symbol.start_with?(x, String)
Symbol.end_with?(x, String)
True when the name starts or ends with the String s. The argument is a String; passing a Symbol is a type problem statically (Ruby’s forms with several arguments or a Regexp do not exist).
p(Symbol.start_with?(:hello, "he")) # => true
p(Symbol.end_with?(:hello, "lo")) # => true
p(Symbol.start_with?(:hello, "x")) # => false
```ruby error p(Symbol.start_with?(:abc, :a)) # !> Symbol.start_with?: argument 2 must be String, but is :a
## slice
`Symbol.slice(x, Integer, [Integer])`
A part of the name as a String. `slice(s, i)` is the one character at position `i` (a negative one counts from the end); `slice(s, i, n)` is `n` characters from position `i`. Outside the name it is nil (as Ruby's `String#slice`; `slice(s, i, n)` with `i` equal to the length is `""`). Because the result may be nil, using it unchecked is reported at level 2. There is no Range or String index (a `type` problem statically).
```ruby
p(Symbol.slice(:hello, 1)) # => "e"
p(Symbol.slice(:hello, -1)) # => "o"
p(Symbol.slice(:hello, 1, 3)) # => "ell"
p(Symbol.slice(:hello, 10)) # => nil
p(Symbol.slice(:abc, 3, 1)) # => ""
```ruby error p(Symbol.slice(:abc, 0..1)) # !> Symbol.slice: argument 2 must be Integer, but is Range[Integer]
## match
`Symbol.match(x, Regexp|String)`
Matches the pattern against the name and returns the MatchData of the first match, or nil when there is none (reported at level 2). A String pattern is taken as the source of a regular expression (as in Ruby).
```ruby
m = Symbol.match(:hello, /l+/)
p(m) # => #<MatchData "ll">
if m
p(MatchData.to_s(m)) # => "ll"
end
p(Symbol.match(:hello, "l+")) # => #<MatchData "ll">
p(Symbol.match(:hello, /z/)) # => nil
20.8 match?
Symbol.match?(x, Regexp|String)
True when the name matches the pattern. It makes no MatchData, so use it when only whether it matched matters.
p(Symbol.match?(:hello, /ell/)) # => true
p(Symbol.match?(:hello, "x")) # => false
20.9 casecmp
Symbol.casecmp(x, Symbol)
Compares the names of two Symbols ignoring case (in the ASCII range) and returns -1, 0, or 1. The argument must be a Symbol (a String is a type problem statically). Because Ruby’s casecmp returns nil when it cannot compare, the result type is Integer | nil, and using it in arithmetic unchecked is reported at level 2.
p(Symbol.casecmp(:a, :A)) # => 0
p(Symbol.casecmp(:a, :B)) # => -1
p(Symbol.casecmp(:b, :A)) # => 1
```ruby error p(Symbol.casecmp(:a, “b”)) # !> Symbol.casecmp: argument 2 must be Symbol, but is String
## casecmp?
`Symbol.casecmp?(x, Symbol)`
True when the names of two Symbols are equal ignoring case (by Unicode case folding). Unlike `casecmp`, the result is a Boolean and never nil: where Ruby's method gives nil (incompatible encodings) it is false.
```ruby
p(Symbol.casecmp?(:a, :A)) # => true
p(Symbol.casecmp?(:a, :b)) # => false
p(Symbol.casecmp?(:Straße, :STRASSE)) # => true
20.10 ==, !=
Symbol.==(x, Any)
Symbol.!=(x, Any)
True when the two are Symbols of the same name (!= is the negation). A right operand that is not a Symbol (a String, an Integer, nil) is simply not equal, with the function form as with the operator: Symbol.==(:a, "a") is false (no error).
p(:a == :a) # => true
p(:a != :b) # => true
p(:a == "a") # => false
p(Symbol.==(:a, :a)) # => true
p(Symbol.==(:a, "a")) # => false
p(Symbol.!=(:a, "a")) # => true
20.11 <, <=, >, >=
Symbol.<(x, Any)
Symbol.<=(x, Any)
Symbol.>(x, Any)
Symbol.>=(x, Any)
Compare the names of two Symbols as Strings (byte order, as in Ruby). The right operand must be a Symbol; a String or an Integer is a type problem statically.
p(:a < :b) # => true
p(:b >= :c) # => false
p(Symbol.<=(:a, :a)) # => true
```ruby error p(:a < “a”) # !> which the left operand’s type does not support
## <=>
`Symbol.<=>(x, Any)`
The result of comparing the names as -1, 0, or 1. The rules are those of `<`, and the right operand must be a Symbol. It serves as the key of `Array.sort_by`.
```ruby
p(:a <=> :b) # => -1
p(:aa <=> :b) # => -1
p(Symbol.<=>(:a, :a)) # => 0
21. Regexp
A Regexp is a regular expression. The literal /\d+/ takes Ruby’s flags: i (ignore case), m (. also matches a newline), x (ignore whitespace and comments), n (match bytes; /a/n has the encoding US-ASCII). /a#{x}/ embeds a String. Regexp.new(s, flags) builds one from a String. The syntax is Ruby’s (Onigmo) as it is, named groups (?<name>...) included (Values and types).
The main difference from Ruby is that a match leaves nothing behind globally: $~, $1 and Regexp.last_match do not exist, and /(?<y>\d+)/ =~ s does not create a local variable y (a static error). The result of a match is the MatchData that Regexp.match returns, kept in a variable and read from there (MatchData).
The operators on Regexps are =~ (the position of a match in a String, or nil), == and !=. The String forms s =~ re and s !~ re exist too. String.match, match?, scan, sub, gsub, split, index and others also take a Regexp (Operators and indexing). A Regexp cannot be a Hash key or a Set element (TypeError).
21.1 new
Regexp.new(String, [String])
Makes a Regexp from a source String. The second argument is a String of flag letters (“” by default): the literal’s i, m, x and n; any other letter is an ArgumentError at run time. A source that is not a valid regular expression is a RegexpError, which can be rescued (Ruby’s Integer flags such as Regexp::IGNORECASE are not taken). To match the special characters of the source literally, pass it through Regexp.escape.
p(Regexp.new("a.b")) # => /a.b/
re = Regexp.new("ab+", "i")
p(re) # => /ab+/i
p(Regexp.match?(re, "ABB")) # => true
p(Regexp.options(Regexp.new("a", "mi"))) # => 5
p(Regexp.new("a", "n")) # => /a/n
begin
p(Regexp.new("("))
rescue RegexpError => e
puts(Exception.message(e)) # => end pattern with unmatched parenthesis: /(/
end
```ruby error p(Regexp.new(“a”, “q”)) # !> ArgumentError: Regexp.new: unknown regexp option: q
```ruby error
p(Regexp.new("(")) # !> RegexpError: Regexp.new: end pattern with unmatched parenthesis: /(/
21.2 escape
Regexp.escape(String)
The String with the regular-expression special characters (., *, (, space, …) escaped with backslashes. Regexp.new(Regexp.escape(s)) is the Regexp that matches s itself.
p(Regexp.escape("a.b*c")) # => "a\\.b\\*c"
word = "a.b"
re = Regexp.new(Regexp.escape(word))
p(Regexp.match?(re, "a.b")) # => true
p(Regexp.match?(re, "axb")) # => false
21.3 source
Regexp.source(x)
The source of the regular expression (a String), without the flags. Embedded expressions appear expanded.
p(Regexp.source(/\d+/)) # => "\\d+"
p(Regexp.source(/a b/x)) # => "a b"
x = "c"
p(Regexp.source(/a#{x}/i)) # => "ac"
21.4 options
Regexp.options(x)
The flags as bits in an Integer (as Ruby’s: i is 1, x is 2, m is 4, n is 32).
p(Regexp.options(/a/)) # => 0
p(Regexp.options(/a/i)) # => 1
p(Regexp.options(/a/imx)) # => 7
p(Regexp.options(/a/n)) # => 32
21.5 casefold?
Regexp.casefold?(x)
True when the flag i is set.
p(Regexp.casefold?(/a/i)) # => true
p(Regexp.casefold?(/a/)) # => false
21.6 encoding
Regexp.encoding(x)
The name of the Regexp’s encoding (a String). An ASCII-only source is “US-ASCII”, a source with non-ASCII characters “UTF-8”, and /.../n “US-ASCII” (it matches bytes).
p(Regexp.encoding(/a/)) # => "US-ASCII"
p(Regexp.encoding(/あ/)) # => "UTF-8"
p(Regexp.encoding(/a/n)) # => "US-ASCII"
21.7 fixed_encoding?
Regexp.fixed_encoding?(x)
True when the encoding is fixed (a source with non-ASCII characters). An ASCII-only source and /.../n are false and can be matched against Strings of any encoding. As Ruby’s.
p(Regexp.fixed_encoding?(/a/)) # => false
p(Regexp.fixed_encoding?(/あ/)) # => true
21.8 timeout
Regexp.timeout(x)
The match timeout set on this Regexp (seconds, a Float) or nil. Sake has no way to set one, so it is always nil, and --strict (level 2) reports using it unchecked.
p(Regexp.timeout(/a/)) # => nil
21.9 names
Regexp.names(x)
The Array of the named groups’ names (Strings, in order of appearance, each once). Empty when there are no named groups.
p(Regexp.names(/(?<y>\d+)-(?<m>\d+)/)) # => ["y", "m"]
p(Regexp.names(/(\d+)/)) # => []
21.10 named_captures
Regexp.named_captures(x)
A Hash from each named group’s name (a String) to the Array of its group numbers. A name used twice lists two numbers.
p(Regexp.named_captures(/(?<y>\d+)-(?<m>\d+)/)) # => {"y" => [1], "m" => [2]}
p(Regexp.named_captures(/(?<a>.)(?<a>.)/)) # => {"a" => [1, 2]}
p(Regexp.named_captures(/(a)/)) # => {}
21.11 match
Regexp.match(x, String, [Integer])
The MatchData of the first match in the String. Without a match the result is nil, which --strict (level 2) reports when used unchecked (check it with if m or or raise). The third argument is the position to start searching from (in characters; 0 by default, negative counts from the end). String.match(s, re) is the same. Unlike Ruby, nothing is left in $~.
re = /(?<y>\d+)-(?<m>\d+)/
m = Regexp.match(re, "on 2026-10")
if m
p(m["y"]) # => "2026"
end
p(Regexp.match(re, "none")) # => nil
n = Regexp.match(/\d/, "a1b2", 2) or raise("no digit")
p(n[0]) # => "2"
```ruby error m = Regexp.match(/a/, “a”) p(m[0]) # !> the operands may be nil (MatchData | nil)
## match?
`Regexp.match?(x, String, [Integer])`
True when there is a match. It builds no MatchData, so it is the lighter choice when only the fact of a match matters. The third argument is the position to start from.
```ruby
p(Regexp.match?(/\d+/, "a1")) # => true
p(Regexp.match?(/\d+/, "abc")) # => false
p(Regexp.match?(/\d/, "1abc", 1)) # => false
21.12 =~
Regexp.=~(x, Any)
The function form of re =~ s. The position of the first match (an Integer, in characters), or nil when there is none (reported at --strict level 2). The right operand must be a String. s =~ re gives the same result, and s !~ re is true when there is no match. Unlike Ruby, named groups do not become local variables and $~ is not set.
p(/b/ =~ "abc") # => 1
p(/z/ =~ "abc") # => nil
p("abc" =~ /c/) # => 2
p("abc" !~ /z/) # => true
p(Regexp.=~(/b/, "abc")) # => 1
```ruby error
if /(?
## ==, !=
`Regexp.==(x, Any)`
`Regexp.!=(x, Any)`
True when the two Regexps have the same source and flags (`!=` is the negation). A right operand that is not a Regexp is never equal.
```ruby
p(/a/ == /a/) # => true
p(/a/ == /a/i) # => false
p(/a/ != /b/) # => true
p(/a/ == "a") # => false
21.13 union
Regexp.union(*String|Regexp|Array)
A Regexp that matches any of the arguments: Strings are escaped, Regexps keep their flags, and all are joined with |. With no arguments it is /(?!)/, which matches nothing. The patterns are listed one by one, or, as Ruby allows, given as one Array of Strings and Regexps (Regexp.union(Any["a", "b"])); an Array among other arguments, or an element that is neither a String nor a Regexp, is a TypeError at run time.
p(Regexp.union("a.b", /c/i)) # => /a\.b|(?i-mx:c)/
p(Regexp.match?(Regexp.union("cat", "dog"), "hotdog")) # => true
p(Regexp.union()) # => /(?!)/
p(Regexp.union(Any["a", "b"])) # => /a|b/
p(Regexp.union(Any["a.b", /c/])) # => /a\.b|(?-mix:c)/
ruby error
p(Regexp.union("a", Any["b"])) # !> TypeError: Regexp.union: no implicit conversion of Array into String
22. MatchData
A MatchData is the result of a regular-expression match. Regexp.match(re, s) and String.match(s, re) return one, or nil when there is no match, so it is checked before use (Regexp). Ruby’s globals $~ and $1 do not exist; the MatchData is kept in a variable and indexed (Values and types).
Groups are numbered as in Ruby: 0 is the whole match, and the (...) groups count from 1. A named group (?<name>...) can also be read by its name (a String). As in Ruby, once there is a named group, plain (...) groups do not count as groups. A group that did not take part in the match (an (a)? that matched nothing) is nil.
The only operator on a MatchData is the index m[k]; m[k] = v is not available (Operators and indexing). A MatchData itself cannot be a Hash key.
22.1 []
MatchData.[](x, Any)
The function form of m[k]. With an Integer k, the String of that group (0 is the whole match; a negative value counts from the end); with a String or Symbol, the String of the group of that name. A group that did not take part and an index out of range give nil, whereas an unknown name is an IndexError at run time. The nil is that of x[k] (an index miss), so --strict level 2 does not report using it unchecked; level 3 (index-nil) does. An index that is neither an Integer nor a String is a TypeError.
m = Regexp.match(/(?<y>\d+)-(?<m>\d+)?/, "ab 12- cd") or raise("no match")
p(m[0]) # => "12-"
p(m[1]) # => "12"
p(m[2]) # => nil
p(m[9]) # => nil
p(m["y"]) # => "12"
p(m[:y]) # => "12"
p(MatchData.[](m, -1)) # => nil
```ruby error
m = Regexp.match(/(?
## captures
`MatchData.captures(x)`
The Array of the Strings of groups 1 onwards (the whole match is not included). A group that did not take part is nil, so the element type is String or nil.
```ruby
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.captures(m)) # => ["12", nil]
22.2 named_captures
MatchData.named_captures(x)
A Hash from each named group’s name (a String) to the String it matched (nil when it did not take part). Empty when there are no named groups.
m = Regexp.match(/(?<y>\d+)-(?<m>\d+)?/, "12-") or raise("no match")
p(MatchData.named_captures(m)) # => {"y" => "12", "m" => nil}
22.3 names
MatchData.names(x)
The Array of the named groups’ names (as Regexp.names).
m = Regexp.match(/(?<y>\d+)-(?<m>\d+)?/, "12-") or raise("no match")
p(MatchData.names(m)) # => ["y", "m"]
22.4 to_a
MatchData.to_a(x)
The Array of the whole match and every group (m[0], m[1], …). A group that did not take part is nil.
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.to_a(m)) # => ["12-", "12", nil]
22.5 to_s
MatchData.to_s(x)
The String of the whole match (as m[0], but never nil).
m = Regexp.match(/\d+/, "ab 12 cd") or raise("no match")
p(MatchData.to_s(m)) # => "12"
22.6 values_at
MatchData.values_at(x, *Integer)
The Array of the Strings of the given group numbers (in the manner of Array.values_at). A group that did not take part and a number out of range give nil. Only Integers are accepted, not names (a type problem statically).
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.values_at(m, 0, 2, 9)) # => ["12-", nil, nil]
p(MatchData.values_at(m)) # => []
22.7 match
MatchData.match(x, Integer)
The String of the group with that number (nil when it did not take part: the nil of a miss, index-nil, reported only at --strict level 3). It differs from m[i] in that a number out of range is an IndexError at run time rather than nil.
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.match(m, 1)) # => "12"
p(MatchData.match(m, 2)) # => nil
```ruby error m = Regexp.match(/(a)/, “a”) or raise(“no match”) p(MatchData.match(m, 5)) # !> IndexError: MatchData.match: index 5 out of matches
## match_length
`MatchData.match_length(x, Integer)`
The number of characters the group with that number matched (an Integer). Nil when it did not take part (the nil of a miss, `index-nil`, level 3); a number out of range is an `IndexError`.
```ruby
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.match_length(m, 1)) # => 2
p(MatchData.match_length(m, 2)) # => nil
22.8 pre_match, post_match
MatchData.pre_match(x)
MatchData.post_match(x)
The String before the match and the String after it (Ruby’s $` and $').
m = Regexp.match(/\d+/, "ab 12 cd") or raise("no match")
p(MatchData.pre_match(m)) # => "ab "
p(MatchData.post_match(m)) # => " cd"
22.9 begin, end
MatchData.begin(x, Integer)
MatchData.end(x, Integer)
The position where the group with that number starts and where it ends (Integers in characters; end is one past the last character). For a group that did not take part they are nil; the result is Integer | nil, and the nil is that of a miss (index-nil), reported only at --strict level 3 (so are the nils of bytebegin, byteend, offset, byteoffset, match and match_length). A number out of range is an IndexError (index 5 out of matches), which can be rescued.
m = Regexp.match(/(\d+)-(\d+)?/, "ab 12-") or raise("no match")
p(MatchData.begin(m, 0)) # => 3
p(MatchData.end(m, 0)) # => 6
p(MatchData.begin(m, 1)) # => 3
p(MatchData.end(m, 1)) # => 5
p(MatchData.begin(m, 2)) # => nil
b = MatchData.begin(m, 2)
if b
p(b)
else
puts("group 2 did not take part") # => group 2 did not take part
end
```ruby error m = Regexp.match(/(a)/, “a”) or raise(“no match”) p(MatchData.begin(m, 5)) # !> IndexError: MatchData.begin: index 5 out of matches
## offset
`MatchData.offset(x, Integer)`
The Tuple `[begin, end]` of the group with that number. A group that did not take part gives `[nil, nil]`; a number out of range is an `IndexError`.
```ruby
m = Regexp.match(/(\d+)-(\d+)?/, "ab 12-") or raise("no match")
p(MatchData.offset(m, 1)) # => [3, 5]
p(MatchData.offset(m, 2)) # => [nil, nil]
```ruby error m = Regexp.match(/(a)/, “a”) or raise(“no match”) p(MatchData.offset(m, 5)) # !> IndexError: MatchData.offset: index 5 out of matches
## bytebegin, byteend
`MatchData.bytebegin(x, Integer)`
`MatchData.byteend(x, Integer)`
The byte versions of `begin` and `end` (Integers). They differ from the character positions in a String with multibyte characters. A group that did not take part gives nil (the nil of a miss, `index-nil`, level 3); a number out of range is an `IndexError`.
```ruby
m = Regexp.match(/(い)/, "あいう") or raise("no match")
p(MatchData.begin(m, 1)) # => 1
p(MatchData.bytebegin(m, 1)) # => 3
p(MatchData.byteend(m, 1)) # => 6
22.10 byteoffset
MatchData.byteoffset(x, Integer)
The byte version of offset: the Tuple [bytebegin, byteend]. A group that did not take part gives [nil, nil].
m = Regexp.match(/(い)/, "あいう") or raise("no match")
p(MatchData.byteoffset(m, 1)) # => [3, 6]
p(MatchData.offset(m, 1)) # => [1, 2]
22.11 length, size
MatchData.length(x)
MatchData.size(x)
The number of groups plus one (the whole match included; the length of to_a).
m = Regexp.match(/(\d+)-(\d+)?/, "12-") or raise("no match")
p(MatchData.size(m)) # => 3
p(MatchData.length(m)) # => 3
22.12 regexp
MatchData.regexp(x)
The Regexp that made this match.
m = Regexp.match(/\d+/i, "12") or raise("no match")
p(MatchData.regexp(m)) # => /\d+/i
22.13 string
MatchData.string(x)
The whole String the match was tried on. It is frozen: an in-place change such as String.upcase! is a TypeError.
m = Regexp.match(/\d+/, "ab 12 cd") or raise("no match")
p(MatchData.string(m)) # => "ab 12 cd"
23. Array
An Array is a sequence whose length varies. Any[1, 2, 3] makes an Array. Ruby’s [1, 2, 3] is an Array, but Sake’s literal [1, 2, 3] is a Tuple of fixed length, and passing one to Array.push and the like is rejected statically (Values and types). A typed Array such as Integer[1, 2], String[] or Point[p] is an Array with a declared element type: every write (Array.push, append, unshift, prepend, insert, concat, map!, fill, a[i] = v, …) is checked, and a value of another type is a type problem statically and a TypeError at run time. Every operation in this chapter works on typed Arrays too. The new Arrays returned by map, select, sort, first(a, n), + and so on carry no element type declaration; only dup keeps it.
The checker gives one Array (one construction site) a single element type for the whole program. After an in-place rewrite such as Array.map! puts another type in, the Array is taken to hold both types from then on, and operations that fit only one of them become type (partial) problems (Values and types).
Two kinds of operation use nil for “no element”. The nil of a miss (x[k], first, last, pop, shift, min, max, minmax, min_by, max_by, minmax_by, at, slice, slice!, dig, sample, delete_at: an empty Array or an index outside it), used unchecked, is an index-nil problem at --strict=3 only; --strict (level 2) lets it pass. Every other nil (find, index, find_index, rindex, bsearch, delete, uniq!, …) is a level 2 nil problem (Overview).
The operators on Arrays are +, -, *, ==, !=, <, <=, >, >=, <=> (two Arrays compare in dictionary order) and the index a[i], a[i, n], a[range], a[i] = v. The entries Array.+(x, y) and so on are the function forms of those operators (Operators and indexing). Operations that compare elements (sort, min, max, <, …) need elements of comparable types: Integers and Floats compare with each other, Tuples element by element, class instances with their type’s <=> (include Comparable).
Operations with a block are written as in Ruby (Array.map(xs) { |x| x * 2 }). There is no Enumerator (no blockless each). The operations whose block is optional (each_with_index, each_slice, each_cons) return an Array when called without one.
23.1 Array[]
Array[*Any]
Makes an Array of Arrays, as T[...] makes an Array of T for every T: each element must be an Array, checked on every write (a literal of another type is a static error, any other value a type problem and a TypeError at run time). An Array of any values is Any[...] (Kernel).
rows = Array[Any[1, 2], Any[3]]
Array.push(rows, Any[4, 5])
p(rows) # => [[1, 2], [3], [4, 5]]
```ruby error p(Array[1]) # !> Array[]: element 1 must be Array, got Integer
## new
`Array.new(Integer, [Any]) [{ }]`
Makes a new Array of length `n`. `Array.new(n)` holds n nils, `Array.new(n, v)` the same value `v` n times (one shared value: the two elements of `Array.new(2, Any[])` are the same Array), and `Array.new(n) { |i| ... }` the block's value for each index i. The element type is nil, the type of `v`, or the block's result type. A negative `n` is an `ArgumentError`.
```ruby
p(Array.new(3)) # => [nil, nil, nil]
p(Array.new(2, "a")) # => ["a", "a"]
p(Array.new(3) { |i| i * i }) # => [0, 1, 4]
rows = Array.new(2, Any[])
Array.push(Array.fetch(rows, 0), 1)
p(rows) # => [[1], [1]]
rows2 = Array.new(2) { |i| Any[] }
Array.push(Array.fetch(rows2, 0), 1)
p(rows2) # => [[1], []]
```ruby error Array.new(-1) # !> ArgumentError: Array.new: negative size -1
## dup
`Array.dup(x)`
A new Array with the same elements (a shallow copy: the elements are shared). The copy of a typed Array is a typed Array with the same element type; unlike every other new Array in this chapter, this one keeps the declaration.
```ruby
xs = Integer[1, 2]
ys = Array.dup(xs)
Array.push(ys, 3)
p(xs) # => [1, 2]
p(ys) # => [1, 2, 3]
```ruby error ys = Array.dup(Integer[1]) Array.push(ys, “s”) # !> Array.push: an element must be Integer, but is String
## to_a
`Array.to_a(x)`
The subject itself (the same Array), not a copy. It exists for symmetry with `Tuple.to_a`, `Set.to_a`, `Hash.to_a` and the other operations that give an Array of another type.
```ruby
xs = Any[1, 2]
p(Array.to_a(xs)) # => [1, 2]
p(Kernel.equal?(Array.to_a(xs), xs)) # => true
23.2 length, size
Array.length(x)
Array.size(x)
The number of elements (an Integer).
p(Array.size(Any[3, 1, 2])) # => 3
p(Array.length(Any[])) # => 0
23.3 empty?
Array.empty?(x)
True when there are no elements.
p(Array.empty?(Any[])) # => true
p(Array.empty?(Any[1])) # => false
23.4 []
Array.[](x, Any, [Integer])
The function form of a[i], a[i, n] and a[range]. a[i] is the element at position i (an Integer; a negative one counts from the end), or nil outside the Array. That nil is the nil of a miss: using it unchecked is an index-nil problem at --strict=3 (level 2 does not report it). a[i, n] gives n elements from position i and a[range] the elements of the range, as a new Array, or nil when the start is past the end. Use Array.fetch for an exception instead. An index that is neither an Integer nor a Range is a TypeError.
xs = Any[3, 1, 2]
p(xs[1]) # => 1
p(xs[-1]) # => 2
p(xs[9]) # => nil
p(xs[1, 2]) # => [1, 2]
p(xs[0..1]) # => [3, 1]
p(xs[5, 1]) # => nil
p(Array.[](xs, 0)) # => 3
p(Array.[](xs, 0, 2)) # => [3, 1]
23.5 []=
Array.[]=(x, Any, Any)
The function form of a[i] = v. Replaces the element at position i with v and returns v. Writing past the end fills the gap with nil, as in Ruby. In a typed Array the type of v is checked (type statically, TypeError at run time), and since nil cannot be an element, writing past the end is an IndexError. A negative index before the start is an IndexError.
xs = Any[1]
xs[3] = 2
p(xs) # => [1, nil, nil, 2]
xs[-1] = 5
p(xs) # => [1, nil, nil, 5]
p(Array.[]=(xs, 0, 0)) # => 0
```ruby error is = Integer[1] is[3] = 2 # !> IndexError: Array.[]=: index 3 is past the end of Integer[] (length 1); the gap would be nil
## at
`Array.at(x, Integer)`
The element at position `i` (the same as `a[i]`; a negative one counts from the end). Outside the Array it is nil, the nil of a miss, reported at level 3 only.
```ruby
xs = Any[3, 1, 2]
p(Array.at(xs, 0)) # => 3
p(Array.at(xs, -1)) # => 2
p(Array.at(xs, 9)) # => nil
23.6 fetch
Array.fetch(x, Integer, [Any])
The element at position i. Outside the Array it returns default when given and raises IndexError otherwise. The result is never nil (unless the default is), so it is the way to avoid the nil check of an index under --strict. The result type is the element type (joined with the default’s type when given).
xs = Any[3, 1, 2]
p(Array.fetch(xs, 1)) # => 1
p(Array.fetch(xs, -1)) # => 2
p(Array.fetch(xs, 9, 0)) # => 0
```ruby error Array.fetch(Any[3, 1, 2], 9) # !> IndexError: Array.fetch: index 9 outside of array bounds: -3…3
## first, last
`Array.first(x, [Integer])`
`Array.last(x, [Integer])`
`first(a)` is the first element and `last(a)` the last; on an empty Array nil (the nil of a miss, reported at level 3 only). `first(a, n)` and `last(a, n)` give the first or last n elements as a new Array (fewer when there are not enough; never nil). A negative n is an `ArgumentError`.
```ruby
xs = Any[3, 1, 2]
p(Array.first(xs)) # => 3
p(Array.last(xs)) # => 2
p(Array.first(xs, 2)) # => [3, 1]
p(Array.last(xs, 5)) # => [3, 1, 2]
p(Array.first(Any[])) # => nil
p(Array.last(Any[], 2)) # => []
```ruby error Array.first(Any[1], -1) # !> ArgumentError: Array.first: negative size -1
## slice
`Array.slice(x, Integer|Range, [Integer])`
The same results as `a[i]`, `a[i, n]` and `a[range]`: `slice(a, i)` is an element or nil, `slice(a, i, n)` and `slice(a, range)` a new Array or nil (when the start is past the end). Like `a[i]`'s, this nil is the nil of a miss: used unchecked it is an `index-nil` problem at `--strict=3` only (level 2 does not report it). For an exception instead, use `Array.fetch`.
```ruby
xs = Any[3, 1, 2]
p(Array.slice(xs, 1)) # => 1
p(Array.slice(xs, 1, 5)) # => [1, 2]
p(Array.slice(xs, 1..)) # => [1, 2]
p(Array.slice(xs, 9)) # => nil
y = Array.slice(xs, 0)
p(y + 1) if y # => 4
23.7 dig
Array.dig(x, Integer, *Any)
The element at position i or nil (the same as a[i]). With two or more keys it digs into nested values as Ruby’s dig does: each further key is applied to the value the previous one gave (an Integer position for an Array, the key for a Hash), and a nil on the way stays nil. The values on the way must be Arrays, Tuples or Hashes; another type (Integer, String, Record, …) is a type problem statically and, run without the check, a TypeError (Integer cannot be dug into (not an Array, a Tuple, or a Hash)). A non-Integer key applied to an Array or a Tuple is a TypeError. The nil is the nil of a miss, reported at level 3 only.
xs = Any[Any[1, 2], Any[3]]
p(Array.dig(xs, 0)) # => [1, 2]
p(Array.dig(xs, 5)) # => nil
p(Array.dig(xs, 0, 1)) # => 2
p(Array.dig(xs, 1, 5)) # => nil
p(Array.dig(xs, 5, 0)) # => nil
p(Array.dig(Any[Hash.of(a: 1)], 0, :a)) # => 1
p(Array.dig(Any[[1, "x"]], 0, 1)) # => "x"
p(Array.dig(Any[[1, "x"]], 0, 5)) # => nil
```ruby error p(Array.dig(Any[Any[1, 2]], 0, 1, 2)) # !> Array.dig: the value must be Array|Hash|Tuple, but is Integer
```ruby error
p(Array.dig(Any[Any[1]], 0, "k")) # !> TypeError: Array.dig: an index into Array must be Integer, got String
23.8 values_at
Array.values_at(x, *Integer)
A new Array of the elements at the given positions. A position outside the Array gives nil (the element type is joined with nil).
xs = Any[3, 1, 2]
p(Array.values_at(xs, 0, 2, 7)) # => [3, 2, nil]
23.9 fetch_values
Array.fetch_values(x, *Integer)
A new Array of the elements at the given positions. A position outside the Array is an IndexError (never nil).
p(Array.fetch_values(Any[3, 1, 2], 0, 2)) # => [3, 2]
```ruby error Array.fetch_values(Any[3, 1, 2], 0, 9) # !> IndexError: Array.fetch_values: index 9 outside of array bounds: -3…3
## push, append
`Array.push(x, *Any)`
`Array.append(x, *Any)`
Add the values (any number) at the end and return the subject. In place. A typed Array checks each value's type (`type` statically, `TypeError` at run time). The checker adds the added values' types to the Array's element type. There is no Ruby `<<`.
```ruby
xs = Any[1]
p(Array.push(xs, 2, 3)) # => [1, 2, 3]
p(Array.append(xs, 4)) # => [1, 2, 3, 4]
```ruby error Array.push(Integer[1, 2], “x”) # !> Array.push: an element must be Integer, but is String
## unshift, prepend
`Array.unshift(x, *Any)`
`Array.prepend(x, *Any)`
Add the values (any number, in the order given) at the front and return the subject. In place. A typed Array checks the types.
```ruby
xs = Any[3]
p(Array.unshift(xs, 1, 2)) # => [1, 2, 3]
p(Array.prepend(xs, 0)) # => [0, 1, 2, 3]
23.10 insert
Array.insert(x, Integer, *Any)
Inserts the values (any number) before position i and returns the subject. An i equal to the length appends. A negative i counts from the end (-1 appends, -size - 1 prepends). When i is beyond the length Ruby fills the gap with nil; Sake raises IndexError instead (in an untyped Array too: it does not create nils silently). A negative i below -size - 1 (before the start) is also an IndexError. A typed Array checks the inserted values’ types (type statically, TypeError at run time).
xs = Any[1, 2, 3]
p(Array.insert(xs, 1, 9, 9)) # => [1, 9, 9, 2, 3]
p(Array.insert(xs, -2, 7)) # => [1, 9, 9, 2, 7, 3]
p(Array.insert(xs, 6, 8)) # => [1, 9, 9, 2, 7, 3, 8]
```ruby error Array.insert(Any[1], 3, 2) # !> IndexError: Array.insert: index 3 is past the end of the Array (length 1); the gap would be nil
```ruby error
Array.insert(Any[1, 2], -4, 0) # !> IndexError: Array.insert: index -4 is before the start of the Array (length 2)
```ruby error Array.insert(Integer[1], 0, “a”) # !> Array.insert: an element must be Integer, but is String
## concat
`Array.concat(x, Array)`
Appends all elements of the other Array and returns the subject. In place (`+` makes a new Array). A typed Array checks the elements' types.
```ruby
xs = Any[1]
p(Array.concat(xs, Any[2, 3])) # => [1, 2, 3]
```ruby error Array.concat(Integer[1], Any[“a”]) # !> Array.concat: an element must be Integer, but is String
## fill
`Array.fill(x, Any)`
Replaces every element with `v` and returns the subject (the length does not change). Ruby's range and block forms do not exist. A typed Array checks the type of `v` (`type` statically; `TypeError` when run without the check).
```ruby
xs = Any[1, 2, 3]
p(Array.fill(xs, 0)) # => [0, 0, 0]
```ruby error Array.fill(Integer[1], “a”) # !> Array.fill: an element must be Integer, but is String
## replace
`Array.replace(x, Array)`
Replaces the subject's contents with the other Array's elements and returns the subject (unlike `a = other`, every place that shares the Array sees it). A typed Array checks the elements' types (`type` statically, `TypeError` at run time).
```ruby
xs = Any[1, 2, 3]
ys = xs
p(Array.replace(xs, Any[9])) # => [9]
p(ys) # => [9]
```ruby error Array.replace(Integer[1], Any[“a”]) # !> Array.replace: an element must be Integer, but is String
## pop
`Array.pop(x)`
Removes and returns the last element. On an empty Array nil (the nil of a miss, reported at level 3 only). There is no Ruby `pop(n)`.
```ruby
xs = Any[1, 2]
p(Array.pop(xs)) # => 2
p(xs) # => [1]
p(Array.pop(Any[])) # => nil
23.11 shift
Array.shift(x, [Integer])
shift(a) removes and returns the first element, nil on an empty Array (the nil of a miss, reported at level 3 only). shift(a, n) removes the first n elements and returns them as a new Array (fewer when there are not enough; never nil). A negative n is an ArgumentError.
xs = Any[1, 2, 3, 4]
p(Array.shift(xs)) # => 1
p(Array.shift(xs, 2)) # => [2, 3]
p(xs) # => [4]
p(Array.shift(Any[])) # => nil
p(Array.shift(Any[], 2)) # => []
23.12 delete
Array.delete(x, Any)
Removes every element == to v and returns v. When there was none, nil (a level 2 nil problem).
xs = Any[1, 2, 1, 3]
p(Array.delete(xs, 1)) # => 1
p(xs) # => [2, 3]
p(Array.delete(xs, 42)) # => nil
23.13 delete_at
Array.delete_at(x, Integer)
Removes and returns the element at position i. Outside the Array nil (the nil of a miss, reported at level 3 only).
xs = Any[1, 2, 3]
p(Array.delete_at(xs, 1)) # => 2
p(xs) # => [1, 3]
p(Array.delete_at(xs, 99)) # => nil
23.14 slice!
Array.slice!(x, Integer|Range, [Integer])
Returns what slice returns and removes it from the subject: slice!(a, i) an element or nil, slice!(a, i, n) and slice!(a, range) a new Array or nil. The nil is the nil of a miss, reported at level 3 only.
xs = Any[1, 2, 3, 4]
p(Array.slice!(xs, 1, 2)) # => [2, 3]
p(xs) # => [1, 4]
p(Array.slice!(xs, 0)) # => 1
p(Array.slice!(xs, 5)) # => nil
p(Array.slice!(xs, 0..0)) # => [4]
p(xs) # => []
23.15 clear
Array.clear(x)
Removes every element and returns the subject (now empty).
xs = Any[1, 2]
p(Array.clear(xs)) # => []
p(xs) # => []
23.16 each, each_entry
Array.each(x) { }
Array.each_entry(x) { }
Passes each element in order to the block and returns the subject. The block is required (there is no form returning Ruby’s Enumerator). break v ends it at once with v as the result. each_entry is the same as each.
xs = Any[1, 2, 3]
r = Array.each(xs) { |x| puts(x) }
# => 1
# => 2
# => 3
p(r) # => [1, 2, 3]
p(Array.each(xs) { |x| break x if x == 2 }) # => 2
23.17 each_with_index
Array.each_with_index(x) [{ }]
Passes each element and its index (from 0) to the block and returns the subject. Without a block it returns, instead of Ruby’s Enumerator, a new Array of [element, index] Tuples.
xs = Any["a", "b"]
Array.each_with_index(xs) { |s, i| puts("#{i}:#{s}") }
# => 0:a
# => 1:b
p(Array.each_with_index(xs)) # => [["a", 0], ["b", 1]]
23.18 each_index
Array.each_index(x) { }
Passes the indexes 0 to length-1 in order to the block and returns the subject.
xs = Any["a", "b"]
Array.each_index(xs) { |i| puts(i) }
# => 0
# => 1
23.19 reverse_each
Array.reverse_each(x) { }
Passes each element to the block from the last to the first and returns the subject.
Array.reverse_each(Any[1, 2, 3]) { |x| puts(x) }
# => 3
# => 2
# => 1
23.20 each_slice, each_cons
Array.each_slice(x, Integer) [{ }]
Array.each_cons(x, Integer) [{ }]
each_slice(a, n) passes the elements cut into Arrays of n, and each_cons(a, n) each window of n consecutive elements, to the block, and returns the subject. Without a block they return, instead of Ruby’s Enumerator, a new Array of those Arrays (each_cons gives [] when n exceeds the length). n must be at least 1; 0 and a negative n are an ArgumentError.
xs = Any[1, 2, 3]
p(Array.each_slice(xs, 2)) # => [[1, 2], [3]]
p(Array.each_cons(xs, 2)) # => [[1, 2], [2, 3]]
Array.each_slice(xs, 2) { |s| p(s) }
# => [1, 2]
# => [3]
p(Array.each_cons(xs, 2) { |s| s }) # => [1, 2, 3]
p(Array.each_cons(xs, 5)) # => []
```ruby error Array.each_slice(Any[1], 0) # !> ArgumentError: Array.each_slice: invalid slice size
## each_with_object
`Array.each_with_object(x, Any) { }`
Passes each element and `memo` (in that order) to the block and finally returns `memo`, whose type is the result type. Unlike `reduce`, the block's value is not used: the result accumulates by changing `memo` in place.
```ruby
xs = Any[1, 2, 3]
p(Array.each_with_object(xs, Any[]) { |x, acc| Array.push(acc, x * 2) }) # => [2, 4, 6]
23.21 cycle
Array.cycle(x, Integer) { }
Passes all elements to the block n times over and returns nil. Ruby’s endless form (no argument) does not exist: n is required. For n of 0 or less nothing happens.
p(Array.cycle(Any[1, 2], 2) { |x| puts(x) })
# => 1
# => 2
# => 1
# => 2
# => nil
23.22 map, collect
Array.map(x) { }
Array.collect(x) { }
A new Array of the block’s results for each element. Its element type is the block’s result type, with no declaration (anything can be added, even when made from a typed Array).
xs = Any[1, 2, 3]
p(Array.map(xs) { |x| x * 2 }) # => [2, 4, 6]
p(Array.collect(xs) { |x| Integer.to_s(x) }) # => ["1", "2", "3"]
p(xs) # => [1, 2, 3]
23.23 map!, collect!
Array.map!(x) { }
Array.collect!(x) { }
Replace each element with the block’s value and return the subject. In place. Because the checker gives one Array one element type, putting in a type other than the original makes the Array hold both types from then on, and operations that fit only one of them become type problems. Use them for a mapping within one type, and map (a new Array) to change the type. A typed Array checks the results’ type (type statically, TypeError at run time).
xs = Any[1, 2, 3]
p(Array.map!(xs) { |x| x * 2 }) # => [2, 4, 6]
p(Array.collect!(xs) { |x| x + 1 }) # => [3, 5, 7]
p(xs) # => [3, 5, 7]
```ruby error xs = Any[1, 2, 3] Array.map!(xs) { |x| Integer.to_s(x) } p(Array.sum(xs)) # !> Array.sum: an element must be Integer|Float|Rational|Complex, but can be String
## flat_map
`Array.flat_map(x) { }`
A new Array that joins the sequences the block returns for each element. The block must return an Array or a Tuple; any other value is a `TypeError` at run time. Unlike Ruby's, it does not keep a non-Array value as it is.
```ruby
p(Array.flat_map(Any[1, 2]) { |x| Any[x, x * 10] }) # => [1, 10, 2, 20]
p(Array.flat_map(Any[1, 2]) { |x| [x, x] }) # => [1, 1, 2, 2]
```ruby error Array.flat_map(Any[1]) { |x| x } # !> TypeError: Array.flat_map: the block must return an Array or a Tuple, got Integer
## filter_map
`Array.filter_map(x) { }`
A new Array of the block's results for each element, without the nils and falses (`map` and `compact` in one; nil is removed from the element type).
```ruby
xs = Any[1, 2, 3, 4]
p(Array.filter_map(xs) { |x| x * 10 if x > 2 }) # => [30, 40]
p(Array.filter_map(xs) { |x| x > 2 }) # => [true, true]
23.24 select, filter, find_all
Array.select(x) { }
Array.filter(x) { }
Array.find_all(x) { }
A new Array of the elements for which the block returns a truthy value (anything but nil and false).
xs = Any[1, 2, 3, 4]
p(Array.select(xs) { |x| x > 2 }) # => [3, 4]
p(Array.filter(xs) { |x| x > 2 }) # => [3, 4]
p(Array.find_all(xs) { |x| x > 2 }) # => [3, 4]
23.25 select!, filter!, keep_if
Array.select!(x) { }
Array.filter!(x) { }
Array.keep_if(x) { }
Keep only the elements for which the block is truthy (in place). keep_if always returns the subject. select! and filter! return nil when nothing was removed (as Ruby’s), and using that result unchecked is a level 2 nil problem.
xs = Any[1, 2, 3]
p(Array.select!(xs) { |x| x > 1 }) # => [2, 3]
p(Array.select!(xs) { |x| x > 1 }) # => nil
p(Array.keep_if(xs) { |x| x > 1 }) # => [2, 3]
p(Array.keep_if(xs) { |x| x > 2 }) # => [3]
p(xs) # => [3]
23.26 reject
Array.reject(x) { }
A new Array without the elements for which the block is truthy.
p(Array.reject(Any[1, 2, 3, 4]) { |x| x > 2 }) # => [1, 2]
23.27 reject!, delete_if
Array.reject!(x) { }
Array.delete_if(x) { }
Remove the elements for which the block is truthy (in place). delete_if always returns the subject. reject! returns nil when nothing was removed (as Ruby’s), and using that result unchecked is a level 2 nil problem.
xs = Any[1, 2, 3]
p(Array.reject!(xs) { |x| x > 2 }) # => [1, 2]
p(Array.reject!(xs) { |x| x > 2 }) # => nil
p(Array.delete_if(xs) { |x| x > 5 }) # => [1, 2]
p(Array.delete_if(xs) { |x| x > 1 }) # => [1]
23.28 partition
Array.partition(x) { }
The two-element Tuple [selected, rest]: an Array of the elements for which the block is truthy, and an Array of the others. Take it apart with multiple assignment.
evens, odds = Array.partition(Any[1, 2, 3, 4]) { |x| Integer.even?(x) }
p(evens) # => [2, 4]
p(odds) # => [1, 3]
23.29 group_by
Array.group_by(x) { }
A Hash from each block value to the Array of the elements that gave it (in order of first appearance). The keys must be values a Hash key can be (a Regexp and the like are a TypeError; Values and types).
p(Array.group_by(Any[1, 2, 3, 4]) { |x| x % 2 }) # => {1 => [1, 3], 0 => [2, 4]}
23.30 chunk_while, slice_when
Array.chunk_while(x) { }
Array.slice_when(x) { }
Cut the sequence by passing each pair of neighbours (a, b) to the block, and return a new Array of the pieces (Arrays). chunk_while keeps neighbours together while the block is truthy; slice_when cuts where it is.
xs = Any[1, 2, 4, 5, 7]
p(Array.chunk_while(xs) { |a, b| b == a + 1 }) # => [[1, 2], [4, 5], [7]]
p(Array.slice_when(xs) { |a, b| b != a + 1 }) # => [[1, 2], [4, 5], [7]]
23.31 take, drop
Array.take(x, Integer)
Array.drop(x, Integer)
take(a, n) is a new Array of the first n elements, drop(a, n) of the rest after the first n (fewer when there are not enough). A negative n is an ArgumentError.
xs = Any[1, 2, 3, 4]
p(Array.take(xs, 2)) # => [1, 2]
p(Array.drop(xs, 2)) # => [3, 4]
p(Array.take(xs, 9)) # => [1, 2, 3, 4]
23.32 take_while, drop_while
Array.take_while(x) { }
Array.drop_while(x) { }
A new Array of the leading elements for which the block is truthy (take_while), or of the rest after dropping them (drop_while).
xs = Any[1, 2, 3, 4]
p(Array.take_while(xs) { |x| x < 3 }) # => [1, 2]
p(Array.drop_while(xs) { |x| x < 3 }) # => [3, 4]
23.33 include?
Array.include?(x, Any)
True when some element is == to v. class instances compare with their type’s equality (Operators and indexing).
p(Array.include?(Any[1, 2], 2)) # => true
p(Array.include?(Any[1, 2], nil)) # => false
p(Array.include?(Any[[1, 2]], [1, 2])) # => true
23.34 index, rindex
Array.index(x, Any)
Array.rindex(x, Any)
The position (an Integer) of the first (index) or last (rindex) element == to v, or nil when there is none: a level 2 nil problem. Ruby’s block form does not exist; to search by a condition use find_index.
xs = Any[3, 1, 4, 1]
p(Array.index(xs, 1)) # => 1
p(Array.rindex(xs, 1)) # => 3
p(Array.index(xs, 9)) # => nil
```ruby error i = Array.index(Any[1, 2], 2) p(i + 1) # !> the operands may be nil
## find_index
`Array.find_index(x) { }`
The position (an Integer) of the first element for which the block is truthy, or nil when there is none (a level 2 `nil` problem).
```ruby
p(Array.find_index(Any[3, 1, 4]) { |x| x > 3 }) # => 2
p(Array.find_index(Any[3, 1, 4]) { |x| x > 9 }) # => nil
23.35 find, detect
Array.find(x) { }
Array.detect(x) { }
The first element for which the block is truthy, or nil when there is none: a level 2 nil problem (an empty Array gives nil too; there is no telling “no elements” from “not found”).
xs = Any[3, 1, 4]
p(Array.find(xs) { |x| x > 3 }) # => 4
p(Array.detect(xs) { |x| x > 30 }) # => nil
```ruby error y = Array.find(Any[3, 1, 4]) { |x| x > 1 } p(y + 1) # !> the operands may be nil
## rfind
`Array.rfind(x) { }`
The last element for which the block is truthy, or nil when there is none (a level 2 `nil` problem). It maps to Ruby 4.0's `Array#rfind` and is unavailable on an older Ruby.
```ruby
p(Array.rfind(Any[3, 1, 4, 1, 5]) { |x| x < 4 }) # => 1
23.36 bsearch, bsearch_index
Array.bsearch(x) { }
Array.bsearch_index(x) { }
Binary search in a sorted Array, giving the element (bsearch) or its position (bsearch_index). Ruby’s two modes apply: when the block returns true/false, the first element for which it is true (find-minimum); when it returns an Integer (the result of <=>), an element for which it is 0 (find-any). Nil when nothing is found (a level 2 nil problem). On an unsorted Array the result is undefined.
xs = Any[1, 3, 5, 7]
p(Array.bsearch(xs) { |x| x >= 4 }) # => 5
p(Array.bsearch_index(xs) { |x| x >= 4 }) # => 2
p(Array.bsearch(xs) { |x| 3 <=> x }) # => 3
p(Array.bsearch(xs) { |x| x >= 40 }) # => nil
23.37 assoc, rassoc
Array.assoc(x, Any)
Array.rassoc(x, Any)
In an Array of pairs (Tuples or Arrays), the first element whose first item equals k (assoc) or whose second item equals v (rassoc). Nil when there is none (a level 2 nil problem). Ruby’s assoc looks at Array elements only; Sake’s pairs are usually Tuples, so Tuples are looked at too.
pairs = Any[[1, "a"], [2, "b"]]
p(Array.assoc(pairs, 2)) # => [2, "b"]
p(Array.rassoc(pairs, "a")) # => [1, "a"]
p(Array.assoc(pairs, 9)) # => nil
23.38 count
Array.count(x, [Any]) [{ }]
count(a) is the number of elements, count(a, v) the number of elements == to v, and count(a) { |x| ... } the number of elements for which the block is truthy (an Integer).
xs = Any[3, 1, 4, 1]
p(Array.count(xs)) # => 4
p(Array.count(xs, 1)) # => 2
p(Array.count(xs) { |x| x > 1 }) # => 2
23.39 any?, all?, none?, one?
Array.any?(x) { }
Array.all?(x) { }
Array.none?(x) { }
Array.one?(x) { }
True when the block is truthy for at least one element (any?), for every element (all?), for no element (none?), or for exactly one (one?). The block is required (Ruby’s blockless form and pattern argument do not exist). On an empty Array all? and none? are true, any? and one? false.
xs = Any[3, 1, 4]
p(Array.any?(xs) { |x| x > 3 }) # => true
p(Array.all?(xs) { |x| x > 0 }) # => true
p(Array.none?(xs) { |x| x > 4 }) # => true
p(Array.one?(xs) { |x| x > 3 }) # => true
p(Array.all?(Any[]) { |x| x > 0 }) # => true
```ruby error Array.any?(Any[1]) # !> Array.any? requires a block
## sum
`Array.sum(x, [Any]) [{ }]`
The sum of the elements; with a block, of the block's values. Addition starts from `init` (default 0), and on an empty Array the result is `init` itself. The elements and `init` must be numbers (Integer, Float, Rational, Complex) or values of a class that includes `Arithmetic` and defines `+`; anything else is a `type` problem statically and a `TypeError` at run time (to concatenate Strings use `join`). Write `sum(a, 0.0)` for a Float sum: it is a Float even when the Array is empty.
```ruby
p(Array.sum(Any[1, 2, 3])) # => 6
p(Array.sum(Any[])) # => 0
p(Array.sum(Any[], 0.0)) # => 0.0
p(Array.sum(Any[1, 2.5])) # => 3.5
p(Array.sum(Any[1, 2], 10)) # => 13
p(Array.sum(Any["a", "bb"]) { |s| String.size(s) }) # => 3
```ruby error Array.sum(Any[“a”, “b”]) # !> Array.sum: an element must be Integer|Float|Rational|Complex, but is String
## reduce, inject
`Array.reduce(x, Any) { }`
`Array.inject(x, Any) { }`
Starting from `init`, passes the accumulated value and each element (in that order) to the block, takes its value as the next accumulated value, and returns the last. Unlike Ruby's, `init` is required (leaving it out is a static argument-count error). On an empty Array the result is `init`. Ruby's Symbol form (`inject(:+)`) does not exist. The result type is the union of `init`'s type and the block's result type.
```ruby
p(Array.reduce(Any[1, 2, 3], 0) { |acc, x| acc + x }) # => 6
p(Array.inject(Any[1, 2, 3], 1) { |acc, x| acc * x }) # => 6
p(Array.reduce(Any[], 0) { |acc, x| acc + x }) # => 0
p(Array.reduce(Any["a", "b"], "") { |acc, x| acc + x }) # => "ab"
```ruby error Array.reduce(Any[1, 2]) { |acc, x| acc + x } # !> wrong number of arguments for Array.reduce (given 1, expected 2)
## min, max
`Array.min(x)`
`Array.max(x)`
The smallest or largest element. On an empty Array nil (the nil of a miss, reported at level 3 only). The elements must be comparable with each other: a pair of incomparable types in the element type (an Integer and a String, say) is a `type` problem statically (`elements compared in order may be (Integer, String), which cannot be compared`), and an element that may be nil a `nil` problem. A comparison failure the checker cannot see (`Float.NAN`, say) is an `ArgumentError` at run time. Integers and Floats compare with each other. class instances compare with their type's `<=>` (`include Comparable`). Ruby's `min(n)` and block forms do not exist.
```ruby
xs = Any[3, 1, 4, 1, 5]
p(Array.min(xs)) # => 1
p(Array.max(xs)) # => 5
p(Array.max(Any[2, 1.5])) # => 2
p(Array.min(Any[])) # => nil
```ruby error Array.min(Any[1, “a”]) # !> Array.min: elements compared in order may be (Integer, String), which cannot be compared
```ruby error
Array.max(Any[1.0, Float.NAN]) # !> ArgumentError: Array.max: cannot compare elements of types Float
23.40 minmax
Array.minmax(x)
The two-element Tuple [min, max]. On an empty Array [nil, nil], and each position’s type is the element type joined with nil (the nil of a miss, like min’s and max’s: reported at level 3 only). The comparison rules are those of min and max: incomparable types are a type problem statically, a failure at run time an ArgumentError.
p(Array.minmax(Any[3, 1, 4])) # => [1, 4]
p(Array.minmax(Any[])) # => [nil, nil]
lo, hi = Array.minmax(Any[3, 1, 4])
p(lo + hi) # => 5
23.41 min_by, max_by
Array.min_by(x) { }
Array.max_by(x) { }
The element for which the block’s value is smallest or largest. On an empty Array nil (the nil of a miss, reported at level 3 only, like min’s and max’s). The block’s values must be comparable with each other: incomparable types are a type problem statically, a failure the checker cannot see an ArgumentError at run time.
ws = Any["bb", "a", "ccc"]
p(Array.min_by(ws) { |s| String.size(s) }) # => "a"
p(Array.max_by(ws) { |s| String.size(s) }) # => "ccc"
p(Array.min_by(Any[]) { |s| s }) # => nil
```ruby error Array.min_by(Any[1, “a”]) { |x| x } # !> Array.min_by: elements compared in order may be (Integer, String), which cannot be compared
## minmax_by
`Array.minmax_by(x) { }`
The two-element Tuple of the element with the smallest block value and the one with the largest. On an empty Array `[nil, nil]` (each position the nil of a miss, reported at level 3 only). The comparison rules are those of `min_by` and `max_by`.
```ruby
lo, hi = Array.minmax_by(Any["bb", "a", "ccc"]) { |s| String.size(s) }
p([lo, hi]) # => ["a", "ccc"]
23.42 tally
Array.tally(x)
A Hash from each element to the number of times it occurs (an Integer), in order of first appearance. The elements must be values a Hash key can be (TypeError).
p(Array.tally(Any["a", "b", "a"])) # => {"a" => 2, "b" => 1}
23.43 sort, sort!
Array.sort(x)
Array.sort!(x)
A new Array of the elements in ascending order (sort), or the subject sorted in place and returned (sort!). Ruby’s comparison block is not accepted (a static error); for another order use sort_by. The elements must be comparable with each other: a pair of incomparable types in the element type is a type problem statically, and an element that may be nil a nil problem (compact removes the nils). A comparison failure the checker cannot see (Float.NAN, say) is an ArgumentError at run time. Integers and Floats compare with each other, Tuples element by element, class instances with their type’s <=>.
xs = Any[3, 1, 2]
p(Array.sort(xs)) # => [1, 2, 3]
p(xs) # => [3, 1, 2]
p(Array.sort!(xs)) # => [1, 2, 3]
p(xs) # => [1, 2, 3]
p(Array.sort(Any[[2, "a"], [1, "b"]])) # => [[1, "b"], [2, "a"]]
```ruby error Array.sort(Any[1, 2]) { |a, b| b <=> a } # !> Array.sort does not take a block
```ruby error
Array.sort(Any[1, "a"]) # !> Array.sort: elements compared in order may be (Integer, String), which cannot be compared
```ruby error Array.sort(Any[1, nil]) # !> Array.sort: argument elements may be nil
```ruby error
Array.sort(Any[1.0, Float.NAN]) # !> ArgumentError: Array.sort: cannot compare elements of types Float
23.44 sort_by, sort_by!
Array.sort_by(x) { }
Array.sort_by!(x) { }
A new Array of the elements in ascending order of the block’s values (the keys), or the subject sorted in place and returned (sort_by!). The keys must be comparable with each other: incomparable types are a type problem statically, a key that may be nil a nil problem, and a failure the checker cannot see an ArgumentError at run time. A descending order is a negated key; several keys are a Tuple [k1, k2] (Tuples compare in dictionary order).
ws = Any["bb", "a", "ccc"]
p(Array.sort_by(ws) { |s| -String.size(s) }) # => ["ccc", "bb", "a"]
p(Array.sort_by(Any["bb", "a", "c"]) { |s| [String.size(s), s] }) # => ["a", "c", "bb"]
xs = Any[3, 1, 2]
p(Array.sort_by!(xs) { |x| -x }) # => [3, 2, 1]
p(xs) # => [3, 2, 1]
23.45 reverse, reverse!
Array.reverse(x)
Array.reverse!(x)
A new Array of the elements in reverse order (reverse), or the subject reversed in place and returned (reverse!).
xs = Any[1, 2, 3]
p(Array.reverse(xs)) # => [3, 2, 1]
p(xs) # => [1, 2, 3]
p(Array.reverse!(xs)) # => [3, 2, 1]
p(xs) # => [3, 2, 1]
23.46 rotate, rotate!
Array.rotate(x, [Integer])
Array.rotate!(x, [Integer])
A new Array with the first n elements (default 1) moved to the end (rotate), or the subject rotated in place and returned (rotate!). A negative n rotates the other way.
xs = Any[1, 2, 3]
p(Array.rotate(xs)) # => [2, 3, 1]
p(Array.rotate(xs, 2)) # => [3, 1, 2]
p(Array.rotate(xs, -1)) # => [3, 1, 2]
p(Array.rotate!(xs)) # => [2, 3, 1]
p(xs) # => [2, 3, 1]
23.47 shuffle, shuffle!
Array.shuffle(x)
Array.shuffle!(x)
A new Array of the elements in random order (shuffle), or the subject shuffled in place and returned (shuffle!).
xs = Any[1, 2, 3]
p(Array.size(Array.shuffle(xs))) # => 3
p(Array.sort(Array.shuffle!(xs))) # => [1, 2, 3]
23.48 sample
Array.sample(x)
One element chosen at random. On an empty Array nil (the nil of a miss, reported at level 3 only). There is no Ruby sample(n).
p(Array.sample(Any[7])) # => 7
p(Array.sample(Any[])) # => nil
23.49 uniq, uniq!
Array.uniq(x)
Array.uniq!(x)
A new Array without the duplicate elements (== to an earlier one; the first is kept), or the subject with them removed in place and returned (uniq!). uniq! returns nil when nothing was removed (as Ruby’s), and using that result unchecked is a level 2 nil problem. Ruby’s block form does not exist.
p(Array.uniq(Any[1, 2, 1, 3, 2])) # => [1, 2, 3]
xs = Any[1, 1]
p(Array.uniq!(xs)) # => [1]
p(Array.uniq!(xs)) # => nil
```ruby error p(Array.size(Array.uniq!(Any[1, 1]))) # !> Array.size: argument 1 may be nil
## compact, compact!
`Array.compact(x)`
`Array.compact!(x)`
A new Array without the nil elements (`compact`; nil is removed from the element type), or the subject with them removed in place and returned (`compact!`). `compact!` returns nil when there was no nil, and using that result unchecked is a level 2 `nil` problem.
```ruby
p(Array.compact(Any[1, nil, 2])) # => [1, 2]
xs = Any[1, nil]
p(Array.compact!(xs)) # => [1]
p(Array.compact!(xs)) # => nil
23.50 flatten, flatten!
Array.flatten(x)
Array.flatten!(x)
A new Array with every nested Array expanded into its elements (flatten), or the subject flattened in place and returned (flatten!). Only Arrays are expanded: a Tuple is one value and stays whole (Ruby’s flatten would open it, so a Tuple element is reported statically as a type problem; to open Tuples, write Array.flat_map(xs) { |t| Tuple.to_a(t) }). flatten! returns nil when there was nothing nested, and using that result unchecked is a level 2 nil problem. Ruby’s depth argument does not exist.
p(Array.flatten(Any[1, Any[2, Any[3]], 4])) # => [1, 2, 3, 4]
xs = Any[1, Any[2]]
p(Array.flatten!(xs)) # => [1, 2]
p(Array.flatten!(xs)) # => nil
```ruby error p(Array.flatten(Any[[1, 2], [3, 4]])) # !> which flatten leaves whole
## zip
`Array.zip(x, *Array)`
A new Array of Tuples pairing each element of the subject with the elements at the same position in the other Arrays. Its length is the subject's: a shorter other Array is padded with nil (that position's type is the element type joined with nil), and a longer one's extra elements are dropped. Unlike Ruby's, the groups are Tuples, not Arrays.
```ruby
p(Array.zip(Any[1, 2, 3], Any["a", "b"])) # => [[1, "a"], [2, "b"], [3, nil]]
p(Array.zip(Any[1, 2], Any["a", "b"], Any[:x, :y])) # => [[1, "a", :x], [2, "b", :y]]
23.51 product
Array.product(x, Array)
A new Array of all combinations [a, b] (Tuples) of an element of the subject and an element of the other Array. Unlike Ruby’s, it takes exactly one other Array, and the groups are Tuples.
p(Array.product(Any[1, 2], Any["a", "b"])) # => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]
p(Array.product(Any[1], Any[])) # => []
23.52 transpose
Array.transpose(x)
Takes an Array of Arrays as a matrix and returns a new Array with rows and columns exchanged. A row may be an Array or a Tuple (a sequence of Tuples such as zip’s result transposes too); the result’s rows are Arrays. The rows must all have the same length: a different length is an IndexError. An element that is neither an Array nor a Tuple is a TypeError.
p(Array.transpose(Any[Any[1, 2], Any[3, 4]])) # => [[1, 3], [2, 4]]
p(Array.transpose(Array.zip(Any[1, 2], Any["a", "b"]))) # => [[1, 2], ["a", "b"]]
```ruby error Array.transpose(Any[Any[1, 2], Any[3]]) # !> IndexError: Array.transpose: element size differs (1 should be 2)
## combination, repeated_combination
`Array.combination(x, Integer)`
`Array.repeated_combination(x, Integer)`
A new Array of the combinations of n elements, as Arrays: each element at most once (`combination`), or with repetition (`repeated_combination`). An Array, not Ruby's Enumerator. When n exceeds the length, `combination` gives `[]`.
```ruby
p(Array.combination(Any[1, 2, 3], 2)) # => [[1, 2], [1, 3], [2, 3]]
p(Array.repeated_combination(Any[1, 2], 2)) # => [[1, 1], [1, 2], [2, 2]]
p(Array.combination(Any[1, 2], 5)) # => []
23.53 permutation, repeated_permutation
Array.permutation(x, [Integer])
Array.repeated_permutation(x, Integer)
A new Array of the permutations of n elements, as Arrays. permutation(a) without n uses all elements. repeated_permutation may pick the same element again (n is required).
p(Array.permutation(Any[1, 2, 3], 2)) # => [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
p(Array.permutation(Any[1, 2])) # => [[1, 2], [2, 1]]
p(Array.repeated_permutation(Any[1, 2], 2)) # => [[1, 1], [1, 2], [2, 1], [2, 2]]
23.54 union, intersection, difference
Array.union(x, *Array)
Array.intersection(x, *Array)
Array.difference(x, *Array)
New Arrays treating the Arrays as sets (in the subject’s order, comparing with ==). union is the elements of the subject and all the other Arrays without duplicates (the element type is the union of all); intersection the elements that are in every other Array, without duplicates; difference the elements that are in none of the other Arrays (duplicates kept). Any number of other Arrays may be given, even none.
a = Any[1, 2, 3]
b = Any[2, 3, 4]
p(Array.union(a, b)) # => [1, 2, 3, 4]
p(Array.union(Any[1, 1])) # => [1]
p(Array.intersection(a, b)) # => [2, 3]
p(Array.intersection(a, b, Any[3])) # => [3]
p(Array.difference(a, b)) # => [1]
p(Array.difference(Any[1, 2, 2, 3], Any[2], Any[3])) # => [1]
23.55 intersect?
Array.intersect?(x, Array)
True when the two Arrays have at least one element in common.
p(Array.intersect?(Any[1, 2], Any[2, 3])) # => true
p(Array.intersect?(Any[1, 2], Any[9])) # => false
23.56 join
Array.join(x, [String])
A String of the elements converted with to_s, separated by sep (default ""). to_s follows the rules of puts: nil is empty, Arrays and Tuples take their inspect form, and a class instance uses its type’s to_s (Values and types).
p(Array.join(Any[1, 2, 3], ", ")) # => "1, 2, 3"
p(Array.join(Any[1, "a", nil, :s, [1, 2]])) # => "1as[1, 2]"
p(Array.join(Any[])) # => ""
23.57 to_h
Array.to_h(x)
A Hash from a sequence of [key, value] Tuples. Every element must be a two-element Tuple (not an Array); any other value is a TypeError. When a key occurs more than once the later one stays. The keys must be values a Hash key can be. Ruby’s block form does not exist.
p(Array.to_h(Any[[:a, 1], [:b, 2]])) # => {a: 1, b: 2}
p(Array.to_h(Any[[1, "a"], [1, "b"]])) # => {1 => "b"}
p(Array.to_h(Any[])) # => {}
```ruby error Array.to_h(Any[1, 2]) # !> TypeError: Array.to_h: Array.to_h needs [key, value] Tuples, got Integer
## to_set
`Array.to_set(x)`
A new Set of the elements (duplicates become one). The elements must be values a Set element can be (`TypeError`).
```ruby
p(Array.to_set(Any[1, 2, 1])) # => Set[1, 2]
23.58 pack
Array.pack(x, String)
Ruby’s Array#pack: the elements as a byte String according to the format fmt. An invalid format is an ArgumentError; an element that does not fit the format (a String for "C*", say) a TypeError. The reverse is String.unpack.
p(Array.pack(Any[65, 66], "C*")) # => "AB"
p(Array.pack(Any[1, 2], "n*")) # => "\x00\x01\x00\x02"
```ruby error Array.pack(Any[“a”], “C”) # !> TypeError: Array.pack: no implicit conversion of String into Integer
## +, -, *
`Array.+(x, Any)`
`Array.-(x, Any)`
`Array.*(x, Any)`
`a + b` is a new Array of both Arrays' elements (the element type is the union; `concat` does it in place). `a - b` is a new Array without every element that is `==` to one in `b` (the same as `difference`). `a * n` is a new Array of `a` repeated n times; a negative n is an `ArgumentError`. The right operand must be an Array for `+` and `-` and an Integer for `*`; a Tuple or a String (Ruby's `a * ","` is `join`) is a `type` problem statically.
```ruby
a = Any[1, 2, 3]
b = Any[2, 3, 4]
p(a + b) # => [1, 2, 3, 2, 3, 4]
p(a - b) # => [1]
p(Any[1, 2, 2] - Any[2]) # => [1]
p(a * 2) # => [1, 2, 3, 1, 2, 3]
p(Array.+(a, Any[9])) # => [1, 2, 3, 9]
p(Array.*(a, 0)) # => []
```ruby error p(Any[1] + [2]) # !> which the left operand’s type does not support
## ==, !=
`Array.==(x, Any)`
`Array.!=(x, Any)`
True when the two Arrays have the same length and the elements at each position are `==` (`!=` is the negation). A right operand that is not an Array (a Tuple, nil) is never equal. `[1, 2]` is a Tuple and so is not equal to `Any[1, 2]`.
```ruby
a = Any[1, 2, 3]
p(a == Any[1, 2, 3]) # => true
p(a != Any[1, 2]) # => true
p(a == [1, 2, 3]) # => false
p(a == nil) # => false
p(Array.==(a, a)) # => true
23.59 <, <=, >, >=
Array.<(x, Any)
Array.<=(x, Any)
Array.>(x, Any)
Array.>=(x, Any)
Compare two Arrays in dictionary order: elements are compared from the front, the first differing position decides, and when all are equal the shorter Array is smaller. The elements must be of comparable types; a pair that is not is a type problem statically (which cannot be compared) and an ArgumentError at run time. The right operand must be an Array (a Tuple is a type problem).
p(Any[1, 2] < Any[1, 3]) # => true
p(Any[1, 2] < Any[1, 2, 0]) # => true
p(Any[2] > Any[1, 9]) # => true
p(Any[1, 2] <= Any[1, 2]) # => true
p(Array.>=(Any[1], Any[1])) # => true
```ruby error p(Any[1, “a”] < Any[1, 2]) # !> which cannot be compared
## <=>
`Array.<=>(x, Any)`
The result of the dictionary-order comparison as -1, 0, or 1. The rules are those of `<`. An incomparable pair is a `type` problem statically; run without the check (`--strict=0`) it gives nil (where `<` and the others raise `ArgumentError`).
```ruby
a = Any[1, 2, 3]
p(a <=> Any[1, 2, 4]) # => -1
p(a <=> a) # => 0
p(Array.<=>(a, Any[1])) # => 1
24. Enum
Enum is Ruby’s Enumerable under a short name, a module that the prelude (sakelib/prelude.sake, read before every program) defines in Sake. Its functions, Enum.map(x) { } and the others, are mixin functions: they dispatch on the type of the first argument (Program structure, “Calling a module’s functions”). Array, Hash, Set and Range include Enum and answer with their own operations: Enum.map(xs) is Array.map(xs) when xs is an Array, and Enum.each(h) { |k, v| } is Hash.each. Nothing is copied into a built-in type. A value of any other type (an Integer, a Tuple, a String) is a type problem before running and a TypeError while running. Tuple does not include Enum: its length and position types are fixed, so it is indexed and destructured instead.
A class of your own joins with include Enum and def each(c) = ... yield(v) .... Every other function of Enum is written over each, so they are copied into the class (include) and C.map(c) { }, C.count(c) and Enum.first(c) all work; the class’s own definition of one of them wins.
class Deck
include Enum
attr_reader cards
def each(d) = Array.each(@cards) { |c| yield(c) }
end
d = Deck.new(Any["A", "K", "Q", "J"])
p(Deck.map(d) { |c| String.downcase(c) }) # => ["a", "k", "q", "j"]
p(Enum.count(d)) # => 4
p(Enum.select(Any[3, 1, 2]) { |v| v > 1 }) # => [3, 2]
```ruby error p(Enum.map(5) { |v| v }) # !> Enum.map dispatches on its first argument, which is Integer; the types that include Enum are Array, Hash, Set, Range
The functions whose result may be nil (`find`, `first`, `min_by`, `max_by`) return `nil | T` in a class's copy, and using that unchecked is reported at level 2; dispatched to a built-in type, they follow that type's operation (the nil of a miss of `Array.first` is level 3). The sections below describe Enum's own definitions; on a built-in type the operation of the same name runs ([Array](Array.md), [Hash](Hash.md), [Set](Set.md), [Range](Range.md)).
## each
`Enum.each(x)`
The required function. A class that includes Enum defines `def each(c)` and calls `yield(v)` for each element (two values are fine, as Hash does). `Enum.each(x) { }` dispatches to the `each` of x's type. A class that does not define it is a `type` problem wherever a call can reach it, and `NotImplementedError` while running.
```ruby
Enum.each(Hash.of(a: 1, b: 2)) { |k, v| puts("#{k}: #{v}") }
# => a: 1
# => b: 2
24.1 map
Enum.map(x) { }
An Array of the block’s value for each element.
p(Enum.map(1..3) { |v| v * v }) # => [1, 4, 9]
p(Enum.map(Set.of(1, 2)) { |v| -v }) # => [-1, -2]
24.2 select, filter
Enum.select(x) { }
Enum.filter(x) { }
An Array of the elements for which the block is true.
p(Enum.select(Any[1, 2, 3, 4]) { |v| v % 2 == 0 }) # => [2, 4]
p(Enum.filter(1..6) { |v| v > 4 }) # => [5, 6]
24.3 reject
Enum.reject(x) { }
An Array of the elements for which the block is false.
p(Enum.reject(Any[1, 2, 3, 4]) { |v| v % 2 == 0 }) # => [1, 3]
24.4 filter_map
Enum.filter_map(x) { }
Applies the block to each element and keeps the true results (nil and false are dropped).
p(Enum.filter_map(Any["1", "x", "3"]) { |s| String.to_i(s) if String.match?(s, /\A\d+\z/) }) # => [1, 3]
24.5 flat_map
Enum.flat_map(x) { }
The Arrays (or Tuples) the block returns, joined one level deep.
p(Enum.flat_map(Any[1, 2]) { |v| Any[v, v * 10] }) # => [1, 10, 2, 20]
24.6 find, detect
Enum.find(x) { }
Enum.detect(x) { }
The first element for which the block is true, or nil when there is none (level 2 in a class’s copy, and for Array.find and its kin).
p(Enum.find(Any[3, 8, 5]) { |v| v > 4 }) # => 8
p(Enum.detect(1..3) { |v| v > 9 }) # => nil
24.7 reduce, inject
Enum.reduce(x, init) { }
Enum.inject(x, init) { }
Starting from init, calls the block as yield(acc, v) for each element and returns the last accumulated value. Unlike Ruby’s, the initial value cannot be left out: the result type would not follow from the element type alone (as for Array’s reduce).
p(Enum.reduce(Any[1, 2, 3], 0) { |acc, v| acc + v }) # => 6
p(Enum.inject(1..4, 1) { |acc, v| acc * v }) # => 24
24.8 sum
Enum.sum(x, [init]) [{ }]
The sum of the elements, or of the block’s values, starting from init (default 0). On a built-in type the elements must be numbers or values of a type that includes Arithmetic (Array).
p(Enum.sum(1..4)) # => 10
p(Enum.sum(Any[1, 2, 3]) { |v| v * 10 }) # => 60
24.9 count
Enum.count(x) [{ }]
The number of elements, or of those for which the block is true.
p(Enum.count(Set.of(1, 2, 3))) # => 3
p(Enum.count(1..10) { |v| v % 3 == 0 }) # => 3
24.10 include?
Enum.include?(x, v)
True when some element is == to v.
p(Enum.include?(1..5, 3)) # => true
p(Enum.include?(Any["a"], "b")) # => false
24.11 any?, all?, none?, one?
Enum.any?(x) [{ }]
Enum.all?(x) [{ }]
Enum.none?(x) [{ }]
Enum.one?(x) [{ }]
True when the block is true for at least one element / for all / for none / for exactly one. Without a block, the elements’ own truth counts.
xs = Any[1, 2, 3]
p(Enum.any?(xs) { |v| v > 2 }) # => true
p(Enum.all?(xs) { |v| v > 2 }) # => false
p(Enum.none?(xs) { |v| v > 5 }) # => true
p(Enum.one?(xs) { |v| v > 2 }) # => true
24.12 min_by, max_by
Enum.min_by(x) { }
Enum.max_by(x) { }
The element with the smallest / largest block value; nil when empty. The block’s values must be comparable with each other (the checker sees to it statically).
words = Any["pear", "fig", "banana"]
p(Enum.min_by(words) { |w| String.length(w) }) # => "fig"
p(Enum.max_by(words) { |w| String.length(w) }) # => "banana"
24.13 sort_by
Enum.sort_by(x) { }
An Array of the elements in ascending order of the block’s value.
p(Enum.sort_by(Any["bb", "a", "ccc"]) { |s| -String.length(s) }) # => ["ccc", "bb", "a"]
24.14 to_a
Enum.to_a(x)
A new Array of the elements.
p(Enum.to_a(1..3)) # => [1, 2, 3]
p(Enum.to_a(Hash.of(a: 1))) # => [[:a, 1]]
24.15 first
Enum.first(x, [n])
The first element (nil when empty), or with n an Array of the first n.
p(Enum.first(Any[7, 8, 9])) # => 7
p(Enum.first(1..10, 3)) # => [1, 2, 3]
24.16 take, drop
Enum.take(x, n)
Enum.drop(x, n)
An Array of the first n elements / of all but the first n.
p(Enum.take(1..5, 2)) # => [1, 2]
p(Enum.drop(1..5, 2)) # => [3, 4, 5]
24.17 group_by
Enum.group_by(x) { }
A Hash from each block value to the Array of the elements that gave it.
p(Enum.group_by(1..6) { |v| v % 3 }) # => {1 => [1, 4], 2 => [2, 5], 0 => [3, 6]}
24.18 partition
Enum.partition(x) { }
A Tuple [yes, no]: the elements for which the block is true, and the others.
yes, no = Enum.partition(1..6) { |v| v > 3 }
p(yes) # => [4, 5, 6]
p(no) # => [1, 2, 3]
24.19 each_with_object
Enum.each_with_object(x, memo) { }
Calls the block as yield(v, memo) for each element and returns memo.
p(Enum.each_with_object(Any[1, 2], Any[]) { |v, m| Array.unshift(m, v) }) # => [2, 1]
24.20 compact
Enum.compact(x)
An Array of the elements that are not nil.
p(Enum.compact(Any[1, nil, 2])) # => [1, 2]
25. Tuple
A Tuple is a sequence of fixed length with a type per position. The literal [1, "a"] makes a Tuple (Ruby’s [1, "a"] is an Array; in Sake the Array is Any[1, "a"], see Values and types). The number of elements and the type at each position are part of the value’s type, so the checker sees index ranges and position types statically. A Tuple cannot grow or shrink; use an Array for a sequence of varying length.
Multiple assignment a, b = t and the Tuple pattern in [Integer, String] take a Tuple apart. A function’s return a, b returns a Tuple.
The operators on Tuples are ==, !=, <, <=, >, >=, <=> (two Tuples compare in dictionary order) and the index t[i], t[i] = v. The entries Tuple.<(x, y) and so on below are the function forms of those operators (Operators and indexing).
25.1 Tuple[]
Tuple[*Any]
Makes an Array of Tuples (not a Tuple). It is a typed Array like Integer[] and String[]: every element must be a Tuple, and every write (Array.push, …) is checked. An element that is not a Tuple is a type problem statically and a TypeError at run time. The empty Array of Tuples is Tuple[].
pairs = Tuple[[1, "a"], [2, "b"]]
Array.push(pairs, [3, "c"])
p(pairs) # => [[1, "a"], [2, "b"], [3, "c"]]
p(Array.size(Tuple[])) # => 0
```ruby error Array.push(Tuple[], 1) # !> Array.push: an element must be Tuple, but is Integer
## []
`Tuple.[](x, Any)`
The function form of `t[i]`. Returns the element at position `i` (an Integer; a negative one counts from the end). The result has the type of that position. Because a Tuple's length is part of its type, an index outside it does not give nil as an Array's does: it is a `type` problem statically (`the index is outside the Tuple`) and an `IndexError` at run time. A Range is not accepted as the index (`TypeError`).
```ruby
t = [1, "a", 2.5]
p(t[0]) # => 1
p(t[-1]) # => 2.5
p(Tuple.[](t, 1)) # => "a"
```ruby error t = [1, 2] p(t[5]) # !> the index is outside the Tuple
## []=
`Tuple.[]=(x, Any, Any)`
The function form of `t[i] = v`. Replaces the element at position `i` with `v` and returns `v`. A value whose type differs from that position's type is a `type` problem statically and a `TypeError` at run time. An index outside the Tuple is an `IndexError`.
```ruby
t = [1, "a"]
t[0] = 10
Tuple.[]=(t, 1, "b")
p(t) # => [10, "b"]
```ruby error t = [1, 2] t[0] = “x” # !> the value must be Integer, but is String
## length, size
`Tuple.length(x)`
`Tuple.size(x)`
The number of elements (an Integer). A Tuple's length follows from its type, so to the checker it is a constant.
```ruby
p(Tuple.size([1, "a", 2.5])) # => 3
p(Tuple.length([])) # => 0
25.2 max, min
Tuple.max(x)
Tuple.min(x)
The largest or smallest element (Ruby’s [a, b].max). Since each position’s type is known, whether the elements are comparable with each other is checked statically: an incomparable pair (an Integer and a String, say) is a type problem (elements compared in order may be (Integer, String), which cannot be compared), and a position that may be nil a nil problem. A comparison failure the checker cannot see (Float.NAN, say) is an ArgumentError at run time. Integers and Floats compare with each other. The empty Tuple [] gives nil. class instances compare with their type’s <=> (include Comparable). The result’s type is the join of the positions’ types; for a non-empty Tuple it is never nil.
p(Tuple.max([3, 1, 2])) # => 3
p(Tuple.min([3, 1, 2])) # => 1
p(Tuple.max([2, 1.5])) # => 2
p(Tuple.max([3, 1]) + 1) # => 4
p(Tuple.max([])) # => nil
```ruby error p(Tuple.max([1, “a”])) # !> Tuple.max: elements compared in order may be (Integer, String), which cannot be compared
```ruby error
p(Tuple.max([1.0, Float.NAN])) # !> ArgumentError: Tuple.max: cannot compare elements of types Float
25.3 minmax
Tuple.minmax(x)
The two-element Tuple [min, max]. The comparison rules are those of min and max (an incomparable pair is a type problem statically).
lo, hi = Tuple.minmax([3, 1, 2])
p([lo, hi]) # => [1, 3]
25.4 to_a
Tuple.to_a(x)
A new Array with the same elements. It is independent of the Tuple, and the Array can grow. Its element type is the union of the Tuple’s position types.
t = [1, "a"]
a = Tuple.to_a(t)
Array.push(a, :x)
p(a) # => [1, "a", :x]
p(t) # => [1, "a"]
25.5 ==, !=
Tuple.==(x, Any)
Tuple.!=(x, Any)
True when the two Tuples have the same length and the elements at each position are == (!= is the negation). A right operand that is not a Tuple (an Array, nil) is never equal. Any[1, 2] and [1, 2] differ in type and are not equal.
p([1, 2] == [1, 2]) # => true
p([1, 2] != [1, 2, 3]) # => true
p([1, 2] == Any[1, 2]) # => false
25.6 <, <=, >, >=
Tuple.<(x, Any)
Tuple.<=(x, Any)
Tuple.>(x, Any)
Tuple.>=(x, Any)
Compare two Tuples in dictionary order: elements are compared from the front, the first differing position decides, and when all are equal the shorter Tuple is smaller. Each pair of positions must be of comparable types; a pair that is not is a type problem statically (elements compared in order may be (Integer, String)). The right operand must be a Tuple.
p([1, 2] < [1, 3]) # => true
p([2] > [1, 9]) # => true
p([1] < [1, 0]) # => true
p(Tuple.<=([1, 2], [1, 2])) # => true
```ruby error p([1, 2] < [1, “a”]) # !> which cannot be compared
## <=>
`Tuple.<=>(x, Any)`
The result of the dictionary-order comparison as -1, 0, or 1. The rules are those of `<`. A Tuple returned as the key of `Array.sort_by` sorts in this order.
```ruby
p([1, 2] <=> [1, 2]) # => 0
p([1, 2] <=> [1, 2, 3]) # => -1
p(Array.sort_by(Any["bb", "a", "c"]) { |s| [String.size(s), s] }) # => ["a", "c", "bb"]
26. Range
A Range is a pair of ends with a flag that says whether the end is included: 1..5 includes 5, 1...5 does not. The ends are Integers, Floats, Strings, or nil (Values and types): 1.. is an endless Range, ..5 a beginless one. Write an endless Range in parentheses at the end of a line, r = (3..), because 3.. followed by a line break continues onto the next line, as it does in Ruby. Two ends of different types (1.."a") are an ArgumentError at run time. A Range is a value: no operation changes it, and every result is a new value.
The checker keeps the type of the ends with the Range (Range[Integer]), and that type decides which operations apply:
- Walking the values (
each,map,to_a,select,find,count, …) needs a Range that starts with an Integer or a String."a".."e"walks with Ruby’sString#succ. A Range of another type, such as1.0..2.0, is atypeproblem statically (the Range's first value must be Integer|String, but is Float) and aTypeErrorat run time. step,sum, andsizeneed an Integer start; a String Range is atypeproblem for them.- Finite is required where the whole Range is used (
to_a,map,sum,last,min,max, …): on an endless Range those raiseRangeError(cannot do this on an endless Range 1..). Operations that can stop early (each,find,take,first(r, n),each_slice,bsearch) accept an endless Range;eachon one runs untilbreak. cover?,overlap?,begin,end, andexclude_end?compare with the ends only and take any Range, Float and endless ones included.include?andmember?do the same on a Range of numbers, but walk a String Range, as Ruby’s do.- A Range whose beginning is above its end (
5..1) has no values: walking it does nothing,to_ais[],minis nil;first,last,begin, andendstill give the ends.
The operators on Ranges are == and != (two Ranges are equal when their ends and exclude_end? are). Indexing with a Range (a[1..3], s[0...2]) is an operation of Array and String (Operators and indexing). A Range cannot be a Hash key or a Set element (TypeError). Results that may be nil (first, last, min, max, begin, end, find, find_index, bsearch, min_by, max_by) are typed T | nil: --strict (level 2) reports using them unchecked. Only the elements of the Tuples of minmax and minmax_by are the “nil of a miss” (as a, b = tuple), which level 3 alone reports.
Many operations are Ruby’s Enumerable methods. Unlike Ruby, every block is required where the signature shows { } (except count and sum, whose block is optional), reduce and inject need the initial value, and the block of each, map, and the others takes exactly one parameter (each_with_index, each_with_object, chunk_while, slice_when take two); a block with the wrong number of parameters is an ArgumentError at run time.
26.1 begin, end
Range.begin(x)
Range.end(x)
The two ends as written, as Ruby’s r.begin and r.end. end is the written end even when it is excluded (Range.end(1...5) is 5, while Range.max(1...5) is 4). The result is T | nil: end of an endless Range and begin of a beginless one are nil, so --strict asks for a check before the result is used in arithmetic.
p(Range.begin(1..5)) # => 1
p(Range.end(1...5)) # => 5
p(Range.end(1..)) # => nil
p(Range.begin("a".."z")) # => "a"
```ruby error x = Range.begin(1..5) p(x + 1) # !> the operands may be nil
## exclude_end?
`Range.exclude_end?(x)`
True for a Range written with three dots (`1...5`), false for two (`1..5`, `1..`).
```ruby
p(Range.exclude_end?(1..5)) # => false
p(Range.exclude_end?(1...5)) # => true
p(Range.exclude_end?(1..)) # => false
26.2 ==, !=
Range.==(x, Any)
Range.!=(x, Any)
Two Ranges are equal when both ends are == and exclude_end? agrees (!= is the negation). 1..5 and 1...5 differ. A right operand that is not a Range (an Integer, nil) is never equal, with the function form as with the operator: (1..5) == 5 and Range.==(1..5, 5) are both false.
p((1..5) == (1..5)) # => true
p((1..5) == (1...5)) # => false
p((1..5) != (1...6)) # => true
p((1..5) == 5) # => false
p(Range.==(1..5, 1..5)) # => true
p(Range.==(1..5, 5)) # => false
p(Range.!=(1..5, nil)) # => true
26.3 include?, cover?, member?
Range.include?(x, Any)
Range.cover?(x, Any)
Range.member?(x, Any)
True when the value is in the Range. cover? is Ruby’s cover?: it compares with the ends only (begin <= v <= end, or < end for ...) and never walks the Range, so it works on endless, beginless, and Float Ranges, and a Float is found in an Integer Range. include? and member? are Ruby’s include?: on a Range of numbers they are the same as cover?, but a String Range is walked with String#succ, so "a".."z" includes "m" but not "mm" (which cover? accepts, since "a" <= "mm" <= "z"), and an endless or beginless String Range is a TypeError (cannot determine inclusion in beginless/endless ranges). A value of another type than the ends ("a" in 1..5, nil) gives false. A Range as the value: cover? asks whether it lies entirely inside, as Ruby’s; include? and member? answer false.
p(Range.include?(1..5, 5)) # => true
p(Range.include?(1...5, 5)) # => false
p(Range.cover?(1..5, 2.5)) # => true
p(Range.member?(1..5, 0)) # => false
p(Range.include?(1.., 100)) # => true
p(Range.include?("a".."z", "m")) # => true
p(Range.include?("a".."z", "mm")) # => false
p(Range.cover?("a".."z", "mm")) # => true
p(Range.include?(1..5, "a")) # => false
p(Range.cover?(1..5, 2..3)) # => true
p(Range.include?(1..5, 2..3)) # => false
```ruby error p(Range.include?(“a”.., “b”)) # !> TypeError: Range.include?: cannot determine inclusion in beginless/endless ranges
## overlap?
`Range.overlap?(x, Range)`
True when the two Ranges share at least one value, as Ruby's `overlap?`. An excluded end does not count (`1...5` and `5..8` do not overlap). Any ends are accepted; Ranges of different types do not overlap.
```ruby
p(Range.overlap?(1..5, 3..8)) # => true
p(Range.overlap?(1..5, 6..8)) # => false
p(Range.overlap?(1...5, 5..8)) # => false
p(Range.overlap?(1.., 100..200)) # => true
p(Range.overlap?(1.0..2.0, 1.5..3.0)) # => true
26.4 size
Range.size(x)
The number of Integers in the Range (an Integer). Only for an Integer Range: a String Range is a type problem (the Range's first value must be Integer, but is String), as is one that may be a Float (1..2.5). An endless Range is a RangeError. A Range with the beginning above the end has size 0. Use count for a String Range.
p(Range.size(1..5)) # => 5
p(Range.size(1...5)) # => 4
p(Range.size(5..1)) # => 0
```ruby error p(Range.size(1..)) # !> RangeError: Range.size: cannot do this on an endless Range 1..
## count
`Range.count(x) [{ }]`
Without a block, the number of values (an Integer), walking the Range; with a block, the number of values for which the block gives a truthy result. Works on Integer and String Ranges; needs a finite Range (`RangeError`). Unlike Ruby's `count(v)`, there is no form with a value: use `include?`.
```ruby
p(Range.count("a".."c")) # => 3
p(Range.count(1..5) { |i| Integer.odd?(i) }) # => 3
26.5 each
Range.each(x) { }
Calls the block with each value in turn and returns the Range. An Integer Range walks by 1; a String Range by String#succ ("az".."bc" gives "az", "ba", "bb", "bc"). An endless Range runs until a break; break v makes v the result. The block takes one parameter. A Float Range is a type problem.
r = Range.each(1..3) { |i| puts(i) } # => 1
# => 2
# => 3
p(r) # => 1..3
Range.each("a".."c") { |s| print(s) } # => abc
puts("")
x = Range.each(1..) { |i| break i * 10 if i > 2 }
p(x) # => 30
```ruby error Range.each(1.0..2.0) { |f| p(f) } # !> the Range’s first value must be Integer|String, but is Float
## each_with_index
`Range.each_with_index(x) { }`
Calls the block with each value and its position (0, 1, 2, ...) and returns the Range. The block takes two parameters. An endless Range needs a `break`.
```ruby
r = Range.each_with_index("a".."c") { |s, i| p([s, i]) } # => ["a", 0]
# => ["b", 1]
# => ["c", 2]
p(r) # => "a".."c"
26.6 each_entry
Range.each_entry(x) { }
The same as each: Ruby’s Enumerable#each_entry, kept for programs written against it. Returns the Range.
p(Range.each_entry(1..3) { |i| puts(i) }) # => 1
# => 2
# => 3
# => 1..3
26.7 reverse_each
Range.reverse_each(x) { }
Calls the block with each value from the end to the beginning and returns the Range. Needs a finite Range (RangeError).
p(Range.reverse_each(1..3) { |i| puts(i) }) # => 3
# => 2
# => 1
# => 1..3
26.8 step
Range.step(x, Integer) { }
Calls the block with the beginning, the beginning plus the step, and so on while the value lies in the Range, and returns the Range. Only for an Integer Range (type for a String or Float one). An endless Range needs a break. A negative step walks a descending Range (10..1 with -3 gives 10, 7, 4, 1) and gives nothing on an ascending one. A step of 0 is an ArgumentError (step can't be 0).
p(Range.step(1..10, 3) { |i| puts(i) }) # => 1
# => 4
# => 7
# => 10
# => 1..10
Range.step(1...9, 4) { |i| p(i) } # => 1
# => 5
Range.step(10..1, -3) { |i| p(i) } # => 10
# => 7
# => 4
# => 1
```ruby error Range.step(“a”..”z”, 2) { |s| p(s) } # !> the Range’s first value must be Integer, but is String
```ruby error
Range.step(1..5, 0) { |i| p(i) } # !> ArgumentError: Range.step: step can't be 0
26.9 each_slice
Range.each_slice(x, Integer) { }
Calls the block with consecutive Arrays of n values (the last one shorter when the values run out) and returns the Range. An endless Range needs a break. A size of 0 or less is an ArgumentError.
p(Range.each_slice(1..7, 3) { |a| p(a) }) # => [1, 2, 3]
# => [4, 5, 6]
# => [7]
# => 1..7
26.10 each_cons
Range.each_cons(x, Integer) { }
Calls the block with each window of n consecutive values as an Array and returns the Range. With fewer than n values the block is not called. A size of 0 or less is an ArgumentError.
p(Range.each_cons(1..4, 2) { |a| p(a) }) # => [1, 2]
# => [2, 3]
# => [3, 4]
# => 1..4
26.11 each_with_object
Range.each_with_object(x, Any) { }
Calls the block with each value and the given object, and returns that object. Use it to fill a container; as in Ruby, a block that returns a new value does not change the object (Range.each_with_object(1..3, 0) { |i, acc| acc + i } returns 0: use reduce). An endless Range needs a break.
a = Range.each_with_object(1..3, Any[]) { |i, acc| Array.push(acc, i * i) }
p(a) # => [1, 4, 9]
26.12 cycle
Range.cycle(x, Integer) { }
Walks the Range n times over and returns nil. Nothing happens for n of 0 or less. Needs a finite Range (RangeError). Unlike Ruby’s cycle without an argument, the count is required.
p(Range.cycle(1..2, 2) { |i| puts(i) }) # => 1
# => 2
# => 1
# => 2
# => nil
26.13 to_a, entries
Range.to_a(x)
Range.entries(x)
A new Array of all the values. Needs an Integer or String Range (type otherwise) and a finite one (RangeError). The element type of the Array is the type of the ends.
p(Range.to_a(1..5)) # => [1, 2, 3, 4, 5]
p(Range.to_a(1...5)) # => [1, 2, 3, 4]
p(Range.entries("a".."e")) # => ["a", "b", "c", "d", "e"]
p(Range.to_a(5..1)) # => []
```ruby error p(Range.to_a(1..)) # !> RangeError: Range.to_a: cannot do this on an endless Range 1..
## first
`Range.first(x, [Integer])`
Without a count: the beginning of the Range, whatever the Range contains (`Range.first(5..1)` is 5, `Range.first(1...1)` is 1, `Range.first(1.0..2.0)` is 1.0). The type is `T | nil` and `--strict` asks for a check before arithmetic; a beginless Range does not give nil but raises `RangeError` (`cannot get the first element of beginless range`). With a count n: a new Array of the first n values (fewer when the Range is shorter), walking the Range, so an Integer or String Range is needed (a Float Range is a `TypeError` at run time, `can't iterate from Float`); an endless Range is fine. A negative count is a `RangeError` (`negative array size (or size too big)`).
```ruby
p(Range.first(1..5)) # => 1
p(Range.first(1..5, 2)) # => [1, 2]
p(Range.first(1..5, 10)) # => [1, 2, 3, 4, 5]
p(Range.first(1.., 3)) # => [1, 2, 3]
p(Range.first("a".."c", 2)) # => ["a", "b"]
```ruby error p(Range.first(..5)) # !> RangeError: Range.first: cannot get the first element of beginless range
```ruby error
p(Range.first(1.0..2.0, 2)) # !> TypeError: Range.first: can't iterate from Float
26.14 last
Range.last(x, [Integer])
Without a count: the written end, as Range.end (so Range.last(1...5) is 5, as Ruby’s). With a count n: a new Array of the last n values, which excludes an excluded end (Range.last(1...5, 2) is [3, 4]). Needs a finite Range (RangeError on an endless one, even without a count). The result without a count is T | nil. A negative count is a RangeError.
p(Range.last(1..5)) # => 5
p(Range.last(1...5)) # => 5
p(Range.last(1..5, 2)) # => [4, 5]
p(Range.last(1...5, 2)) # => [3, 4]
p(Range.last("a".."c")) # => "c"
```ruby error p(Range.last(1..)) # !> RangeError: Range.last: cannot do this on an endless Range 1..
## take
`Range.take(x, Integer)`
A new Array of the first n values (all of them when there are fewer), as `first(r, n)`. An endless Range is fine. A negative n is an `ArgumentError`.
```ruby
p(Range.take(1..10, 3)) # => [1, 2, 3]
p(Range.take(1.., 3)) # => [1, 2, 3]
p(Range.take(1..3, 10)) # => [1, 2, 3]
26.15 drop
Range.drop(x, Integer)
A new Array of the values after the first n (empty when there are not more). Needs a finite Range (RangeError). A negative n is an ArgumentError.
p(Range.drop(1..5, 2)) # => [3, 4, 5]
p(Range.drop(1..5, 10)) # => []
26.16 take_while
Range.take_while(x) { }
A new Array of the values from the beginning up to, not including, the first for which the block gives a falsy result. An endless Range is fine as long as the block eventually says no.
p(Range.take_while(1..10) { |i| i < 4 }) # => [1, 2, 3]
p(Range.take_while(1..) { |i| i < 4 }) # => [1, 2, 3]
26.17 drop_while
Range.drop_while(x) { }
A new Array of the values from the first for which the block gives a falsy result to the end. An endless Range is not rejected, but the result would be endless, so the operation never returns on one.
p(Range.drop_while(1..6) { |i| i < 4 }) # => [4, 5, 6]
26.18 map, collect
Range.map(x) { }
Range.collect(x) { }
A new Array of the block’s results, one per value. Needs a finite Integer or String Range. The element type of the Array is the type of the block’s results.
n = 4
p(Range.map(1..n) { |i| i * i }) # => [1, 4, 9, 16]
p(Range.collect("a".."c") { |s| String.upcase(s) }) # => ["A", "B", "C"]
```ruby error p(Range.map(1..) { |i| i }) # !> RangeError: Range.map: cannot do this on an endless Range 1..
## flat_map, collect_concat
`Range.flat_map(x) { }`
`Range.collect_concat(x) { }`
A new Array that joins the Arrays (or Tuples) the block returns, one level deep. The block must return an Array or a Tuple: another value is a `TypeError` at run time (`the block must return an Array or a Tuple, got Integer`). Needs a finite Range.
```ruby
p(Range.flat_map(1..3) { |i| Any[i, i] }) # => [1, 1, 2, 2, 3, 3]
p(Range.flat_map(1..2) { |i| [i, i * 10] }) # => [1, 10, 2, 20]
```ruby error p(Range.flat_map(1..2) { |i| i }) # !> TypeError: Range.flat_map: the block must return an Array or a Tuple, got Integer
## select, filter, find_all
`Range.select(x) { }`
`Range.filter(x) { }`
`Range.find_all(x) { }`
A new Array of the values for which the block gives a truthy result. Needs a finite Integer or String Range. The Array's element type is the type of the ends.
```ruby
p(Range.select(1..6) { |i| Integer.even?(i) }) # => [2, 4, 6]
p(Range.filter("a".."e") { |s| s > "c" }) # => ["d", "e"]
26.19 reject
Range.reject(x) { }
A new Array of the values for which the block gives a falsy result; the complement of select. Needs a finite Range.
p(Range.reject(1..6) { |i| Integer.even?(i) }) # => [1, 3, 5]
26.20 filter_map
Range.filter_map(x) { }
A new Array of the block’s results, leaving out nil and false, as Ruby’s filter_map. The checker removes nil from the element type, so the Array can be used without nil checks. Needs a finite Range.
p(Range.filter_map(1..6) { |i| Integer.even?(i) ? i * 10 : nil }) # => [20, 40, 60]
26.21 partition
Range.partition(x) { }
A Tuple of two Arrays: the values for which the block is truthy, then the others. Needs a finite Range.
evens, odds = Range.partition(1..6) { |i| Integer.even?(i) }
p(evens) # => [2, 4, 6]
p(odds) # => [1, 3, 5]
26.22 group_by
Range.group_by(x) { }
A Hash from each block result to the Array of the values that gave it, in order of first appearance. Needs a finite Range. The key type is the type of the block’s results; the value type is an Array of the ends’ type (reading h[k] gives nil for a key that is not there, as any Hash does).
h = Range.group_by(1..6) { |i| i % 3 }
p(h) # => {1 => [1, 4], 2 => [2, 5], 0 => [3, 6]}
p(h[1]) # => [1, 4]
26.23 chunk_while, slice_when
Range.chunk_while(x) { }
Range.slice_when(x) { }
An Array of Arrays that cuts the values into runs. The block receives two consecutive values; chunk_while keeps them in the same run while the block is truthy, slice_when starts a new run when it is truthy. Needs a finite Range.
p(Range.chunk_while(1..5) { |a, b| b != 3 }) # => [[1, 2], [3, 4, 5]]
p(Range.slice_when(1..6) { |a, b| Integer.even?(b) }) # => [[1], [2, 3], [4, 5], [6]]
26.24 slice_before, slice_after
Range.slice_before(x) { }
Range.slice_after(x) { }
An Array of Arrays that cuts the values before (or after) each value for which the block is truthy. Needs a finite Range.
p(Range.slice_before(1..6) { |i| Integer.even?(i) }) # => [[1], [2, 3], [4, 5], [6]]
p(Range.slice_after(1..6) { |i| Integer.even?(i) }) # => [[1, 2], [3, 4], [5, 6]]
26.25 find, detect
Range.find(x) { }
Range.detect(x) { }
The first value for which the block gives a truthy result, or nil when there is none. The result is T | nil, so --strict wants a check before it is used. An endless Range is fine when some value satisfies the block; otherwise the search never ends.
p(Range.find(1..10) { |i| i * i > 10 }) # => 4
p(Range.detect(1..10) { |i| i > 100 }) # => nil
p(Range.find(1..) { |i| i * i > 10 }) # => 4
s = Range.find("a".."e") { |c| c > "b" }
if s
p(String.upcase(s)) # => "C"
end
26.26 find_index
Range.find_index(x) { }
The position (0, 1, 2, …) of the first value for which the block is truthy, or nil (Integer | nil). Unlike Ruby’s find_index(v), a block is required and there is no form with a value. An endless Range is fine when a value is found.
p(Range.find_index(1..10) { |i| i * i > 10 }) # => 3
p(Range.find_index(1..10) { |i| i > 100 }) # => nil
26.27 bsearch
Range.bsearch(x) { }
Binary search over an Integer Range, as Ruby’s: in find-minimum mode the block gives true for the wanted value and every value above it, and the result is the smallest such value, or nil when the block is never true (T | nil). An endless Range is fine. A String Range is a TypeError at run time (can't do binary search for String).
p(Range.bsearch(1..100) { |i| i * i >= 50 }) # => 8
p(Range.bsearch(1..100) { |i| i > 1000 }) # => nil
p(Range.bsearch(1..) { |i| i * i >= 50 }) # => 8
```ruby error p(Range.bsearch(“a”..”z”) { |s| s >= “m” }) # !> TypeError: Range.bsearch: can’t do binary search for String
## all?, any?, none?, one?
`Range.all?(x) { }`
`Range.any?(x) { }`
`Range.none?(x) { }`
`Range.one?(x) { }`
Whether the block is truthy for every value, for at least one, for none, or for exactly one. Unlike Ruby's, the block is required (there is no `any?` without a block and no pattern argument). Needs a finite Range (`RangeError`). On a Range with no values, `all?` and `none?` are true, `any?` and `one?` false.
```ruby
p(Range.all?(1..5) { |i| i > 0 }) # => true
p(Range.any?(1..5) { |i| i > 4 }) # => true
p(Range.none?(1..5) { |i| i > 5 }) # => true
p(Range.one?(1..5) { |i| i == 3 }) # => true
p(Range.any?(5..1) { |i| true }) # => false
26.28 reduce, inject
Range.reduce(x, Any) { }
Range.inject(x, Any) { }
Folds the values: the block receives the accumulated value and the next value, and its result becomes the accumulated value; the result is the last accumulated value. The initial value is required (Ruby’s inject without one is a wrong number of arguments problem), and the result’s type is that of the initial value joined with the block’s results. There is no symbol form (reduce(:+)); use sum for that. Needs a finite Range.
p(Range.reduce(1..5, 0) { |acc, i| acc + i }) # => 15
p(Range.inject(1..5, 1) { |acc, i| acc * i }) # => 120
p(Range.reduce(1..3, "") { |acc, i| acc + Integer.to_s(i) }) # => "123"
p(Range.reduce(5..1, 7) { |acc, i| acc + i }) # => 7
```ruby error p(Range.inject(1..5) { |acc, i| acc + i }) # !> wrong number of arguments for Range.inject (given 1, expected 2)
## sum
`Range.sum(x, [Integer|Float|Rational|Complex]) [{ }]`
The sum of the values, or of the block's results, added to the initial value (default 0). Only for an Integer Range (`type` for a String Range), and a finite one (`RangeError`). The result's type follows the initial value and the block: Integer values with an Integer start give an Integer; a Float start gives a Float. As Ruby's `Range#sum`, an Integer Range with a Rational initial value gives a Float (`Range.sum(1..3, 2r)` is 8.0), and a Complex initial value is a `RangeError` at run time (`can't convert 1+2i into Float`). A block whose results are not numbers is a `type` problem.
```ruby
p(Range.sum(1..100)) # => 5050
p(Range.sum(1..10, 100)) # => 155
p(Range.sum(1..3) { |i| i * 2 }) # => 12
p(Range.sum(1..3, 0.5)) # => 6.5
p(Range.sum(1..3, 2r)) # => 8.0
p(Range.sum(1..0)) # => 0
```ruby error p(Range.sum(“a”..”c”)) # !> the Range’s first value must be Integer, but is String
```ruby error
p(Range.sum(1..3, Complex(1, 2))) # !> RangeError: Range.sum: can't convert 1+2i into Float
26.29 min, max
Range.min(x)
Range.max(x)
The smallest and the largest value, or nil when the Range has no values (5..1): the type is T | nil. max respects an excluded end (Range.max(1...5) is 4). Needs a finite Range (RangeError). Both accept a Float Range with an included end (Range.max(1.0..2.5) is 2.5), as Ruby’s; max of a Float Range with an excluded end is a TypeError (cannot exclude non Integer end value; min of it is fine).
p(Range.min(1..5)) # => 1
p(Range.max(1...5)) # => 4
p(Range.max(5..1)) # => nil
p(Range.min("a".."c")) # => "a"
p(Range.max(1.0..2.5)) # => 2.5
p(Range.min(1.0...2.5)) # => 1.0
```ruby error x = Range.min(1..5) p(x + 1) # !> the operands may be nil
```ruby error
p(Range.max(1.0...2.5)) # !> TypeError: Range.max: cannot exclude non Integer end value
26.30 minmax
Range.minmax(x)
The Tuple [min, max]; both are nil on a Range with no values (5..1, 2.5..1.0). Those are the nil of a miss: using an element unchecked is reported only at --strict=3. Needs a finite Range (RangeError). As min and max, it accepts a Float Range with an included end (Range.minmax(1.0..2.5) is [1.0, 2.5]); a Float Range with an excluded end is a TypeError (cannot exclude non Integer end value).
lo, hi = Range.minmax(1..5)
p([lo, hi]) # => [1, 5]
p(Range.minmax(5..1)) # => [nil, nil]
p(Range.minmax(1.0..2.5)) # => [1.0, 2.5]
p(Range.minmax("a".."c")) # => ["a", "c"]
```ruby error p(Range.minmax(1.0…2.5)) # !> TypeError: Range.minmax: cannot exclude non Integer end value
## min_by, max_by
`Range.min_by(x) { }`
`Range.max_by(x) { }`
The value for which the block gives the smallest (largest) result, or nil on a Range with no values (`T | nil`). Needs a finite Range.
```ruby
p(Range.min_by(1..5) { |i| -i }) # => 5
p(Range.max_by("a".."e") { |s| String.ord(s) }) # => "e"
p(Range.min_by(5..1) { |i| i }) # => nil
26.31 minmax_by
Range.minmax_by(x) { }
The Tuple of the values with the smallest and the largest block result; [nil, nil] on a Range with no values (the nil of a miss, reported only at --strict=3). Needs a finite Range.
p(Range.minmax_by(1..5) { |i| -i }) # => [5, 1]
26.32 sort
Range.sort(x)
A new Array of the values in ascending order, which for a Range is to_a. Needs a finite Integer or String Range.
p(Range.sort(1..3)) # => [1, 2, 3]
p(Range.sort(3..1)) # => []
26.33 sort_by
Range.sort_by(x) { }
A new Array of the values ordered by the block’s results. Needs a finite Range.
p(Range.sort_by(1..5) { |i| -i }) # => [5, 4, 3, 2, 1]
p(Range.sort_by("a".."c") { |s| -String.ord(s) }) # => ["c", "b", "a"]
26.34 uniq
Range.uniq(x)
A new Array of the values without duplicates; since a Range has none, it is to_a. Needs a finite Integer or String Range.
p(Range.uniq(1..3)) # => [1, 2, 3]
26.35 compact
Range.compact(x)
A new Array of the values without nil; since a Range has no nil, it is to_a. Needs a finite Integer or String Range.
p(Range.compact(1..3)) # => [1, 2, 3]
26.36 tally
Range.tally(x)
A Hash from each value to how often it appears, which for a Range is always 1. Needs a finite Integer or String Range.
p(Range.tally("a".."c")) # => {"a" => 1, "b" => 1, "c" => 1}
26.37 to_set
Range.to_set(x)
A new Set of the values. Needs a finite Integer or String Range.
p(Range.to_set(1..3)) # => Set[1, 2, 3]
26.38 zip
Range.zip(x, *Array)
A new Array of Tuples: the i-th Tuple holds the i-th value of the Range and the i-th element of each Array given. A shorter Array contributes nil (the Tuple’s type says so). Without Arrays each Tuple holds one value. Needs a finite Integer or String Range.
p(Range.zip(1..3, Any["a", "b", "c"])) # => [[1, "a"], [2, "b"], [3, "c"]]
p(Range.zip(1..3, Any[10, 20])) # => [[1, 10], [2, 20], [3, nil]]
p(Range.zip(1..2)) # => [[1], [2]]
27. Hash
A Hash maps keys to values and keeps insertion order. There is no literal like Ruby’s {"a" => 1}: a Hash is made by Hash.of("a" => 1, b: 2), Hash.new, or Hash.new(default) (Values and types). {k: v} is a Record, not a Hash (and {} is an empty Record, rejected statically; see Record). Keys written as labels, Hash.of(name: v), are Symbols.
A key may be an Integer, Float, String, Symbol, true, false, nil, Time, or a Tuple, Record, Array, Hash, Set, or a value of a class made only of these. A Regexp, a Range, or a value of a class that defines its own equality (== or Comparable) cannot be a key: TypeError at run time. Tuple and Record keys are copied when stored, so writing to the original later does not change the key (an Array key, as in Ruby, becomes unfindable when changed). "a" and :a are different keys, and so are 1 and 1.0.
h[k] gives nil on a miss (the default, for a Hash made by Hash.new(default)). Using that nil unchecked is reported only at --strict=3, as index-nil (level 2 does not report it). To make a miss an exception, use Hash.fetch. The nil of Hash.dig, Hash.min_by and Hash.max_by is a miss too (level 3). The nil of Hash.delete, Hash.key, Hash.first, Hash.shift, Hash.find and the like is reported at level 2.
An operation that takes a block passes each entry as one Tuple [key, value]: |k, v| takes it apart, |kv| receives the Tuple. each_with_object takes |(k, v), memo| and reduce takes |acc, (k, v)|. Iteration follows insertion order. The pairs returned by to_a, sort_by, take, first and others are [k, v] Tuples too.
The checker gives each Hash, per the place it is made, one key type and one value type. Storing another type with Hash.store or []= makes the value type a union (after putting "x" into Hash.of("a" => 1) the values are Integer | String, and + 1 on a fetched value is a type problem). In-place operations that change the element type, transform_keys! and transform_values!, do the same (see below).
The operators on a Hash are ==, != and the index h[k], h[k] = v. Hash.==(x, y) and so on are the function forms of those operators (Operators and indexing).
27.1 of
Hash.of(*Any)
Hash.of(k => v, ...) makes a new Hash. The arguments are key => value pairs only (in the label form Hash.of(name: v) the key is a Symbol); anything else (Hash.of(1), Hash.of({x: 1})) is a static error. Hash.new is the empty Hash. When a key is written twice, the later value stays. A value that cannot be a key is a TypeError at run time.
h = Hash.of("a" => 1, b: 2)
p(h) # => {"a" => 1, b: 2}
p(Hash.new) # => {}
p(Hash.of(1 => 2, 1 => 3)) # => {1 => 3}
p(Hash.of([1, 2] => "t", nil => "n")) # => {[1, 2] => "t", nil => "n"}
```ruby error
p(Hash.of(1)) # !> Hash.of takes key => value pairs, like Hash.of("a" => 1)
## Hash[]
`Hash[*Any]`
Makes an Array of Hashes, as `T[...]` does for every T: each element must be a Hash, checked on every write. A Hash itself is made with `Hash.of(k => v, ...)` or `Hash.new`.
```ruby
hs = Hash[Hash.of(a: 1), Hash.new]
p(hs) # => [{a: 1}, {}]
27.2 new
Hash.new([Any])
Makes an empty Hash. Hash.new(default) makes one whose h[k] gives default for a missing key, as Ruby’s does. That one object is shared: changing the Array of Hash.new(Any[]) through h["x"] is visible under every key, and nothing is stored in the Hash. Ruby’s block form Hash.new { |h, k| ... } does not exist (a static error). Hash.fetch ignores the default.
counts = Hash.new(0)
counts["x"] += 1
counts["x"] += 1
p(counts) # => {"x" => 2}
p(counts["none"]) # => 0
p(Hash.new["none"]) # => nil
d = Hash.new(Any[])
Array.push(d["a"], 1)
p(d["b"]) # => [1]
p(d) # => {}
```ruby error h = Hash.new { |hh, k| 0 } # !> Hash.new does not take a block
## default, set_default
`Hash.default(x)`
`Hash.set_default(x, Any)`
`default` is the value `h[k]` gives for a missing key (the default of `Hash.new(default)`; nil when there is none). `set_default(h, v)` changes it to `v` and returns `v` (Ruby's `h.default = v`). The checker adds the default's type to the type of `h[k]`.
```ruby
h = Hash.new(0)
p(Hash.default(h)) # => 0
p(Hash.default(Hash.new)) # => nil
Hash.set_default(h, 5)
p(h["q"]) # => 5
27.3 []
Hash.[](x, Any)
The function form of h[k]. Returns the value under key k, or the default (normally nil) when there is none. That nil is reported only at --strict=3, as index-nil. The key may be of any type; no value is a static problem. For a Hash made by Hash.new(default) the default’s type joins the result, so h[k] + 1 on Hash.new(0) passes even at level 3.
h = Hash.of("a" => 1, [1, 2] => "t")
p(h["a"]) # => 1
p(h[[1, 2]]) # => "t"
p(h["zz"]) # => nil
p(Hash.[](h, "a")) # => 1
27.4 []=, store
Hash.[]=(x, Any, Any)
Hash.store(x, Any, Any)
h[k] = v and its function form. Binds key k to value v and returns v. A new key goes to the end; an existing key keeps its position and only its value changes. A value that cannot be a key is a TypeError. The checker adds the types of k and v to the Hash’s key and value types.
h = Hash.of("a" => 1)
h["b"] = 2
p(Hash.store(h, "a", 10)) # => 10
p(h) # => {"a" => 10, "b" => 2}
```ruby error Hash.store(Hash.new, /x/, 1) # !> TypeError: Hash.store: Regexp cannot be a Hash key or Set element
## fetch
`Hash.fetch(x, Any, [Any])`
Returns the value under key `k`. When there is none, returns the third argument `default` if given, and otherwise raises `KeyError` (as Ruby's; there is no block form). The default of `Hash.new(default)` is not used. The result type is the union of the value type and the type of `default`, so it is never nil unless a value is: used instead of `h[k]`, it avoids the `index-nil` report of `--strict=3` too.
```ruby
h = Hash.of("a" => 1)
p(Hash.fetch(h, "a")) # => 1
p(Hash.fetch(h, "zz", 0)) # => 0
p(Hash.fetch(h, "a") + 1) # => 2
```ruby error p(Hash.fetch(Hash.of(“a” => 1), “b”)) # !> KeyError: Hash.fetch: key not found: “b”
## dig
`Hash.dig(x, Any, *Any)`
Digs through nested containers with the keys in order, as Ruby's `dig`: `dig(h, k)` is `h[k]`, and each further key indexes the value found so far (a Hash by key, an Array or a Tuple by position). The result is the value reached, or nil as soon as a key is missing on the way (for a miss in a Hash made by `Hash.new(default)` the default is given, as by `h[k]`, and the checker adds the default's type to the result). That nil is the nil of a miss, as with `h[k]`: using it unchecked is reported only at `--strict=3`, as `index-nil`; level 2 does not report it. The checker follows the keys through the types: a key applied to a value that is not a Hash, an Array, or a Tuple (an Integer, a String, a Record) is a `type` problem statically (`the value must be Array|Hash|Tuple, but is Integer`). A key of the wrong kind for the container reached (a String for an Array) is a `TypeError` at run time (`an index into Array must be Integer, got String`).
```ruby
h = Hash.of("a" => Hash.of("b" => Any[10, 20]))
p(Hash.dig(h, "a", "b", 1)) # => 20
p(Hash.dig(h, "a", "zz", 1)) # => nil
p(Hash.dig(h, "zz")) # => nil
t = Hash.of("t" => [1, "x"])
p(Hash.dig(t, "t", 1)) # => "x"
p(Hash.dig(t, "t", 5)) # => nil
p(Hash.dig(Hash.new(0), "q") + 1) # => 1
x = Hash.dig(h, "a", "b", 0)
if x
p(x + 1) # => 11
end
```ruby error p(Hash.dig(Hash.of(“a” => 1), “a”, “b”)) # !> Hash.dig: the value must be Array|Hash|Tuple, but is Integer
## fetch_values
`Hash.fetch_values(x, *Any)`
An Array of the values under the listed keys, in that order. A missing key raises `KeyError` (the default is not used). The element type is the value type; it is never nil.
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.fetch_values(h, "b", "a")) # => [2, 1]
```ruby error p(Hash.fetch_values(Hash.of(“a” => 1), “a”, “zz”)) # !> KeyError: Hash.fetch_values: key not found: “zz”
## values_at
`Hash.values_at(x, *Any)`
An Array of the values under the listed keys, in that order. A missing key gives the default (normally nil) at its position. The element type is `value | nil`, so using an element unchecked is reported at level 2.
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.values_at(h, "a", "zz")) # => [1, nil]
p(Hash.values_at(Hash.new(9), "x")) # => [9]
27.5 key?, has_key?, include?, member?
Hash.key?(x, Any)
Hash.has_key?(x, Any)
Hash.include?(x, Any)
Hash.member?(x, Any)
True when key k is present, even when its value is nil (this tells it apart from an h[k] that gives nil). The four are the same operation.
h = Hash.of("a" => nil)
p(Hash.key?(h, "a")) # => true
p(Hash.include?(h, "b")) # => false
p(h["a"]) # => nil
27.6 value?, has_value?
Hash.value?(x, Any)
Hash.has_value?(x, Any)
True when some value is == to v.
h = Hash.of("a" => 1, "b" => 2)
p(Hash.value?(h, 2)) # => true
p(Hash.has_value?(h, 3)) # => false
27.7 key
Hash.key(x, Any)
The first key whose value equals v, or nil (reported at level 2).
h = Hash.of("a" => 1, "b" => 2, "c" => 2)
p(Hash.key(h, 2)) # => "b"
p(Hash.key(h, 9)) # => nil
27.8 assoc, rassoc
Hash.assoc(x, Any)
Hash.rassoc(x, Any)
assoc(h, k) is the pair [k, v] under key k; rassoc(h, v) is the first pair [k, v] whose value is v. Both are Tuples, or nil (reported at level 2).
h = Hash.of("a" => 1, "b" => 2)
p(Hash.assoc(h, "a")) # => ["a", 1]
p(Hash.rassoc(h, 2)) # => ["b", 2]
p(Hash.assoc(h, "zz")) # => nil
27.9 length, size
Hash.length(x)
Hash.size(x)
The number of entries (an Integer).
p(Hash.size(Hash.of("a" => 1, "b" => 2))) # => 2
p(Hash.length(Hash.new)) # => 0
27.10 empty?
Hash.empty?(x)
True when there are no entries.
p(Hash.empty?(Hash.new)) # => true
p(Hash.empty?(Hash.new(0))) # => true
p(Hash.empty?(Hash.of("a" => 1))) # => false
27.11 count
Hash.count(x) [{ }]
Without a block, the number of entries (as size); with a block, the number of entries for which the block, given [k, v], returns true (an Integer).
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.count(h)) # => 3
p(Hash.count(h) { |k, v| v >= 2 }) # => 2
27.12 keys
Hash.keys(x)
A new Array of the keys in insertion order. Its element type is the key type. It is not a typed Array, so values of other types can be pushed onto it.
h = Hash.of("b" => 2, "a" => 1)
p(Hash.keys(h)) # => ["b", "a"]
p(Hash.keys(Hash.new)) # => []
27.13 values
Hash.values(x)
A new Array of the values in insertion order. Its element type is the value type.
h = Hash.of("b" => 2, "a" => 1)
p(Hash.values(h)) # => [2, 1]
27.14 to_a
Hash.to_a(x)
A new Array of the entries as [k, v] Tuples, in insertion order. The inner Arrays of Ruby’s to_a are Tuples in Sake.
h = Hash.of(1 => "x", 2 => "y")
p(Hash.to_a(h)) # => [[1, "x"], [2, "y"]]
Array.each(Hash.to_a(h)) { |k, v| p(k) } # => 1
# => 2
27.15 flatten
Hash.flatten(x)
A new Array of the keys and values alternating, [k1, v1, k2, v2, ...]. As Ruby’s flatten at depth 1: an Array or Tuple value is not opened (there is no depth argument). The element type is the union of the key and value types.
p(Hash.flatten(Hash.of(1 => "x", 2 => "y"))) # => [1, "x", 2, "y"]
p(Hash.flatten(Hash.of("a" => Any[1, 2]))) # => ["a", [1, 2]]
27.16 to_h
Hash.to_h(x)
The Hash itself (as Ruby’s Hash#to_h; not a copy). There is no block form. For a copy use Hash.dup.
h = Hash.of("a" => 1)
p(Hash.to_h(h)) # => {"a" => 1}
p(Kernel.equal?(Hash.to_h(h), h)) # => true
27.17 dup
Hash.dup(x)
A new Hash with the same entries (a shallow copy: the key and value objects are shared). The default is kept. A store or delete on one does not affect the other.
h = Hash.new(0)
Hash.store(h, "a", 1)
h2 = Hash.dup(h)
Hash.store(h2, "b", 2)
p(h) # => {"a" => 1}
p(h2) # => {"a" => 1, "b" => 2}
p(h2["zz"]) # => 0
27.18 first
Hash.first(x)
The first inserted entry as a Tuple [k, v], or nil when the Hash is empty (reported at level 2). Ruby’s first(n) form does not exist; the first n entries are Hash.take.
p(Hash.first(Hash.of("a" => 1, "b" => 2))) # => ["a", 1]
p(Hash.first(Hash.new)) # => nil
27.19 take, drop
Hash.take(x, Integer)
Hash.drop(x, Integer)
take(h, n) is the first n entries, drop(h, n) the rest after the first n, as an Array of [k, v] Tuples. The Hash does not change. n may exceed the number of entries. A negative n is an ArgumentError.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.take(h, 2)) # => [["a", 1], ["b", 2]]
p(Hash.drop(h, 2)) # => [["c", 3]]
p(Hash.drop(h, 5)) # => []
```ruby error p(Hash.take(Hash.of(“a” => 1), -1)) # !> ArgumentError: Hash.take: attempt to take negative size
## each, each_pair
`Hash.each(x) { }`
`Hash.each_pair(x) { }`
Calls the block for each entry in insertion order and returns the Hash itself. The block receives the Tuple `[k, v]` (`|k, v|` takes it apart). The block is required (Ruby's blockless form returning an Enumerator does not exist).
```ruby
h = Hash.of("a" => 1, "b" => 2)
Hash.each(h) { |k, v| p(k + Integer.to_s(v)) } # => "a1"
# => "b2"
Hash.each_pair(h) { |kv| p(kv) } # => ["a", 1]
# => ["b", 2]
r = Hash.each(h) { |k, v| nil }
p(Kernel.equal?(r, h)) # => true
27.20 each_key
Hash.each_key(x) { }
Calls the block for each key and returns the Hash itself. The block receives only the key.
Hash.each_key(Hash.of("a" => 1, "b" => 2)) { |k| p(k) } # => "a"
# => "b"
27.21 each_value
Hash.each_value(x) { }
Calls the block for each value and returns the Hash itself. The block receives only the value.
Hash.each_value(Hash.of("a" => 1, "b" => 2)) { |v| p(v * 10) } # => 10
# => 20
27.22 each_with_object
Hash.each_with_object(x, Any) { }
Calls the block for each entry while carrying memo along, and returns memo at the end. The block receives two things, the Tuple [k, v] and memo, so it is written |(k, v), memo|. The result type is the type of memo.
h = Hash.of("a" => 1, "b" => 2)
ks = Hash.each_with_object(h, Any[]) { |(k, v), acc| Array.push(acc, k) }
p(ks) # => ["a", "b"]
27.23 map
Hash.map(x) { }
Calls the block with [k, v] for each entry and returns a new Array of the results. As in Ruby the result is an Array, not a Hash (for a Hash use transform_values or to_h).
h = Hash.of("a" => 1, "b" => 2)
p(Hash.map(h) { |k, v| k + Integer.to_s(v) }) # => ["a1", "b2"]
p(Hash.map(h) { |kv| kv }) # => [["a", 1], ["b", 2]]
27.24 flat_map
Hash.flat_map(x) { }
Calls the block for each entry and returns a new Array of all the Arrays it returned, joined. The block must return an Array or a Tuple (a Tuple’s elements are joined in like an Array’s); a scalar is a TypeError at run time (the block must return an Array or a Tuple, got Integer; Ruby keeps a non-Array result as one element).
h = Hash.of("a" => 1, "b" => 2)
p(Hash.flat_map(h) { |k, v| Any[k, v] }) # => ["a", 1, "b", 2]
p(Hash.flat_map(h) { |k, v| [k, v] }) # => ["a", 1, "b", 2]
```ruby error Hash.flat_map(Hash.of(“a” => 1)) { |k, v| v } # !> TypeError: Hash.flat_map: the block must return an Array or a Tuple, got Integer
## filter_map
`Hash.filter_map(x) { }`
Calls the block for each entry and returns a new Array of the results other than nil and false.
```ruby
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.filter_map(h) { |k, v| v > 1 ? k : nil }) # => ["b", "c"]
27.25 select, filter
Hash.select(x) { }
Hash.filter(x) { }
A new Hash of the entries for which the block returns true (a Hash, as in Ruby). The original does not change. The key and value types are those of the original.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.select(h) { |k, v| v > 1 }) # => {"b" => 2, "c" => 3}
p(Hash.filter(h) { |k, v| k == "a" }) # => {"a" => 1}
p(h) # => {"a" => 1, "b" => 2, "c" => 3}
27.26 reject
Hash.reject(x) { }
A new Hash without the entries for which the block returns true. The original does not change.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.reject(h) { |k, v| v > 1 }) # => {"a" => 1}
27.27 partition
Hash.partition(x) { }
The entries for which the block returns true and those for which it returns false, each as an Array of [k, v] Tuples, in a two-element Tuple [true ones, false ones]. Take it with multiple assignment.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
odd, even = Hash.partition(h) { |k, v| Integer.odd?(v) }
p(odd) # => [["a", 1], ["c", 3]]
p(even) # => [["b", 2]]
27.28 group_by
Hash.group_by(x) { }
Calls the block for each entry and returns a new Hash from each block result to the Array of the [k, v] Tuples that gave it. The block result must be a value that can be a key.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
g = Hash.group_by(h) { |k, v| v % 2 }
p(g) # => {1 => [["a", 1], ["c", 3]], 0 => [["b", 2]]}
p(Hash.fetch(g, 0)) # => [["b", 2]]
27.29 find, detect
Hash.find(x) { }
Hash.detect(x) { }
The first entry for which the block returns true, as a Tuple [k, v], or nil. At level 2, passing that nil on to multiple assignment or an index is reported, so check with if.
h = Hash.of("a" => 1, "b" => 2)
kv = Hash.find(h) { |k, v| v == 2 }
if kv
k, v = kv
p(k) # => "b"
end
p(Hash.detect(h) { |k, v| v == 9 }) # => nil
```ruby error kv = Hash.find(Hash.of(“a” => 1)) { |k, v| v == 1 } k, v = kv # !> multiple assignment: argument 1 may be nil
## min_by, max_by
`Hash.min_by(x) { }`
`Hash.max_by(x) { }`
The entry for which the block result is smallest or largest, as a Tuple `[k, v]`, or nil for an empty Hash. That nil is the nil of a miss (as `Array.min_by`): using it unchecked is reported only at `--strict=3`. The block results must compare with each other: a block whose result type mixes types that cannot be compared (Integer and String) is rejected statically as a `type` problem, and one whose result may be nil as a `nil` problem; when the checker cannot see it (`Float.NAN`) it is an `ArgumentError` at run time.
```ruby
h = Hash.of("a" => 3, "b" => 1, "c" => 2)
p(Hash.min_by(h) { |k, v| v }) # => ["b", 1]
p(Hash.max_by(h) { |k, v| v }) # => ["a", 3]
p(Hash.max_by(Hash.new) { |k, v| v }) # => nil
```ruby error Hash.min_by(Hash.of(“a” => 1, “b” => “x”)) { |k, v| v } # !> Hash.min_by: elements compared in order may be (Integer, String), which cannot be compared
## sort_by
`Hash.sort_by(x) { }`
The entries in ascending order of the block result, as an Array of `[k, v]` Tuples (an Array, not a Hash, as in Ruby). The block results must compare with each other: a result type that mixes types that cannot be compared is rejected statically (`type`), as with `min_by`. For descending order negate in the block or use `Array.reverse`. A Tuple result sorts in dictionary order ([Tuple](Tuple.md)).
```ruby
h = Hash.of("a" => 3, "b" => 1, "c" => 2)
p(Hash.sort_by(h) { |k, v| v }) # => [["b", 1], ["c", 2], ["a", 3]]
p(Hash.sort_by(h) { |k, v| -v }) # => [["a", 3], ["c", 2], ["b", 1]]
```ruby error Hash.sort_by(Hash.of(“a” => 1, “b” => “x”)) { |k, v| v } # !> Hash.sort_by: elements compared in order may be (Integer, String), which cannot be compared
## any?, all?, none?
`Hash.any?(x) { }`
`Hash.all?(x) { }`
`Hash.none?(x) { }`
Whether the block returns true for at least one entry (`any?`), for every entry (`all?`), or for none (`none?`). The block is required (Ruby's blockless forms do not exist and are a static error; for emptiness use `empty?`). On an empty Hash `any?` is false and `all?` and `none?` are true.
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.any?(h) { |k, v| v > 1 }) # => true
p(Hash.all?(h) { |k, v| v > 1 }) # => false
p(Hash.none?(h) { |k, v| v > 5 }) # => true
```ruby error p(Hash.any?(Hash.of(“a” => 1))) # !> Hash.any? requires a block
## one?
`Hash.one?(x) { }`
True when the block returns true for exactly one entry. The block is required.
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.one?(h) { |k, v| v == 2 }) # => true
p(Hash.one?(h) { |k, v| v > 0 }) # => false
27.30 sum
Hash.sum(x, [Integer|Float|Rational|Complex]) { }
Calls the block for each entry and adds the results to init (0 when omitted). The block is required (Ruby’s blockless form does not exist), and init must be a number (a String is a type problem statically; to join strings use reduce). A block result that is not a number is a TypeError at run time (String can't be coerced into Integer). The result type follows from init and the numeric type of the block results.
h = Hash.of("a" => 1, "b" => 2)
p(Hash.sum(h) { |k, v| v }) # => 3
p(Hash.sum(h, 0.5) { |k, v| v }) # => 3.5
p(Hash.sum(Hash.new) { |k, v| 1 }) # => 0
```ruby error Hash.sum(Hash.of(“a” => “x”), “”) { |k, v| v } # !> argument 2 must be Integer|Float|Rational|Complex, but is String
## reduce, inject
`Hash.reduce(x, Any) { }`
`Hash.inject(x, Any) { }`
Starting from `init`, calls the block as `|acc, (k, v)|` for each entry, feeds the result in as the next `acc`, and returns the last result. The initial value is required (Ruby's form without one does not exist). The result type is the union of the types of `init` and of the block results.
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.reduce(h, 0) { |acc, (k, v)| acc + v }) # => 3
p(Hash.inject(h, "") { |acc, (k, v)| acc + k }) # => "ab"
27.31 transform_values
Hash.transform_values(x) { }
A new Hash whose values are the block applied to each value. The block receives only the value. The keys stay, and the original does not change. The result’s value type is the block’s result type, so this is the form for a conversion that changes the type (unlike the in-place transform_values!, it does not make a union with the old type).
h = Hash.of("a" => 1, "b" => 2)
p(Hash.transform_values(h) { |v| Integer.to_s(v) }) # => {"a" => "1", "b" => "2"}
p(h) # => {"a" => 1, "b" => 2}
27.32 transform_keys
Hash.transform_keys(x) { }
A new Hash whose keys are the block applied to each key. The block receives only the key, and its result must be a value that can be a key (TypeError). When two keys map to the same result, the later entry’s value stays. The original does not change. Ruby’s Hash-argument form (transform_keys(a: :b)) does not exist.
h = Hash.of("a" => 1, "b" => 2)
p(Hash.transform_keys(h) { |k| String.to_sym(k) }) # => {a: 1, b: 2}
p(Hash.transform_keys(Hash.of("a" => 1, "A" => 2)) { |k| String.downcase(k) }) # => {"a" => 2}
27.33 transform_values!
Hash.transform_values!(x) { }
Replaces each value with the block’s result and returns the Hash itself. The checker gives a Hash one value type, so a conversion that changes the type (Integer to String) makes the Hash’s value type the union Integer | String, and operations that fit only one of them are reported from then on as type (partial). To change the type, make a new Hash with transform_values.
h = Hash.of("a" => 1, "b" => 2)
r = Hash.transform_values!(h) { |v| v * 10 }
p(h) # => {"a" => 10, "b" => 20}
p(Kernel.equal?(r, h)) # => true
```ruby error h = Hash.of(“a” => 1) Hash.transform_values!(h) { |v| Integer.to_s(v) } # !> argument 1 must be Integer, but can be String
## transform_keys!
`Hash.transform_keys!(x) { }`
Replaces each key with the block's result and returns the Hash itself. The result must be a value that can be a key (`TypeError`). As with `transform_values!`, a conversion that changes the type makes the key type a union, and an operation for one of the types, in `Array.each(Hash.keys(h))` say, is a `type` problem. To change the type use `transform_keys`.
```ruby
h = Hash.of("a" => 1, "b" => 2)
Hash.transform_keys!(h) { |k| String.upcase(k) }
p(h) # => {"A" => 1, "B" => 2}
```ruby error h = Hash.of(“a” => 1) Hash.transform_keys!(h) { |k| String.to_sym(k) } # !> argument 1 must be String, but can be Symbol
## merge
`Hash.merge(x, Hash)`
A new Hash with the entries of both; for a shared key the second argument's value wins. Neither changes. The second argument must be a Hash; passing a Record (`{a: 2}`) is a `type` problem statically. Ruby's block form (deciding the value on a conflict) does not exist. The result's key and value types are the unions of both.
```ruby
a = Hash.of("a" => 1, "b" => 2)
b = Hash.of("b" => 20, "c" => 3)
p(Hash.merge(a, b)) # => {"a" => 1, "b" => 20, "c" => 3}
p(a) # => {"a" => 1, "b" => 2}
```ruby error p(Hash.merge(Hash.of(“a” => 1), {a: 2})) # !> Hash.merge: argument 2 must be Hash, but is {a: Integer}
## merge!, update
`Hash.merge!(x, Hash)`
`Hash.update(x, Hash)`
Writes the second argument's entries into the first Hash (overwriting shared keys) and returns the first Hash itself. The checker adds the second argument's key and value types to the first's. There is no block form.
```ruby
h = Hash.of("a" => 1)
Hash.merge!(h, Hash.of("b" => 2))
p(Hash.update(h, Hash.of("a" => 10))) # => {"a" => 10, "b" => 2}
p(h) # => {"a" => 10, "b" => 2}
27.34 replace
Hash.replace(x, Hash)
Replaces the whole contents of the first Hash with the second’s entries and returns the first Hash itself.
h = Hash.of("a" => 1, "b" => 2)
p(Hash.replace(h, Hash.of("z" => 26))) # => {"z" => 26}
p(h) # => {"z" => 26}
27.35 compact
Hash.compact(x)
A new Hash without the entries whose value is nil. nil leaves the result’s value type.
h = Hash.of("a" => 1, "b" => nil)
p(Hash.compact(h)) # => {"a" => 1}
p(h) # => {"a" => 1, "b" => nil}
27.36 compact!
Hash.compact!(x)
Removes the entries whose value is nil in place and returns the Hash itself, or nil when nothing was removed (as Ruby’s); using the result unchecked is reported at level 2.
h = Hash.of("a" => 1, "b" => nil)
p(Hash.compact!(h)) # => {"a" => 1}
p(Hash.compact!(h)) # => nil
```ruby error p(Hash.size(Hash.compact!(Hash.of(“a” => 1)))) # !> Hash.size: argument 1 may be nil
## slice
`Hash.slice(x, *Any)`
A new Hash of the entries under the listed keys only. Missing keys are ignored; the order is the order listed.
```ruby
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.slice(h, "c", "a", "zz")) # => {"c" => 3, "a" => 1}
27.37 except
Hash.except(x, *Any)
A new Hash without the entries under the listed keys. Missing keys are ignored.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.except(h, "a", "zz")) # => {"b" => 2, "c" => 3}
27.38 invert
Hash.invert(x)
A new Hash with the values as keys and the keys as values. The values must be able to be keys (TypeError); when a value occurs more than once, the later entry stays. The result’s key and value types are the original’s value and key types.
p(Hash.invert(Hash.of("a" => 1, "b" => 2))) # => {1 => "a", 2 => "b"}
p(Hash.invert(Hash.of("a" => 1, "b" => 1))) # => {1 => "b"}
```ruby error Hash.invert(Hash.of(“a” => /x/)) # !> TypeError: Hash.invert: Regexp cannot be a Hash key or Set element
## delete
`Hash.delete(x, Any)`
Removes the entry under key `k` and returns its value, or nil when there is none, which is reported at level 2 (the default is not used). Ruby's block form does not exist.
```ruby
h = Hash.of("a" => 1, "b" => 2)
p(Hash.delete(h, "a")) # => 1
p(Hash.delete(h, "zz")) # => nil
p(h) # => {"b" => 2}
```ruby error h = Hash.of(“a” => 1) p(Hash.delete(h, “a”) + 1) # !> the operands may be nil
## delete_if
`Hash.delete_if(x) { }`
Removes in place the entries for which the block returns true and returns the Hash itself (never nil, even when nothing was removed; that is the difference from `reject!`).
```ruby
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.delete_if(h) { |k, v| v > 1 }) # => {"a" => 1}
p(Hash.delete_if(h) { |k, v| false }) # => {"a" => 1}
27.39 keep_if
Hash.keep_if(x) { }
Keeps in place only the entries for which the block returns true and returns the Hash itself (never nil, even when nothing changed; that is the difference from select!).
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.keep_if(h) { |k, v| v > 1 }) # => {"b" => 2, "c" => 3}
27.40 select!, filter!
Hash.select!(x) { }
Hash.filter!(x) { }
Keep in place only the entries for which the block returns true and return the Hash itself, or nil when nothing was removed (as Ruby’s); using the result unchecked is reported at level 2. The form that always returns the Hash is keep_if.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.select!(h) { |k, v| v > 1 }) # => {"b" => 2, "c" => 3}
p(Hash.filter!(h) { |k, v| v > 1 }) # => nil
p(h) # => {"b" => 2, "c" => 3}
27.41 reject!
Hash.reject!(x) { }
Removes in place the entries for which the block returns true and returns the Hash itself, or nil when nothing was removed (reported at level 2). The form that always returns the Hash is delete_if.
h = Hash.of("a" => 1, "b" => 2, "c" => 3)
p(Hash.reject!(h) { |k, v| v > 2 }) # => {"a" => 1, "b" => 2}
p(Hash.reject!(h) { |k, v| v > 5 }) # => nil
27.42 shift
Hash.shift(x)
Removes the first inserted entry and returns it as a Tuple [k, v], or nil when the Hash is empty (the default is not used; reported at level 2).
h = Hash.of("a" => 1, "b" => 2)
p(Hash.shift(h)) # => ["a", 1]
p(h) # => {"b" => 2}
Hash.shift(h)
p(Hash.shift(h)) # => nil
27.43 clear
Hash.clear(x)
Removes all entries and returns the Hash itself (now empty). The default is kept.
h = Hash.of("a" => 1)
p(Hash.clear(h)) # => {}
p(Hash.empty?(h)) # => true
27.44 ==, !=
Hash.==(x, Any)
Hash.!=(x, Any)
True when the two Hashes have the same set of keys and the values under each key are == (!= is the negation). Insertion order does not matter, and neither does the default. A right operand that is not a Hash (nil, an Integer, an Array) is never equal, with the function form as with the operator: Hash.==(h, nil) is false. A Record {a: 1} and Hash.of(a: 1) differ in type and are not equal.
a = Hash.of("a" => 1, "b" => 2)
p(a == Hash.of("b" => 2, "a" => 1)) # => true
p(a != Hash.of("a" => 1)) # => true
p(Hash.==(a, Hash.new)) # => false
p(Hash.==(a, nil)) # => false
p(Hash.!=(a, 1)) # => true
p(Hash.of(a: 1) == {a: 1}) # => false
28. Record
A Record is a set of (field name, value) pairs, made by the literal {x: 1, y: "a"} (Values and types). In Ruby that spelling is a Hash; in Sake it is a Record, not a Hash. A Hash is made by Hash.of(x: 1) (Hash). {} (empty) and {"a" => 1} (the => form) are rejected statically.
A Record’s type is its set of (field, type) pairs, written {x: Integer, y: String}. Records with the same set have the same type. Field order is not part of the type: {y: 2, x: 1} prints as {x: 1, y: 2}. A Record is never a value of a class (classes are told apart by name, Record types by structure; see Structs).
Fields are read with a pattern: r => {x:, y: name} binds the local x to field x and name to field y. Listing only some of the fields is allowed, and case r in {x:} ... end is the same pattern. Naming a field the Record does not have is a type problem statically (a KeyError at run time), and so is matching a value that is not a Record (a TypeError at run time). Writing to a field (r.x = v, r[:x] = v) does not exist yet (both are rejected statically; Indexable.[]= does not take a Record).
The operators on a Record are == and != (true when the field sets are the same and each value is equal). There is no index r[:x].
The three operations of this chapter treat a Record as data (for code that walks any Record). Their argument type is Any; anything but a Record is a TypeError at run time. The fields come out in field-name order (the order they print in).
28.1 keys
Record.keys(Any)
A new Array of the field names as Symbols. The order is field-name order, not the order written in the literal.
r = {x: 1, y: "a"}
p(Record.keys(r)) # => [:x, :y]
p(Record.keys({y: 2, x: 1})) # => [:x, :y]
```ruby error p(Record.keys(Hash.of(x: 1))) # !> TypeError: Record.keys: argument 1 must be a Record, got Hash
## values
`Record.values(Any)`
A new Array of the field values, in the order of `keys`. It is independent of the Record, and the Array can grow. Its element type is the union of the field types.
```ruby
r = {x: 1, y: "a"}
vs = Record.values(r)
p(vs) # => [1, "a"]
Array.push(vs, 3)
p(r) # => {x: 1, y: "a"}
28.2 to_h
Record.to_h(Any)
A new Hash with the field names as Symbol keys. To the checker its key type is Symbol and its value type is the union of the field types, so for a Record whose fields differ in type, using h[:x] directly in arithmetic is a type problem (a Hash has only one value type). When the fields share a type it is an ordinary Hash. A nested Record stays a Record as a value.
r = {x: 1, y: 2}
h = Record.to_h(r)
p(h) # => {x: 1, y: 2}
p(Hash.fetch(h, :x) + Hash.fetch(h, :y)) # => 3
p(Record.to_h({name: "a", pos: {x: 1}})) # => {name: "a", pos: {x: 1}}
ruby error
h = Record.to_h({x: 1, y: "a"})
p(Hash.fetch(h, :x) + 1) # !> which the left operand's type does not support
29. Set
A Set is a collection of elements without duplicates. Set.of(1, 2) makes a Set, and Set.new(xs) one of an Array’s elements, as Ruby’s Set.new([1, 2]). Elements are told apart by the rules of Hash keys: Integer, Float, String, Symbol, true, false, nil, Time, and Tuples, Records, Arrays, Hashes, Sets and values of a class made of these can be elements; a Regexp, a Range, or a value of a class that defines its own equality (== or Comparable) is a TypeError at run time. Whether two values are the same element is decided as Ruby’s Hash does, by hash and eql?, so 1 and 1.0 are two elements. Elements keep the order they were added in, and iteration follows it. A String used as an element is copied in, so changing the original String afterwards does not change the Set.
The checker follows the element type of a Set per construction site and widens it on writes such as Set.add. The block of an operation that takes one receives values of that element type.
The operators on Sets are | (union), & (intersection), - (difference), == and !=. ^, +, <, <= and the like do not apply; subset tests are Set.subset? and its relatives. The entries Set.|(x, y) and so on below are the function forms of the operators (Operators and indexing). Most operations that pick elements out (select, map, sort, to_a, …) return an Array, not a Set; this differs from Ruby’s Set#select, which returns a Set.
29.1 of
Set.of(*Any)
Makes a Set of the listed elements. Duplicate elements collapse into one. A value that cannot be an element (a Regexp, a Range, a class instance with its own equality) is a TypeError at run time. Set.new is the empty Set; elements added later with Set.add widen its element type.
s = Set.of(1, 2, 2, 3)
p(s) # => Set[1, 2, 3]
p(Set.new) # => Set[]
p(Set.size(Set.of(1, 1.0))) # => 2
```ruby error p(Set.of(/a/)) # !> TypeError: Set.of: Regexp cannot be a Hash key or Set element
## new
`Set.new([Array])`
Makes a Set of the elements of an Array, as Ruby's `Set.new([1, 2])`; `Set.new` with no argument is the empty Set. Duplicates collapse, and a value that cannot be an element is a `TypeError`.
```ruby
p(Set.new(Any[3, 1, 3])) # => Set[3, 1]
p(Set.new) # => Set[]
29.2 Set[]
Set[*Any]
Makes an Array of Sets, as T[...] does for every T: each element must be a Set, checked on every write.
ss = Set[Set.of(1), Set.new]
p(ss) # => [Set[1], Set[]]
29.3 length, size
Set.length(x)
Set.size(x)
The number of elements (an Integer).
p(Set.size(Set.of(1, 2, 3))) # => 3
p(Set.length(Set.new)) # => 0
29.4 empty?
Set.empty?(x)
True when the Set has no elements.
p(Set.empty?(Set.new)) # => true
p(Set.empty?(Set.of(1))) # => false
29.5 include?, member?
Set.include?(x, Any)
Set.member?(x, Any)
True when the value is an element. Elements compare as Hash keys do (hash and eql?), so a Set holding 1 does not hold 1.0. Asking for a value of a type other than the Set’s element type is not a static problem; the answer is false.
s = Set.of(1, 2)
p(Set.include?(s, 1)) # => true
p(Set.include?(s, 1.0)) # => false
p(Set.member?(s, "a")) # => false
p(Set.include?(Set.of([1, 2]), [1, 2])) # => true
29.6 add
Set.add(x, Any)
Adds the value as an element and returns the Set itself (in place). A value already present changes nothing. A value that cannot be an element is a TypeError at run time. The type of the added value joins the Set’s element type.
s = Set.of(1)
p(Set.add(s, 2)) # => Set[1, 2]
p(Set.add(s, 2)) # => Set[1, 2]
p(s) # => Set[1, 2]
```ruby error Set.add(Set.of(1), 1..2) # !> TypeError: Set.add: Range cannot be a Hash key or Set element
## add?
`Set.add?(x, Any)`
As `add`, but returns **nil** when the value was already present (the Set itself when it was newly added). The result is a Set or nil, so `--strict` (level 2) reports using it unchecked. Use it to ask "is this the first time I see this value?".
```ruby
s = Set.of(1)
p(Set.add?(s, 2)) # => Set[1, 2]
p(Set.add?(s, 2)) # => nil
```ruby error s = Set.of(1) x = Set.add?(s, 2) p(Set.size(x)) # !> argument 1 may be nil
## delete
`Set.delete(x, Any)`
Removes the value from the elements and returns the Set itself (in place). A value that is not present changes nothing. As Ruby's.
```ruby
s = Set.of(1, 2, 3)
p(Set.delete(s, 2)) # => Set[1, 3]
p(Set.delete(s, 99)) # => Set[1, 3]
29.7 delete?
Set.delete?(x, Any)
As delete, but returns nil when the value was not present (the Set itself when it was removed). --strict (level 2) reports using the result unchecked.
s = Set.of(1, 2)
p(Set.delete?(s, 2)) # => Set[1]
p(Set.delete?(s, 2)) # => nil
29.8 merge
Set.merge(x, Set)
Adds every element of the other Set and returns the Set itself (in place). The argument must be a Set (an Array is a type problem statically; Ruby’s merge takes any enumerable). For a new Set use | or union.
s = Set.of(1, 2)
p(Set.merge(s, Set.of(2, 3))) # => Set[1, 2, 3]
p(s) # => Set[1, 2, 3]
```ruby error Set.merge(Set.of(1), Any[2]) # !> argument 2 must be Set, but is Array
## subtract
`Set.subtract(x, Any)`
Removes every element contained in the argument and returns the Set itself (in place). The argument is a Set, an Array, a Tuple, or a Range; the checker does not check it (the signature says `Any`), and anything else is a `TypeError` at run time (`argument 2 must be Set, Array, Tuple, or Range, got Integer`). For a new Set use `-` or `difference`.
```ruby
s = Set.of(1, 2, 3, 4, 5)
p(Set.subtract(s, Set.of(1))) # => Set[2, 3, 4, 5]
p(Set.subtract(s, Any[2])) # => Set[3, 4, 5]
p(Set.subtract(s, [3])) # => Set[4, 5]
p(Set.subtract(s, 4..4)) # => Set[5]
p(s) # => Set[5]
```ruby error Set.subtract(Set.of(1, 2), 1) # !> TypeError: Set.subtract: argument 2 must be Set, Array, Tuple, or Range, got Integer
## replace
`Set.replace(x, Set)`
Replaces all elements with those of the other Set and returns the Set itself (in place). The other Set's element type joins this Set's.
```ruby
s = Set.of(1, 2)
p(Set.replace(s, Set.of(7, 8))) # => Set[7, 8]
p(s) # => Set[7, 8]
29.9 clear
Set.clear(x)
Removes all elements and returns the now empty Set itself.
s = Set.of(1, 2)
p(Set.clear(s)) # => Set[]
p(Set.empty?(s)) # => true
29.10 |, union
Set.|(x, Any)
Set.union(x, Set)
The union: a new Set with the elements of both. Set.| is the function form of a | b. The right operand must be a Set; an Array or other value is a type problem statically. The result’s element type is the union of both element types.
a = Set.of(1, 2)
b = Set.of(2, 3)
p(a | b) # => Set[1, 2, 3]
p(Set.union(a, b)) # => Set[1, 2, 3]
p(a) # => Set[1, 2]
```ruby error p(Set.of(1) | Any[2]) # !> Bitwise.|: the operands are (Set@L1[Integer], Array@L1[Integer])
## &, intersection
`Set.&(x, Any)`
`Set.intersection(x, Set)`
The intersection: a **new** Set with only the elements present in both. `Set.&` is the function form of `a & b`. The right operand must be a Set.
```ruby
a = Set.of(1, 2, 3)
b = Set.of(2, 3, 4)
p(a & b) # => Set[2, 3]
p(Set.intersection(a, b)) # => Set[2, 3]
29.11 -, difference
Set.-(x, Any)
Set.difference(x, Set)
The difference: a new Set with the elements of the left operand that are not in the right one. Set.- is the function form of a - b. The right operand must be a Set.
a = Set.of(1, 2, 3)
b = Set.of(2)
p(a - b) # => Set[1, 3]
p(Set.difference(a, b)) # => Set[1, 3]
p(a) # => Set[1, 2, 3]
29.12 ==, !=
Set.==(x, Any)
Set.!=(x, Any)
True when the two Sets have the same elements, in any order (!= is the negation). A right operand that is not a Set (an Array, nil) is never equal.
p(Set.of(1, 2) == Set.of(2, 1)) # => true
p(Set.of(1, 2) == Set.of(1)) # => false
p(Set.of(1) != Set.of(1)) # => false
p(Set.of(1, 2) == Any[1, 2]) # => false
29.13 subset?, superset?
Set.subset?(x, Set)
Set.superset?(x, Set)
subset? is true when every element of x is in the argument Set; superset? is the reverse. Equal Sets answer true to both. The argument must be a Set (an Array is a type problem statically). Ruby’s <= and >= operators do not apply to Sets; use these functions.
a = Set.of(1, 2)
b = Set.of(1, 2, 3)
p(Set.subset?(a, b)) # => true
p(Set.subset?(a, a)) # => true
p(Set.superset?(b, a)) # => true
p(Set.superset?(a, b)) # => false
```ruby error p(Set.of(1) <= Set.of(1, 2)) # !> Comparable.<=: the operands are (Set@L1[Integer], Set@L1[Integer])
## proper_subset?, proper_superset?
`Set.proper_subset?(x, Set)`
`Set.proper_superset?(x, Set)`
Proper subset tests: as `subset?` and `superset?`, but equal Sets answer false (Ruby's `<` and `>`).
```ruby
a = Set.of(1, 2)
b = Set.of(1, 2, 3)
p(Set.proper_subset?(a, b)) # => true
p(Set.proper_subset?(a, a)) # => false
p(Set.proper_superset?(b, a)) # => true
29.14 disjoint?, intersect?
Set.disjoint?(x, Set)
Set.intersect?(x, Set)
intersect? is true when the two Sets share at least one element; disjoint? is its negation.
p(Set.intersect?(Set.of(1, 2), Set.of(2, 3))) # => true
p(Set.disjoint?(Set.of(1, 2), Set.of(2, 3))) # => false
p(Set.disjoint?(Set.of(1), Set.of(9))) # => true
29.15 each, each_entry
Set.each(x) { }
Set.each_entry(x) { }
Passes the elements to the block one at a time, in the order they were added, and returns the Set itself. The block is required (there is no block-less form giving an Enumerator as in Ruby).
s = Set.of(3, 1, 2)
Set.each(s) { |x| puts(x) }
# => 3
# => 1
# => 2
p(Set.each_entry(s) { |x| x }) # => Set[3, 1, 2]
29.16 each_with_index
Set.each_with_index(x) { }
Passes each element and its position (from 0) to the block and returns the Set itself.
Set.each_with_index(Set.of("a", "b")) { |x, i| puts("#{i}: #{x}") }
# => 0: a
# => 1: b
29.17 each_with_object
Set.each_with_object(x, Any) { }
Passes each element and the second argument to the block, and returns that second argument. The usual form passes an Array or Hash to accumulate into.
a = Set.each_with_object(Set.of(1, 2), Any[]) { |x, acc| Array.push(acc, x * 2) }
p(a) # => [2, 4]
29.18 reverse_each
Set.reverse_each(x) { }
Passes the elements to the block in the reverse of the order they were added, and returns the Set itself.
Set.reverse_each(Set.of(1, 2, 3)) { |x| puts(x) }
# => 3
# => 2
# => 1
29.19 each_slice, each_cons
Set.each_slice(x, Integer) { }
Set.each_cons(x, Integer) { }
each_slice cuts the elements into Arrays of n (the last may be shorter) and passes each to the block; each_cons passes each run of n consecutive elements as an Array, moving one element at a time. Both return the Set itself. An n of 0 or less is an ArgumentError.
s = Set.of(1, 2, 3, 4)
Set.each_slice(s, 3) { |a| p(a) }
# => [1, 2, 3]
# => [4]
Set.each_cons(s, 3) { |a| p(a) }
# => [1, 2, 3]
# => [2, 3, 4]
```ruby error Set.each_slice(Set.of(1), 0) { |a| p(a) } # !> ArgumentError: Set.each_slice: invalid slice size
## cycle
`Set.cycle(x, Integer) { }`
Passes the sequence of elements to the block n times over and returns **nil**. The count is required (there is no endless `cycle` as in Ruby). An n of 0 or less does nothing.
```ruby
Set.cycle(Set.of(1, 2), 2) { |x| print(x) }
puts("")
# => 1212
p(Set.cycle(Set.of(1), 0) { |x| p(x) }) # => nil
29.20 map, collect
Set.map(x) { }
Set.collect(x) { }
The Array of the block’s results for each element (not a Set: in element order, duplicates kept). Its element type is the type of the block’s result.
p(Set.map(Set.of(1, 2, 3)) { |x| x * 10 }) # => [10, 20, 30]
p(Set.collect(Set.of(1, 2, 3)) { |x| x % 2 }) # => [1, 0, 1]
29.21 map!, collect!
Set.map!(x) { }
Set.collect!(x) { }
Replaces each element with the block’s result and returns the Set itself (in place). Results that coincide leave fewer elements. The block’s result type joins the element type. A result that cannot be an element is a TypeError at run time.
s = Set.of(1, 2, 3)
p(Set.map!(s) { |x| x % 2 }) # => Set[1, 0]
p(s) # => Set[1, 0]
29.22 flat_map, collect_concat
Set.flat_map(x) { }
Set.collect_concat(x) { }
The block returns an Array or a Tuple for each element, and the result is the one Array of all of them joined. A block result that is neither is a TypeError at run time (the block must return an Array or a Tuple, got Integer; the value is not kept as is, as Ruby would).
p(Set.flat_map(Set.of(1, 2)) { |x| Any[x, x * 10] }) # => [1, 10, 2, 20]
p(Set.flat_map(Set.of(1, 2)) { |x| [x, x * 10] }) # => [1, 10, 2, 20]
```ruby error Set.flat_map(Set.of(1)) { |x| x } # !> TypeError: Set.flat_map: the block must return an Array or a Tuple, got Integer
## select, filter, find_all
`Set.select(x) { }`
`Set.filter(x) { }`
`Set.find_all(x) { }`
The **Array** of the elements for which the block is truthy (Ruby's `Set#select` returns a Set; in Sake it is an Array). For a Set, narrow in place with `select!`, or turn the Array back with `Array.to_set`.
```ruby
s = Set.of(1, 2, 3, 4)
p(Set.select(s) { |x| Integer.even?(x) }) # => [2, 4]
p(Set.filter(s) { |x| x > 3 }) # => [4]
p(Set.find_all(s) { |x| x < 0 }) # => []
29.23 reject
Set.reject(x) { }
The Array of the elements for which the block is falsy (the reverse of select).
p(Set.reject(Set.of(1, 2, 3, 4)) { |x| Integer.even?(x) }) # => [1, 3]
29.24 select!, filter!, reject!
Set.select!(x) { }
Set.filter!(x) { }
Set.reject!(x) { }
select! and filter! remove the elements for which the block is falsy, reject! those for which it is truthy, in place. They return the Set itself when something was removed and nil when nothing changed (as Ruby’s). --strict (level 2) reports using the result unchecked. The forms that always return the Set are keep_if and delete_if.
s = Set.of(1, 2, 3, 4)
p(Set.select!(s) { |x| x > 1 }) # => Set[2, 3, 4]
p(Set.select!(s) { |x| x > 1 }) # => nil
p(Set.reject!(s) { |x| x > 3 }) # => Set[2, 3]
p(Set.filter!(s) { |x| x < 9 }) # => nil
p(s) # => Set[2, 3]
29.25 keep_if, delete_if
Set.keep_if(x) { }
Set.delete_if(x) { }
keep_if keeps only the elements for which the block is truthy; delete_if removes those (in place). Both always return the Set itself.
s = Set.of(1, 2, 3, 4)
p(Set.keep_if(s) { |x| x > 1 }) # => Set[2, 3, 4]
p(Set.delete_if(s) { |x| x > 3 }) # => Set[2, 3]
p(Set.delete_if(s) { |x| x > 9 }) # => Set[2, 3]
29.26 filter_map
Set.filter_map(x) { }
Applies the block to each element and collects the results that are neither nil nor false, as an Array. The result’s element type has nil removed.
p(Set.filter_map(Set.of(1, 2, 3, 4)) { |x| Integer.even?(x) ? x * 10 : nil }) # => [20, 40]
29.27 partition
Set.partition(x) { }
The Tuple [yes, no] of the Array of elements for which the block is truthy and the Array of the others.
ev, od = Set.partition(Set.of(1, 2, 3, 4)) { |x| Integer.even?(x) }
p(ev) # => [2, 4]
p(od) # => [1, 3]
29.28 group_by
Set.group_by(x) { }
A Hash from each block result to the Array of the elements that gave it. A block result that cannot be a key is a TypeError at run time.
p(Set.group_by(Set.of(1, 2, 3, 4)) { |x| x % 2 }) # => {1 => [1, 3], 0 => [2, 4]}
29.29 classify
Set.classify(x) { }
The same grouping as group_by, but the Hash’s values are Sets rather than Arrays (Ruby’s Set#classify).
p(Set.classify(Set.of(1, 2, 3, 4)) { |x| x % 2 }) # => {1 => Set[1, 3], 0 => Set[2, 4]}
29.30 tally
Set.tally(x)
A Hash from each element to its count. A Set has no duplicates, so every value is 1 (the operation comes from Array and exists for uniformity).
p(Set.tally(Set.of("a", "b"))) # => {"a" => 1, "b" => 1}
29.31 chunk_while, slice_when
Set.chunk_while(x) { }
Set.slice_when(x) { }
Pass each pair of adjacent elements to the block; chunk_while keeps a run together while the block is truthy, slice_when cuts where it is truthy. The result is an Array of Arrays.
s = Set.of(1, 2, 4, 5, 7)
p(Set.chunk_while(s) { |a, b| b == a + 1 }) # => [[1, 2], [4, 5], [7]]
p(Set.slice_when(s) { |a, b| b > a + 1 }) # => [[1, 2], [4, 5], [7]]
29.32 slice_before, slice_after
Set.slice_before(x) { }
Set.slice_after(x) { }
An Array of Arrays cut just before (slice_before) or just after (slice_after) each element for which the block is truthy.
s = Set.of(1, 2, 3, 4)
p(Set.slice_before(s) { |x| x == 3 }) # => [[1, 2], [3, 4]]
p(Set.slice_after(s) { |x| x == 2 }) # => [[1, 2], [3, 4]]
29.33 find, detect
Set.find(x) { }
Set.detect(x) { }
The first element for which the block is truthy. When no element is, the result is nil, and --strict (level 2) reports using it unchecked.
s = Set.of(1, 2, 3)
p(Set.find(s) { |x| x > 1 }) # => 2
p(Set.detect(s) { |x| x > 9 }) # => nil
```ruby error x = Set.find(Set.of(1)) { |v| v > 0 } p(x + 1) # !> the operands may be nil
## find_index
`Set.find_index(x) { }`
The position (an Integer, from 0 in the order added) of the first element for which the block is truthy, or **nil** when there is none (reported at `--strict` level 2).
```ruby
s = Set.of("a", "b", "c")
p(Set.find_index(s) { |x| x == "c" }) # => 2
p(Set.find_index(s) { |x| x == "z" }) # => nil
29.34 any?, all?, none?, one?
Set.any?(x) { }
Set.all?(x) { }
Set.none?(x) { }
Set.one?(x) { }
True when the block is truthy for at least one element, for all of them, for none, or for exactly one. The block is required (Ruby’s block-less forms do not exist; emptiness is empty?).
s = Set.of(1, 2, 3)
p(Set.any?(s) { |x| x > 2 }) # => true
p(Set.all?(s) { |x| x > 2 }) # => false
p(Set.none?(s) { |x| x > 9 }) # => true
p(Set.one?(s) { |x| x == 1 }) # => true
```ruby error p(Set.any?(Set.of(1))) # !> Set.any? requires a block
## count
`Set.count(x) [{ }]`
Without a block, the number of elements; with one, the number of elements for which the block is truthy (an Integer).
```ruby
s = Set.of(1, 2, 3)
p(Set.count(s)) # => 3
p(Set.count(s) { |x| x > 1 }) # => 2
29.35 sum
Set.sum(x, [Integer|Float|Rational|Complex])
The sum of the elements: the initial value (0 by default) plus each element in turn. Elements and the initial value must be numbers: a String or other initial value is a type problem statically, and an element that is not a number is a TypeError at run time (String can't be coerced into Integer; Ruby’s sum("") for joining Strings does not exist; use join). The empty Set gives the initial value.
p(Set.sum(Set.of(1, 2, 3))) # => 6
p(Set.sum(Set.of(1, 2), 0.5)) # => 3.5
p(Set.sum(Set.new)) # => 0
```ruby error p(Set.sum(Set.of(“a”, “b”), “”)) # !> argument 2 must be Integer|Float|Rational|Complex, but is String
## reduce, inject
`Set.reduce(x, Any) { }`
`Set.inject(x, Any) { }`
Folds the elements: starting from the initial value, the block receives the accumulated value and an element. The initial value is **required** (Ruby's form that starts from the first element does not exist). The result type follows from the initial value and the block's result.
```ruby
p(Set.reduce(Set.of(1, 2, 3), 0) { |acc, x| acc + x }) # => 6
p(Set.inject(Set.of(1, 2, 3), 1) { |acc, x| acc * x }) # => 6
```ruby error p(Set.reduce(Set.of(1)) { |a, b| a + b }) # !> wrong number of arguments for Set.reduce (given 1, expected 2)
## min, max
`Set.min(x)`
`Set.max(x)`
The smallest or largest element. The elements must be comparable with each other: the checker rejects a Set whose element type mixes types that cannot be compared (`Set.of(1, "a")`) statically as a `type` problem, and an element that may be nil as a `nil` problem. When the checker cannot see it (`Set.of(1.0, Float.NAN)`), it is an `ArgumentError` at run time (`cannot compare the elements`). The empty Set gives **nil**, the nil of a miss, as with `Array.min`: using it unchecked is reported only at `--strict=3`.
```ruby
s = Set.of(3, 1, 2)
p(Set.min(s)) # => 1
p(Set.max(s)) # => 3
p(Set.max(Set.new)) # => nil
```ruby error p(Set.min(Set.of(1, “a”))) # !> Set.min: elements compared in order may be (Integer, String), which cannot be compared
```ruby error
p(Set.min(Set.of(1.0, Float.NAN))) # !> ArgumentError: Set.min: cannot compare the elements
29.36 minmax
Set.minmax(x)
The Tuple [min, max]. The empty Set gives [nil, nil]; those are the nil of a miss, reported only at --strict=3 when an element is used unchecked. The comparison rules are those of min and max, static check included.
lo, hi = Set.minmax(Set.of(3, 1, 2))
p([lo, hi]) # => [1, 3]
p(Set.minmax(Set.new)) # => [nil, nil]
29.37 min_by, max_by
Set.min_by(x) { }
Set.max_by(x) { }
The element for which the block’s result is smallest or largest. The block’s results must be comparable with each other: a block whose result type mixes types that cannot be compared is rejected statically (type), as with min. The empty Set gives nil, the nil of a miss (reported only at --strict=3).
s = Set.of("bb", "a", "ccc")
p(Set.min_by(s) { |x| String.size(x) }) # => "a"
p(Set.max_by(s) { |x| String.size(x) }) # => "ccc"
p(Set.max_by(Set.new) { |x| x }) # => nil
```ruby error p(Set.min_by(Set.of(1, 2)) { |x| x == 1 ? 1 : “a” }) # !> Set.min_by: elements compared in order may be (Integer, String), which cannot be compared
## minmax_by
`Set.minmax_by(x) { }`
The Tuple of the elements with the smallest and the largest block result. The empty Set gives `[nil, nil]` (the nil of a miss, level 3). The block's results are checked as for `min_by`.
```ruby
p(Set.minmax_by(Set.of(1, 2, 3)) { |x| -x }) # => [3, 1]
29.38 sort
Set.sort(x)
The Array of the elements in ascending order. The elements must be comparable with each other: a Set whose element type mixes types that cannot be compared is rejected statically (type), one whose elements may be nil as a nil problem; when the checker cannot see it (Set.of(1.0, Float.NAN)), ArgumentError at run time (cannot compare the elements).
p(Set.sort(Set.of(3, 1, 2))) # => [1, 2, 3]
```ruby error p(Set.sort(Set.of(1, “a”))) # !> Set.sort: elements compared in order may be (Integer, String), which cannot be compared
## sort_by
`Set.sort_by(x) { }`
The Array of the elements in ascending order of the block's results. A Tuple result sorts in dictionary order. The block's results must be comparable with each other; a result type that mixes types that cannot be compared is rejected statically (`type`).
```ruby
p(Set.sort_by(Set.of("bb", "a", "ccc")) { |x| String.size(x) }) # => ["a", "bb", "ccc"]
p(Set.sort_by(Set.of(1, 2, 3)) { |x| -x }) # => [3, 2, 1]
```ruby error p(Set.sort_by(Set.of(1, 2)) { |x| x == 1 ? 1 : “a” }) # !> Set.sort_by: elements compared in order may be (Integer, String), which cannot be compared
## first
`Set.first(x)`
The element added first. The empty Set gives **nil**, the nil of a miss as with `Array.first`: using it unchecked is reported only at `--strict=3`. There is no form with a count (use `take`).
```ruby
p(Set.first(Set.of(3, 1))) # => 3
p(Set.first(Set.new)) # => nil
29.39 take, drop
Set.take(x, Integer)
Set.drop(x, Integer)
The Array of the first n elements, or of the elements after the first n. A negative n is an ArgumentError.
s = Set.of(1, 2, 3, 4)
p(Set.take(s, 2)) # => [1, 2]
p(Set.drop(s, 2)) # => [3, 4]
p(Set.take(s, 9)) # => [1, 2, 3, 4]
```ruby error p(Set.take(Set.of(1), -1)) # !> ArgumentError: Set.take: attempt to take negative size
## take_while, drop_while
`Set.take_while(x) { }`
`Set.drop_while(x) { }`
The Array of the leading elements while the block is truthy, or of the rest.
```ruby
s = Set.of(1, 2, 3, 1)
p(Set.take_while(s) { |x| x < 3 }) # => [1, 2]
p(Set.drop_while(s) { |x| x < 3 }) # => [3]
29.40 to_a, entries
Set.to_a(x)
Set.entries(x)
A new Array of the elements in the order they were added. Changing the Array does not change the Set.
s = Set.of(3, 1)
a = Set.to_a(s)
Array.push(a, 9)
p(a) # => [3, 1, 9]
p(s) # => Set[3, 1]
p(Set.entries(s)) # => [3, 1]
29.41 to_set
Set.to_set(x)
Returns the Set itself (not a copy). It exists for uniformity with Array.to_set.
s = Set.of(1)
Set.add(Set.to_set(s), 2)
p(s) # => Set[1, 2]
29.42 uniq
Set.uniq(x)
The Array of the elements. A Set has no duplicates, so it is the same as to_a.
p(Set.uniq(Set.of(1, 2))) # => [1, 2]
29.43 compact
Set.compact(x)
The Array of the elements without nil.
p(Set.compact(Set.of(1, nil, 2))) # => [1, 2]
29.44 flatten
Set.flatten(x)
A new Set with the element Sets opened up recursively (Ruby’s Set#flatten). Array elements are not opened.
p(Set.flatten(Set.of(1, Set.of(2, Set.of(3))))) # => Set[1, 2, 3]
p(Set.flatten(Set.of(1, 2))) # => Set[1, 2]
29.45 zip
Set.zip(x, *Array)
The Array of Tuples pairing each element with the elements at the same position of each Array. A shorter Array contributes nil. The arguments must be Arrays (a Set is a type problem statically).
p(Set.zip(Set.of(1, 2, 3), Any["a", "b"])) # => [[1, "a"], [2, "b"], [3, nil]]
p(Set.zip(Set.of(1, 2))) # => [[1], [2]]
29.46 join
Set.join(x, [String])
The String of the elements’ to_s joined, with the separator (“” by default) between them. Array elements are joined through recursively.
s = Set.of(1, 2, 3)
p(Set.join(s)) # => "123"
p(Set.join(s, ", ")) # => "1, 2, 3"
30. Time
A Time is one instant: the seconds since 1970-01-01 00:00:00 UTC (to rational precision) and the UTC offset it is shown in. There is no literal; Time.now, Time.at(seconds) and Time.new(year, month, day, ...) make one (Values and types). The values are Ruby’s Times, but where Ruby’s t.utc and t.localtime change t itself, every operation in Sake returns a new Time and leaves its subject alone. Time has no typed Array Time[]; a sequence of Times is an Any[...].
Zones. The in: keyword and the zone argument of getlocal and new are fixed UTC offsets: "+09:00", "-05:00", "UTC" or "Z", a military letter "A".."I", "K".."Z", or an Integer number of seconds. A region name such as "Asia/Tokyo" is not accepted (ArgumentError). Without in: the local zone of the machine applies, so every example whose output must not depend on the machine passes in:. A Time made with "UTC" or "Z" is in UTC mode (utc? is true and to_s ends in UTC); one made with "+00:00" is an ordinary Time at offset 0 (utc? is false, to_s ends in +0000).
The operators on Times are + and - (with a number of seconds on the right, Integer, Float or Rational; Time - Time is the difference in seconds as a Float), ==, !=, <, <=, >, >= and <=> (with a Time on the right). The entries Time.+(x, y) and so on below are their function forms (Operators and indexing). Time.strftime(t, fmt) and Time.iso8601(t) make Strings. Reading a Time from a String (Ruby’s Time.parse, Time.iso8601(s)) is not built in.
30.1 now
Time.now([in: String|Integer])
The current time (a Time). With in: it is shown at that offset, without it in the local zone. A bad zone is an ArgumentError.
t = Time.now(in: "UTC")
p(Time.utc?(t)) # => true
p(Time.year(t) >= 2026) # => true
p(Time.utc_offset(Time.now(in: "+09:00"))) # => 32400
30.2 at
Time.at(Integer|Float|Rational|Time, [in: String|Integer])
The Time seconds after 1970-01-01 00:00:00 UTC (Ruby’s Time.at). An Integer, a Float or a Rational; a Rational keeps its precision. Given a Time, a Time of the same instant (and the same zone when in: is absent). in: picks the zone; a bad one is an ArgumentError.
t = Time.at(0, in: "UTC")
p(t) # => 1970-01-01 00:00:00 UTC
p(Time.at(1.5, in: "UTC")) # => 1970-01-01 00:00:01.5 UTC
p(Time.subsec(Time.at(1r/3, in: "UTC"))) # => (1/3)
p(Time.at(t, in: "+09:00")) # => 1970-01-01 09:00:00 +0900
p(Time.at(t) == t) # => true
```ruby error Time.at(0, in: “Asia/Tokyo”) # !> ArgumentError: Time.at: “+HH:MM”, “-HH:MM”, “UTC” or “A”..”I”,”K”..”Z” expected for utc_offset: Asia/Tokyo
## new
`Time.new(Integer, [Integer], [Integer], [Integer], [Integer], [Integer|Float|Rational], [String|Integer], [in: String|Integer])`
A Time from year, month, day, hour, minute, second and zone (Ruby's `Time.new(y, m, d, h, min, s, zone)`). The year is required, the rest may be left out (month and day default to 1, hour, minute and second to 0). The second may be a Float or a Rational with a fraction. The zone is either the seventh argument or `in:`, not both (`ArgumentError`), and the local zone when neither is given. A month outside 1..12, an hour outside 0..23 and so on are an `ArgumentError` (`mon out of range`). Ruby's `Time.new` without arguments (the current time) does not exist here: use `Time.now`.
```ruby
t = Time.new(2024, 2, 29, 12, 30, 45, "+09:00")
puts(Time.to_s(t)) # => 2024-02-29 12:30:45 +0900
puts(Time.to_s(Time.new(2024, in: "UTC"))) # => 2024-01-01 00:00:00 UTC
puts(Time.to_s(Time.new(2024, 3, 4, 5, in: "UTC"))) # => 2024-03-04 05:00:00 UTC
p(Time.subsec(Time.new(2024, 1, 1, 0, 0, 7.5, in: "Z"))) # => (1/2)
puts(Time.to_s(Time.new(2024, 3, 4, 5, 6, 7, 3600))) # => 2024-03-04 05:06:07 +0100
```ruby error Time.new(2024, 13, 1, in: “UTC”) # !> ArgumentError: Time.new: mon out of range
## year, month, mon, day, mday, hour, min, sec
`Time.year(x)`
`Time.month(x)`
`Time.mon(x)`
`Time.day(x)`
`Time.mday(x)`
`Time.hour(x)`
`Time.min(x)`
`Time.sec(x)`
The parts of the time (Integers), read at the Time's own offset: the year, the month (1..12; `mon` is an alias), the day of the month (1..31; `mday` is an alias), the hour (0..23), the minute (0..59), the second (0..60, without its fraction, see `subsec`).
```ruby
t = Time.new(2024, 2, 29, 12, 30, 45.5, in: "+09:00")
p([Time.year(t), Time.month(t), Time.day(t)]) # => [2024, 2, 29]
p([Time.hour(t), Time.min(t), Time.sec(t)]) # => [12, 30, 45]
p(Time.mon(t) == Time.month(t)) # => true
p(Time.hour(Time.utc(t))) # => 3
30.3 wday, yday
Time.wday(x)
Time.yday(x)
The day of the week (an Integer, Sunday 0 to Saturday 6) and the day of the year (January 1 is 1, December 31 of a leap year 366).
t = Time.new(2024, 2, 29, in: "UTC")
p(Time.wday(t)) # => 4
p(Time.yday(t)) # => 60
p(Time.wday(Time.at(0, in: "UTC"))) # => 4
30.4 sunday?, monday?, tuesday?, wednesday?, thursday?, friday?, saturday?
Time.sunday?(x)
Time.monday?(x)
Time.tuesday?(x)
Time.wednesday?(x)
Time.thursday?(x)
Time.friday?(x)
Time.saturday?(x)
True on that day of the week. Time.wday(t) == 4 and Time.thursday?(t) say the same.
t = Time.new(2024, 2, 29, in: "UTC")
p(Time.thursday?(t)) # => true
p(Time.friday?(t)) # => false
p(Time.sunday?(t + 3 * 86400)) # => true
30.5 nsec, usec, subsec, tv_sec, tv_nsec, tv_usec
Time.nsec(x)
Time.usec(x)
Time.subsec(x)
Time.tv_sec(x)
Time.tv_nsec(x)
Time.tv_usec(x)
The part finer than a second. nsec (alias tv_nsec) is the nanoseconds (an Integer, 0..999999999), usec (alias tv_usec) the microseconds (an Integer, 0..999999), subsec the fraction of the second as it is (the Integer 0, or a Rational). tv_sec is to_i, the whole seconds since 1970.
t = Time.at(1700000000.123456789r, in: "UTC")
p(Time.nsec(t)) # => 123456789
p(Time.usec(t)) # => 123456
p(Time.subsec(Time.at(1.5, in: "UTC"))) # => (1/2)
p(Time.subsec(Time.at(2, in: "UTC"))) # => 0
p(Time.tv_sec(t)) # => 1700000000
p(Time.tv_nsec(t) == Time.nsec(t)) # => true
30.6 to_i, to_f, to_r
Time.to_i(x)
Time.to_f(x)
Time.to_r(x)
The seconds since 1970-01-01 00:00:00 UTC as an Integer (the fraction dropped), a Float, or an exact Rational. The zone plays no part. They invert Time.at.
t = Time.at(1.5, in: "UTC")
p(Time.to_i(t)) # => 1
p(Time.to_f(t)) # => 1.5
p(Time.to_r(t)) # => (3/2)
p(Time.to_i(Time.at(100, in: "+09:00"))) # => 100
30.7 to_a
Time.to_a(x)
Ruby’s t.to_a: the ten-element Tuple [sec, min, hour, day, month, year, wday, yday, isdst, zone]. The last element is Time.zone: "UTC" for a UTC Time, nil for a Time at a fixed offset such as in: "+09:00". Its type is String | nil, so using it unchecked as a String is a nil problem under --strict (level 2); the other nine are Integers and a Boolean.
p(Time.to_a(Time.at(0, in: "UTC"))) # => [0, 0, 0, 1, 1, 1970, 4, 1, false, "UTC"]
p(Time.to_a(Time.at(0, in: "+09:00"))) # => [0, 0, 9, 1, 1, 1970, 4, 1, false, nil]
sec, min, hour, day, month, year, wday, yday, isdst, zone = Time.to_a(Time.at(0, in: "UTC"))
p(zone ? String.size(zone) : 0) # => 3
```ruby error zone = Time.to_a(Time.at(0, in: “+09:00”))[9] p(String.size(zone)) # !> String.size: argument 1 may be nil
## to_s
`Time.to_s(x)`
The String `"2024-02-29 12:30:45 +0900"` (Ruby's `t.to_s`). A Time in UTC mode ends in `UTC`. The fraction of the second is not written (`p(t)` and `inspect` write it). `puts(t)` and `"#{t}"` use this form.
```ruby
t = Time.new(2024, 2, 29, 12, 30, 45.5, in: "+09:00")
puts(Time.to_s(t)) # => 2024-02-29 12:30:45 +0900
puts(Time.utc(t)) # => 2024-02-29 03:30:45 UTC
p(Time.at(0, in: "+00:00")) # => 1970-01-01 00:00:00 +0000
30.8 strftime
Time.strftime(x, String)
A String in the format of Ruby’s strftime: %Y year, %m month, %d day, %H, %M, %S hour, minute, second, %F (%Y-%m-%d), %T (%H:%M:%S), %z (+0900), %:z (+09:00), %Z (the zone name, empty for a Time at a fixed offset), %A, %a weekday names, %B, %b month names, %j day of the year, %s seconds since 1970, %L milliseconds, %N nanoseconds (%3N for a digit count), - as in %-d for no zero padding, %% for %. An unknown directive is left as it is.
t = Time.new(2024, 2, 29, 12, 30, 45, in: "+09:00")
puts(Time.strftime(t, "%Y-%m-%d %H:%M:%S %z")) # => 2024-02-29 12:30:45 +0900
puts(Time.strftime(t, "%F %T %:z")) # => 2024-02-29 12:30:45 +09:00
puts(Time.strftime(t, "%A %-d %B, day %j")) # => Thursday 29 February, day 060
puts(Time.strftime(t, "%s|%L|%Z|%%")) # => 1709177445|000||%
puts(Time.strftime(Time.utc(t), "%Z %z")) # => UTC +0000
30.9 iso8601
Time.iso8601(x, [Integer])
The ISO 8601 String 2024-02-29T12:30:45+09:00 (Ruby’s t.iso8601, xmlschema). A Time in UTC mode ends in Z. With n, the fraction of the second is written with n digits (default 0). It does not parse (Ruby’s class method Time.iso8601(s)).
t = Time.new(2024, 2, 29, 12, 30, 45.5, in: "+09:00")
puts(Time.iso8601(t)) # => 2024-02-29T12:30:45+09:00
puts(Time.iso8601(t, 3)) # => 2024-02-29T12:30:45.500+09:00
puts(Time.iso8601(Time.utc(t))) # => 2024-02-29T03:30:45Z
30.10 asctime, ctime
Time.asctime(x)
Time.ctime(x)
The form of C’s asctime, "Thu Feb 29 12:30:45 2024" (Ruby’s t.asctime and t.ctime, which are the same). The zone is not written.
t = Time.new(2024, 2, 29, 12, 30, 45, in: "+09:00")
puts(Time.asctime(t)) # => Thu Feb 29 12:30:45 2024
p(Time.ctime(t) == Time.asctime(t)) # => true
30.11 utc, getutc, gmtime, getgm, gm
Time.utc(Time|Integer, [Integer], [Integer], [Integer], [Integer], [Integer|Float|Rational])
Time.getutc(x)
Time.gmtime(x)
Time.getgm(x)
Time.gm(Integer, [Integer], [Integer], [Integer], [Integer], [Integer|Float|Rational])
Given a Time, a new Time showing the same instant in UTC mode (Ruby’s t.getutc). The four are the same operation, and none changes its subject as Ruby’s t.utc and t.gmtime do. The result has utc? true, zone "UTC", and is == to the original. Given a year (and the parts after it, as new), Time.utc and Time.gm make that time in UTC (Ruby’s class methods Time.utc(y, m, d, h, min, s) and Time.gm); a Time with more arguments is an ArgumentError.
t = Time.new(2024, 2, 29, 12, 30, 45, in: "+09:00")
u = Time.utc(t)
puts(Time.to_s(u)) # => 2024-02-29 03:30:45 UTC
puts(Time.to_s(t)) # => 2024-02-29 12:30:45 +0900
p(Time.utc?(u)) # => true
puts(Time.utc(2024, 2, 29, 3, 30, 45)) # => 2024-02-29 03:30:45 UTC
puts(Time.gm(2024)) # => 2024-01-01 00:00:00 UTC
p(u == t) # => true
p(Time.getutc(t) == Time.gmtime(t) && Time.getgm(t) == u) # => true
30.12 localtime, getlocal
Time.localtime(x, [String|Integer])
Time.getlocal(x, [String|Integer])
A new Time showing the same instant at another offset (Ruby’s t.getlocal(zone)). The zone is "+09:00", "UTC", an Integer of seconds and so on (see above); without it, the machine’s local zone. The two are the same operation, and neither changes its subject as Ruby’s t.localtime does. With "UTC" the result is in UTC mode.
t = Time.at(1700000000, in: "+09:00")
puts(Time.to_s(Time.getlocal(t, "-05:00"))) # => 2023-11-14 17:13:20 -0500
puts(Time.to_s(Time.localtime(t, 3600))) # => 2023-11-14 23:13:20 +0100
p(Time.utc?(Time.getlocal(t, "UTC"))) # => true
p(Time.getlocal(t) == t) # => true
puts(Time.to_s(t)) # => 2023-11-15 07:13:20 +0900
30.13 utc?, gmt?
Time.utc?(x)
Time.gmt?(x)
True for a Time in UTC mode (made with "UTC" or "Z", or by Time.utc). A Time made with "+00:00" is at offset 0 but gives false. gmt? is an alias.
p(Time.utc?(Time.at(0, in: "UTC"))) # => true
p(Time.utc?(Time.at(0, in: "+00:00"))) # => false
p(Time.gmt?(Time.utc(Time.at(0, in: "+09:00")))) # => true
30.14 zone
Time.zone(x)
The zone’s name (a String) or nil: "UTC" in UTC mode, the abbreviation of the local zone for a local Time ("JST" and the like, depending on the machine), and nil for a Time made with a fixed offset such as "+09:00", which has no name. The result is String | nil, so passing it unchecked to a String operation is stopped by --strict (level 2). For the offset itself use utc_offset.
p(Time.zone(Time.at(0, in: "UTC"))) # => "UTC"
p(Time.zone(Time.at(0, in: "+09:00"))) # => nil
30.15 utc_offset, gmt_offset, gmtoff
Time.utc_offset(x)
Time.gmt_offset(x)
Time.gmtoff(x)
The offset from UTC in seconds (an Integer: 32400 for +09:00, 0 for UTC). The three are aliases.
t = Time.at(0, in: "+09:00")
p(Time.utc_offset(t)) # => 32400
p(Time.gmt_offset(Time.at(0, in: "-05:00"))) # => -18000
p(Time.gmtoff(Time.utc(t))) # => 0
30.16 dst?, isdst
Time.dst?(x)
Time.isdst(x)
True during daylight saving time (Ruby’s t.dst?). Always false for a Time at a fixed offset or in UTC; only a Time in the local zone can give true. isdst is an alias.
p(Time.dst?(Time.at(0, in: "UTC"))) # => false
p(Time.isdst(Time.at(0, in: "+09:00"))) # => false
30.17 round, floor, ceil
Time.round(x, [Integer])
Time.floor(x, [Integer])
Time.ceil(x, [Integer])
A new Time with the fraction of the second rounded to n digits (default 0), Ruby’s t.round(n): round to the nearest, floor down, ceil up. A negative n is an ArgumentError.
t = Time.at(1700000000.123456789r, in: "UTC")
puts(Time.iso8601(Time.round(t, 2), 9)) # => 2023-11-14T22:13:20.120000000Z
puts(Time.iso8601(Time.floor(t, 1), 9)) # => 2023-11-14T22:13:20.100000000Z
puts(Time.iso8601(Time.ceil(t), 9)) # => 2023-11-14T22:13:21.000000000Z
p(Time.round(Time.at(1.5, in: "UTC")) == Time.at(2, in: "UTC")) # => true
30.18 +, -
Time.+(x, Any)
Time.-(x, Any)
t + seconds and t - seconds are new Times that many seconds (an Integer, Float or Rational) later or earlier, in t’s zone. t1 - t2 is the difference of two Times in seconds, a Float. Time + Time and a right operand that is not a number of seconds are type problems statically. There are no day or month units: a day is written 86400.
a = Time.at(100, in: "UTC")
p(a + 5) # => 1970-01-01 00:01:45 UTC
p(a - 1.5) # => 1970-01-01 00:01:38.5 UTC
p((a + 5) - a) # => 5.0
p(a - (a + 5)) # => -5.0
p(Time.-(a, 1r/4) == Time.at(99.75, in: "UTC")) # => true
d = Time.new(2024, 3, 10, in: "UTC") - Time.new(2024, 1, 1, in: "UTC")
p(Integer(d / 86400)) # => 69
```ruby error a = Time.at(0, in: “UTC”) p(a + a) # !> the operands are (Time, Time), which the left operand’s type does not support
## ==, !=
`Time.==(x, Any)`
`Time.!=(x, Any)`
True for the same instant (`!=` is the negation). The zone is not compared: `Time.at(100, in: "UTC")` and `Time.at(100, in: "+09:00")` are equal. A right operand that is not a Time (nil, an Integer, ...) gives false for `==` and true for `!=`, in the operator form `t == v` and in the function form `Time.==(t, v)` alike: the function forms accept any value and never raise.
```ruby
a = Time.at(100, in: "UTC")
p(a == Time.at(100, in: "+09:00")) # => true
p(a != a + 1) # => true
p(a == nil) # => false
p(Time.==(a, Time.at(100.0, in: "UTC"))) # => true
p(Time.==(a, 100)) # => false
p(Time.!=(a, 100)) # => true
30.19 <, <=, >, >=
Time.<(x, Any)
Time.<=(x, Any)
Time.>(x, Any)
Time.>=(x, Any)
Compare two instants (the earlier is smaller). The right operand must be a Time; an Integer or the like is a type problem statically, in the function form Time.<(t, 100) too (unlike ==, which just gives false).
a = Time.at(100, in: "UTC")
b = a + 5
p(a < b) # => true
p(a <= a) # => true
p(b > a && b >= a) # => true
p(Time.<(b, a)) # => false
```ruby error a = Time.at(100, in: “UTC”) p(a < 100) # !> the operands are (Time, Integer), which the left operand’s type does not support
## <=>
`Time.<=>(x, Any)`
The result of the comparison as -1, 0 or 1 (an Integer; two Times can always be compared, so it is never nil). A Time as the key of `Array.sort` or `Array.sort_by` sorts in time order.
```ruby
a = Time.at(100, in: "UTC")
p(a <=> a + 1) # => -1
p(a <=> a) # => 0
p(Time.<=>(a + 1, a)) # => 1
xs = Array.sort(Any[a + 9, a, a + 3])
p(Array.map(xs) { |t| Time.to_i(t) }) # => [100, 103, 109]
31. IO
IO is the type of open files and of the program’s standard streams. Its values come from operations: IO.stdin, IO.stdout, and IO.stderr give the standard streams, File.open(path, mode) gives a file (Sake has no $stdout or STDOUT, see Values and types). Files and streams share one type, so a function that takes an IO writes to either. There is no IO literal.
Ruby’s io.puts(x) is written IO.puts(io, x). Of Ruby’s IO methods only those listed here exist. A failing read or write (a closed IO, an IO not opened for that direction, an error from the OS) is an IOError (Ruby’s Errno::ENOENT and the like are all IOError, see Exceptions).
The only operators on IO are == and != (equal when the two values are the same stream or the same result of File.open). An operation without arguments, IO.stdout(), can also be written IO.stdout.
31.1 stdin, stdout, stderr
IO.stdin()
IO.stdout()
IO.stderr()
The program’s standard input, standard output, and standard error as IO values. Every call gives the same value: IO.stdout == IO.stdout is true. Kernel.puts and Kernel.p write to IO.stdout; Kernel.gets reads from IO.stdin. Writing to IO.stderr flushes stdout first, so the lines of the two streams keep their order. Closing a standard stream with IO.close is allowed but rarely useful: afterwards every write to it (p, puts, IO.write) is an IOError (closed stream), which can be rescued; IO.stderr keeps working.
out = IO.stdout
IO.puts(out, "hello") # => hello
p(IO.stdout == out) # => true
p(IO.stdout == IO.stderr) # => false
p(IO.stdin) # => #<IO:<STDIN>>
```ruby error IO.close(IO.stdout) puts(“x”) # !> IOError: Kernel.puts: closed stream
## puts
`IO.puts(x, *Any)`
Writes each argument to `x` as `Kernel.puts` does and returns nil. One line per argument (a newline is added unless the text ends with one); an Array or Tuple is flattened to one line per element, an empty Array is an empty line, and no arguments give one empty line. Values other than Strings are written as `Kernel.to_s` shows them (nil as an empty line, a Symbol as its name). A closed or read-only IO is an `IOError`.
```ruby
out = IO.stdout
IO.puts(out, "a", 1) # => a
# => 1
IO.puts(out, [1, Any[2, 3]]) # => 1
# => 2
# => 3
IO.puts(out, "x\n") # => x
p(IO.puts(out, :s)) # => s
# => nil
31.2 print
IO.print(x, *Any)
Writes the to_s of each argument to x without newlines and returns nil (Ruby’s io.print). An Array is written as [1, 2], nil as an empty string.
out = IO.stdout
IO.print(out, "a", 1, :s, "\n") # => a1s
IO.print(out, Any[1, 2], "\n") # => [1, 2]
31.3 write
IO.write(x, String)
Writes the string to x as it is and returns the number of bytes written (an Integer). No newline is added. The argument must be a String; another type is a type problem statically (unlike puts and print, nothing is converted).
p(IO.write(IO.stdout, "ab\n")) # => ab
# => 3
p(IO.write(IO.stdout, "日本\n")) # => 日本
# => 7
```ruby error IO.write(IO.stdout, 1) # !> IO.write: argument 2 must be String, but is Integer
## flush
`IO.flush(x)`
Writes out buffered output and returns `x`. `IO.write` and `IO.puts` to a file from `File.open` are buffered; the data reaches the file on `flush` or `close`.
```ruby
f = File.open("log.txt", "w")
IO.puts(f, "a")
p(File.read("log.txt")) # => ""
IO.flush(f)
p(File.read("log.txt")) # => "a\n"
IO.close(f)
31.4 gets
IO.gets(x)
The next line including its newline (Ruby’s io.gets), or nil at the end. The result is String | nil, so --strict (level 2) reports using it unchecked as a nil problem: test it first, as if line or while (line = IO.gets(io)). A write-only IO is an IOError.
io = IO.stdin
line = IO.gets(io)
if line
p(String.to_i(line)) # => 3
end
while (l = IO.gets(io))
p(l) # => "1 2\n"
end
p(IO.gets(io)) # => nil
```ruby error line = IO.gets(IO.stdin) p(String.upcase(line)) # !> argument 1 may be nil
## read
`IO.read(x, [Integer])`
`IO.read(io)` reads everything from the current position to the end and returns a String (at the end it is the empty string `""`, never nil). `IO.read(io, n)` reads up to `n` bytes (bytes, not characters) and returns nil when the end has been reached (with `n` 0 it is always `""`). Only this two-argument form is `String | nil` and checked at level 2. An IO not opened for reading is an `IOError`.
```ruby
io = IO.stdin
p(IO.read(io, 2)) # => "3\n"
p(IO.read(io)) # => "1 2\n"
p(IO.read(io)) # => ""
p(IO.read(io, 1)) # => nil
31.5 getc
IO.getc(x)
The next character as a String, or nil at the end (String | nil, level 2).
io = IO.stdin
p(IO.getc(io)) # => "3"
p(IO.getc(io)) # => "\n"
IO.read(io)
p(IO.getc(io)) # => nil
31.6 readlines
IO.readlines(x)
The rest, from the current position to the end, as an Array of Strings, one per line (each keeps its newline). At the end it is an empty Array.
p(IO.readlines(IO.stdin)) # => ["3\n", "1 2\n"]
p(IO.readlines(IO.stdin)) # => []
31.7 each_line
IO.each_line(x) { }
Passes each line (a String with its newline) from the current position to the end to the block and returns x. The block’s value is not used. As Ruby’s io.each_line; the block is required.
r = IO.each_line(IO.stdin) { |l| p(String.chomp(l)) } # => "3"
# => "1 2"
p(r == IO.stdin) # => true
31.8 eof?
IO.eof?(x)
True when the read position is at the end. A write-only or closed IO is an IOError.
io = IO.stdin
p(IO.eof?(io)) # => false
IO.read(io)
p(IO.eof?(io)) # => true
31.9 seek
IO.seek(x, Integer, [Integer|Symbol])
Moves the read/write position and returns 0 (Ruby’s io.seek). The second argument is the offset, the third the origin, the start of the file (0) when omitted: 0/:SET counts from the start, 1/:CUR from the current position, 2/:END from the end (with a negative offset). Only these three Symbols exist; another Symbol is an ArgumentError. A stream that cannot seek (IO.stdin as a pipe) is an IOError.
File.write("a.txt", "hello\n")
f = File.open("a.txt")
IO.seek(f, 2)
p(IO.read(f, 2)) # => "ll"
IO.seek(f, -2, :END)
p(IO.read(f)) # => "o\n"
p(IO.seek(f, 1, :SET)) # => 0
p(IO.getc(f)) # => "e"
IO.close(f)
```ruby error File.write(“a.txt”, “hello\n”) f = File.open(“a.txt”) IO.seek(f, 1, :FOO) # !> ArgumentError: IO.seek: unknown whence: :FOO (0/1/2 or :SET/:CUR/:END)
## pos
`IO.pos(x)`
The current position (bytes from the start, an Integer). A stream that cannot seek is an `IOError`.
```ruby
File.write("a.txt", "日本\n")
f = File.open("a.txt")
p(IO.pos(f)) # => 0
IO.getc(f)
p(IO.pos(f)) # => 3
IO.close(f)
31.10 rewind
IO.rewind(x)
Moves the position back to the start and returns 0, to read the file again.
File.write("a.txt", "one\n")
f = File.open("a.txt")
p(IO.gets(f)) # => "one\n"
p(IO.rewind(f)) # => 0
p(IO.gets(f)) # => "one\n"
IO.close(f)
31.11 size
IO.size(x)
The size of the open file in bytes (an Integer), including writes not yet flushed. An IO that is not a file (a stream such as IO.stdin) is an IOError (not a file).
f = File.open("a.txt", "w")
IO.write(f, "abc")
p(IO.size(f)) # => 3
IO.close(f)
```ruby error IO.size(IO.stdin) # !> IOError: IO.size: not a file
## truncate
`IO.truncate(x, Integer)`
Cuts the file to `n` bytes and returns 0. A file not opened for writing is an `IOError`.
```ruby
File.write("a.txt", "hello")
r = File.open("a.txt", "r+") { |f| IO.truncate(f, 3) }
p(r) # => 0
p(File.read("a.txt")) # => "hel"
```ruby error File.write(“a.txt”, “hello”) f = File.open(“a.txt”) IO.truncate(f, 1) # !> IOError: IO.truncate: not opened for writing
## close
`IO.close(x)`
Closes the IO and returns nil. Buffered output is written out. Closing a closed IO does nothing (as in Ruby). Reading or writing a closed IO is an `IOError`. The block form of `File.open` closes the file after the block, so `close` is needed only for a file opened by hand.
```ruby
f = File.open("a.txt", "w")
IO.write(f, "x")
p(IO.close(f)) # => nil
p(IO.close(f)) # => nil
p(File.read("a.txt")) # => "x"
```ruby error f = File.open(“a.txt”, “w”) IO.close(f) IO.write(f, “x”) # !> IOError: IO.write: closed stream
## closed?
`IO.closed?(x)`
True when the IO is closed. The IO passed to the block of `File.open` is closed after the block.
```ruby
kept = IO.stdout
File.open("a.txt", "w") { |f| kept = f }
p(IO.closed?(kept)) # => true
p(IO.closed?(IO.stdout)) # => false
31.12 tty?
IO.tty?(x)
True when the IO is a terminal (Ruby’s io.tty?); false for a file or a pipe. Under the checker the standard streams are pipes, so it is false there too. The terminal operations below (winsize, raw, noecho, getch) work only when this is true.
f = File.open("a.txt", "w")
p(IO.tty?(f)) # => false
IO.close(f)
31.13 winsize
IO.winsize(x)
The size of the terminal that x is connected to, as the Tuple [rows, columns] (Integer, Integer; Ruby’s io.winsize of io/console). Take it apart with rows, cols = IO.winsize(IO.stdout). An IO that is not a terminal (a file, a pipe, the standard streams under the checker) is an IOError (not a terminal). In a terminal:
rows, cols = IO.winsize(IO.stdout)
p([rows, cols]) # [24, 80] for an 80x24 terminal
Under the checker stdin is a pipe, so only the error can be shown:
begin
IO.winsize(IO.stdin)
rescue IOError => e
p(Exception.message(e)) # => "not a terminal"
end
31.14 raw, noecho
IO.raw(x) { }
IO.noecho(x) { }
Run the block with the terminal of x in raw mode (raw: a key is delivered at once, without waiting for a line, and is not echoed) or with echo off (noecho: lines are still read as lines, but what is typed is not shown), restore the terminal afterwards (also when the block raises), and return the block’s value (Ruby’s io.raw { } and io.noecho { }). The result has the block’s type. The block is required and takes no parameters. An IO that is not a terminal is an IOError (not a terminal). In a terminal, a password is read without showing it, and one key without waiting for Enter:
pw = IO.noecho(IO.stdin) { IO.gets(IO.stdin) } # "secret\n" after typing secret and Enter
k = IO.raw(IO.stdin) { IO.getch(IO.stdin) } # "x" as soon as x is pressed
begin
IO.noecho(IO.stdin) { IO.gets(IO.stdin) }
rescue IOError => e
p(Exception.message(e)) # => "not a terminal"
end
31.15 getch
IO.getch(x)
Reads one key from the terminal of x and returns it as a String, without waiting for Enter and without echo (Ruby’s io.getch). The result is String | nil (nil at the end of input), so --strict (level 2) reports using it unchecked as a nil problem. An IO that is not a terminal is an IOError (not a terminal); to read a byte from a pipe or a file use IO.getc. In a terminal:
k = IO.getch(IO.stdin) # "y" as soon as y is pressed
begin
IO.getch(IO.stdin)
rescue IOError => e
p(Exception.message(e)) # => "not a terminal"
end
31.16 ==, !=
IO.==(x, Any)
IO.!=(x, Any)
Two IO values are equal when they are the same stream or the same result of File.open (!= is the negation). Opening the same path twice gives two different values. A value that is not an IO is never equal.
f = File.open("a.txt", "w")
g = File.open("a.txt", "w")
p(f == f) # => true
p(f == g) # => false
p(IO.stdout != IO.stdin) # => true
IO.close(f)
IO.close(g)
32. File
File is the group of operations that work on a file by its path (a String). There is no value of type File: File.open returns an IO (IO), and the other operations take a path and read, write, or query the file on the spot. They have the names of Ruby’s class methods, File.read(path) and so on; what Ruby does with instance methods (file.read) is an IO operation (Values and types, Built-ins).
A failing system call (a missing path, permissions, reading a directory; Ruby’s Errno::ENOENT and the like) is an IOError with Ruby’s message (Exceptions). The path operations (basename, join, expand_path, …) do not look at the file system. The predicates (exist?, …) never fail; they return false.
File has no operators. The examples run in an empty directory and create files there.
32.1 open
File.open(String, [String], [Integer]) [{ }]
Opens the file and returns an IO. The second argument is the mode, "r" when omitted (as Ruby’s: "r" read, "w" write (truncating), "a" append, "r+" read and write, "w+", "a+", and "b" for binary). The third argument is the permission bits for a newly created file (0o600; an existing file is not changed). With a block, the IO is passed to the block, the file is closed after it, and the block’s value is returned (as in Ruby). Without a block, close it with IO.close. A missing or unreadable file is an IOError; an unknown mode is an ArgumentError.
f = File.open("a.txt", "w")
IO.puts(f, "one")
IO.close(f)
n = File.open("a.txt", "a") { |io| IO.write(io, "two\n") }
p(n) # => 4
p(File.open("a.txt") { |io| IO.readlines(io) }) # => ["one\n", "two\n"]
File.open("p.txt", "w", 0o600) { |io| nil }
p(File.exist?("p.txt")) # => true
```ruby error File.open(“nope.txt”) # !> IOError: File.open: No such file or directory
## read
`File.read(String)`
The whole file as a String. A missing file or a directory is an `IOError`.
```ruby
File.write("a.txt", "hello\n")
p(File.read("a.txt")) # => "hello\n"
```ruby error File.read(“nope.txt”) # !> IOError: File.read: No such file or directory
## readlines
`File.readlines(String)`
The whole file as an Array of Strings, one per line (each keeps its newline). An empty file gives an empty Array.
```ruby
File.write("a.txt", "a\nb\n")
p(File.readlines("a.txt")) # => ["a\n", "b\n"]
32.2 write
File.write(String, String)
Creates the file (or replaces its content) with the string and returns the number of bytes written (an Integer). It does not append (append with File.open(path, "a")). The second argument must be a String; another type is a type problem statically.
p(File.write("a.txt", "日本")) # => 6
p(File.write("a.txt", "x")) # => 1
p(File.read("a.txt")) # => "x"
```ruby error File.write(“a.txt”, 1) # !> File.write: argument 2 must be String, but is Integer
## exist?
`File.exist?(String)`
True when a file or directory is at the path. It never fails.
```ruby
File.write("a.txt", "")
p(File.exist?("a.txt")) # => true
p(File.exist?(".")) # => true
p(File.exist?("nope")) # => false
32.3 file?, directory?, symlink?
File.file?(String)
File.directory?(String)
File.symlink?(String)
True for a regular file, a directory, or a symbolic link. file? and directory? follow links and look at the target. A missing path is false.
File.write("a.txt", "")
File.symlink("a.txt", "l1.txt")
p(File.file?("a.txt")) # => true
p(File.file?(".")) # => false
p(File.directory?(".")) # => true
p(File.symlink?("l1.txt")) # => true
p(File.symlink?("a.txt")) # => false
p(File.file?("nope")) # => false
32.4 zero?, empty?
File.zero?(String)
File.empty?(String)
True when the file exists and has size 0 (the two are the same, as in Ruby). A missing path is false.
File.write("e.txt", "")
File.write("a.txt", "x")
p(File.zero?("e.txt")) # => true
p(File.empty?("a.txt")) # => false
p(File.zero?("nope")) # => false
32.5 readable?, writable?, executable?
File.readable?(String)
File.writable?(String)
File.executable?(String)
True when this process can read, write, or execute the path. A missing path is false.
File.write("a.txt", "")
p(File.readable?("a.txt")) # => true
p(File.writable?("a.txt")) # => true
p(File.executable?("a.txt")) # => false
File.chmod(0o755, "a.txt")
p(File.executable?("a.txt")) # => true
32.6 size
File.size(String)
The size of the file in bytes (an Integer). A missing path is an IOError.
File.write("a.txt", "日本")
p(File.size("a.txt")) # => 6
```ruby error File.size(“nope”) # !> IOError: File.size: No such file or directory
## mtime, atime
`File.mtime(String)`
`File.atime(String)`
The time of the last modification or the last access, as a Time. A missing path is an `IOError`.
```ruby
File.write("a.txt", "")
File.utime(Time.at(0), Time.at(60), "a.txt")
p(Time.to_i(File.atime("a.txt"))) # => 0
p(Time.to_i(File.mtime("a.txt"))) # => 60
p(File.mtime("a.txt") <= Time.now) # => true
32.7 ftype
File.ftype(String)
The kind as a String: "file", "directory", "link" (the link itself; not followed), "characterSpecial", and the others as in Ruby. A missing path is an IOError.
File.write("a.txt", "")
File.symlink("a.txt", "l2.txt")
p(File.ftype("a.txt")) # => "file"
p(File.ftype(".")) # => "directory"
p(File.ftype("l2.txt")) # => "link"
32.8 identical?
File.identical?(String, String)
True when the two paths name the same file (links are followed; a hard link counts as the same). False when either is missing.
File.write("a.txt", "")
File.write("b.txt", "")
File.symlink("a.txt", "l3.txt")
p(File.identical?("a.txt", "l3.txt")) # => true
p(File.identical?("a.txt", "b.txt")) # => false
p(File.identical?("a.txt", "nope")) # => false
32.9 rename
File.rename(String, String)
Moves (renames) the file and returns 0. An existing file at the new path is replaced. A missing source is an IOError.
File.write("a.txt", "x")
p(File.rename("a.txt", "b.txt")) # => 0
p(File.exist?("a.txt")) # => false
p(File.read("b.txt")) # => "x"
```ruby error File.rename(“nope”, “b.txt”) # !> IOError: File.rename: No such file or directory
## delete, unlink
`File.delete(String)`
`File.unlink(String, *String)`
Remove files. `delete` takes one path and returns 1; `unlink` takes one or more and returns how many it removed (an Integer). A directory cannot be removed this way (`Dir.rmdir`). A missing path is an `IOError` (Ruby's `Errno::ENOENT`).
```ruby
File.write("a.txt", "")
File.write("b.txt", "")
File.write("c.txt", "")
p(File.delete("a.txt")) # => 1
p(File.unlink("b.txt", "c.txt")) # => 2
p(File.exist?("c.txt")) # => false
```ruby error File.delete(“nope”) # !> IOError: File.delete: No such file or directory
## symlink, link
`File.symlink(String, String)`
`File.link(String, String)`
`symlink(target, link)` makes a symbolic link, `link(target, link)` a hard link; both return 0. An existing `link` path is an `IOError`.
```ruby
File.write("a.txt", "x")
p(File.symlink("a.txt", "s.txt")) # => 0
p(File.link("a.txt", "h.txt")) # => 0
p(File.read("s.txt")) # => "x"
p(File.symlink?("h.txt")) # => false
p(File.identical?("a.txt", "h.txt")) # => true
```ruby error File.write(“a.txt”, “”) File.symlink(“a.txt”, “a.txt”) # !> IOError: File.symlink: File exists
## readlink
`File.readlink(String)`
The path a symbolic link points to, as a String (as written; not resolved). A path that is not a link is an `IOError`.
```ruby
File.write("a.txt", "")
File.symlink("a.txt", "l4.txt")
p(File.readlink("l4.txt")) # => "a.txt"
```ruby error File.write(“a.txt”, “”) File.readlink(“a.txt”) # !> IOError: File.readlink: Invalid argument
## chmod
`File.chmod(Integer, *String)`
Sets the permission bits to `mode` (`0o644`, ...) and returns the number of files changed; 0 with no paths. A missing path is an `IOError`.
```ruby
File.write("a.txt", "")
File.write("b.sh", "")
p(File.chmod(0o755, "a.txt", "b.sh")) # => 2
p(File.executable?("b.sh")) # => true
p(File.chmod(0o644)) # => 0
32.10 utime
File.utime(Time|Nil, Time|Nil, *String)
utime(atime, mtime, *paths): sets the access and modification times and returns the number of files changed. nil means now. A missing path is an IOError.
File.write("a.txt", "")
p(File.utime(Time.at(10), Time.at(20), "a.txt")) # => 1
p(Time.to_i(File.atime("a.txt"))) # => 10
p(Time.to_i(File.mtime("a.txt"))) # => 20
File.utime(nil, nil, "a.txt")
p(File.mtime("a.txt") > Time.at(20)) # => true
32.11 basename
File.basename(String, [String])
The last component of the path. A second argument names an extension (".rb") to strip; ".*" strips any extension. The file system is not consulted.
p(File.basename("/a/b/c.rb")) # => "c.rb"
p(File.basename("/a/b/c.rb", ".rb")) # => "c"
p(File.basename("c.tar.gz", ".*")) # => "c.tar"
p(File.basename("/a/b/")) # => "b"
32.12 dirname
File.dirname(String, [Integer])
The directory part, without the last component. A second argument n goes up n levels (1 when omitted; 0 gives the path unchanged). Without a directory part it is ".".
p(File.dirname("/a/b/c.rb")) # => "/a/b"
p(File.dirname("/a/b/c.rb", 2)) # => "/a"
p(File.dirname("c.rb")) # => "."
32.13 extname
File.extname(String)
The extension of the last component, with its dot; "" when there is none. A name that starts with a dot (.bashrc) has no extension.
p(File.extname("c.tar.gz")) # => ".gz"
p(File.extname("c")) # => ""
p(File.extname(".bashrc")) # => ""
32.14 split
File.split(String)
The two-element Tuple [dirname, basename] (String, String). In Ruby it is an Array; in Sake it is a Tuple, taken apart with dir, base = File.split(p).
dir, base = File.split("/a/b/c.rb")
p(dir) # => "/a/b"
p(base) # => "c.rb"
p(File.split("c.rb")) # => [".", "c.rb"]
32.15 join
File.join(*String|Array)
The parts joined with /; doubled / are collapsed. The arguments are Strings or Arrays of Strings (nested Arrays are flattened); with no arguments it is "". Another type is a type problem statically.
p(File.join("a", "b", "c.rb")) # => "a/b/c.rb"
p(File.join("a/", "/b")) # => "a/b"
p(File.join(Any["a", "b"], "c")) # => "a/b/c"
p(File.join()) # => ""
```ruby error File.join(1) # !> File.join: argument 1 must be String|Array, but is Integer
## expand_path, absolute_path
`File.expand_path(String, [String])`
`File.absolute_path(String, [String])`
Make a relative path absolute. The second argument is the base directory, the current directory (`Dir.pwd`) when omitted. `.` and `..` are resolved, but the file system is not consulted and links are not followed. They differ on `~`: `expand_path` expands `~` to the home directory (`~user` for an unknown user is an `ArgumentError`), `absolute_path` leaves it as it is (as in Ruby).
```ruby
p(File.expand_path("/x/../y")) # => "/y"
p(File.expand_path("b", "/a")) # => "/a/b"
p(File.absolute_path("b", "/a")) # => "/a/b"
p(File.expand_path("a") == File.join(Dir.pwd, "a")) # => true
p(File.expand_path("~") == Dir.home) # => true
p(File.absolute_path("~") == File.join(Dir.pwd, "~")) # => true
32.16 absolute_path?
File.absolute_path?(String)
True when the path is absolute (starts with /). The file system is not consulted.
p(File.absolute_path?("/a/b")) # => true
p(File.absolute_path?("a/b")) # => false
32.17 realpath
File.realpath(String, [String])
The real absolute path with every symbolic link followed. The second argument is the base directory for a relative path. The path must exist; a missing one is an IOError (the difference from expand_path).
File.write("a.txt", "")
File.symlink("a.txt", "l5.txt")
p(File.realpath("l5.txt") == File.join(Dir.pwd, "a.txt")) # => true
p(File.realpath("l5.txt", Dir.pwd) == File.realpath("./a.txt")) # => true
ruby error
File.realpath("nope") # !> IOError: File.realpath: No such file or directory
33. Dir
Dir is the group of operations that work on a directory by its path (a String). There is no value of type Dir (no Dir.open, no Dir object as in Ruby); every operation has the name and the result of Ruby’s class method of the same name (Built-ins). Listings come back as Arrays of Strings.
A failing system call (a missing directory, removing a directory that is not empty; Ruby’s Errno::ENOENT and the like) is an IOError with Ruby’s message (Exceptions). exist? never fails and returns false; glob returns an empty Array when nothing matches.
Dir has no operators. The examples run in an empty directory and create directories there.
33.1 children, entries
Dir.children(String)
Dir.entries(String)
The names (names only, not paths) in the directory, as an Array. children leaves out . and ..; entries includes them. The order is the OS’s and not defined, so Array.sort before relying on it. A missing directory is an IOError.
Dir.mkdir("d1")
File.write("d1/a.txt", "")
File.write("d1/b.rb", "")
p(Array.sort(Dir.children("d1"))) # => ["a.txt", "b.rb"]
p(Array.sort(Dir.entries("d1"))) # => [".", "..", "a.txt", "b.rb"]
```ruby error Dir.children(“nope”) # !> IOError: Dir.children: No such file or directory
## each_child
`Dir.each_child(String) { }`
Passes each name of `children` to the block in turn and returns nil (Ruby returns the Dir; Sake returns nil). The block's value is not used. The order is undefined, as for `children`. A missing directory is an `IOError`.
```ruby
Dir.mkdir("d2")
File.write("d2/x", "")
File.write("d2/y", "")
names = Any[]
r = Dir.each_child("d2") { |n| Array.push(names, n) }
p(Array.sort(names)) # => ["x", "y"]
p(r) # => nil
33.2 glob
Dir.glob(String|Array, [base: String])
The paths that match the pattern, as an Array (Ruby’s Dir.glob, with *, ?, [...], {a,b}, and **/). The pattern is a String or an Array of Strings (a path matching any of them). The result is sorted, as Ruby’s, and a name that starts with a dot does not match *. With the keyword base: the search starts in that directory and the result holds relative names. When nothing matches, or the base: directory is missing, the result is an empty Array; it does not fail.
Dir.mkdir("d3")
File.write("d3/a.txt", "")
File.write("d3/b.rb", "")
Dir.mkdir("d3/sub")
File.write("d3/sub/c.txt", "")
p(Dir.glob("d3/*.rb")) # => ["d3/b.rb"]
p(Dir.glob("d3/**/*.txt")) # => ["d3/a.txt", "d3/sub/c.txt"]
p(Dir.glob(Any["d3/*.txt", "d3/*.rb"])) # => ["d3/a.txt", "d3/b.rb"]
p(Dir.glob("*", base: "d3")) # => ["a.txt", "b.rb", "sub"]
p(Dir.glob("nope/*")) # => []
33.3 exist?
Dir.exist?(String)
True when a directory is at the path. A file or a missing path is false; it never fails.
Dir.mkdir("d4")
File.write("f4", "")
p(Dir.exist?("d4")) # => true
p(Dir.exist?("f4")) # => false
p(Dir.exist?("nope")) # => false
33.4 empty?
Dir.empty?(String)
True when the directory holds nothing but . and ... A missing directory is an IOError.
Dir.mkdir("d5")
p(Dir.empty?("d5")) # => true
File.write("d5/a", "")
p(Dir.empty?("d5")) # => false
33.5 mkdir
Dir.mkdir(String, [Integer])
Creates one directory and returns 0. The second argument is the permission bits (0o700; when omitted, 0o777 reduced by the umask). A missing parent, or an existing entry of the same name, is an IOError (there is no counterpart of mkdir -p).
p(Dir.mkdir("d6")) # => 0
p(Dir.mkdir("d6/sub", 0o700)) # => 0
p(Dir.exist?("d6/sub")) # => true
```ruby error Dir.mkdir(“d6b”) Dir.mkdir(“d6b”) # !> IOError: Dir.mkdir: File exists
## rmdir, unlink
`Dir.rmdir(String)`
`Dir.unlink(String)`
Remove an empty directory and return 0 (the two are the same, as in Ruby). A directory that is not empty, or a missing path, is an `IOError`. Files are removed with `File.delete`.
```ruby
Dir.mkdir("d7")
p(Dir.rmdir("d7")) # => 0
p(Dir.exist?("d7")) # => false
Dir.mkdir("d7")
p(Dir.unlink("d7")) # => 0
```ruby error Dir.mkdir(“d7b”) File.write(“d7b/a”, “”) Dir.rmdir(“d7b”) # !> IOError: Dir.rmdir: Directory not empty
## pwd
`Dir.pwd()`
The absolute path of the current directory. Sake has no `Dir.chdir`, so it does not change while the program runs.
```ruby
p(File.absolute_path?(Dir.pwd)) # => true
p(Dir.pwd == File.expand_path(".")) # => true
33.6 home
Dir.home([String])
The absolute path of the home directory; with a user name, that user’s home. An unknown user is an ArgumentError.
p(Dir.home == ENV.fetch("HOME")) # => true
p(File.expand_path("~") == Dir.home) # => true
```ruby error Dir.home(“no_such_user_xyz”) # !> ArgumentError: Dir.home: user no_such_user_xyz doesn’t exist
## tmpdir
`Dir.tmpdir()`
The absolute path of the directory for temporary files (`$TMPDIR`, or `/tmp`).
```ruby
p(Dir.exist?(Dir.tmpdir)) # => true
p(File.absolute_path?(Dir.tmpdir)) # => true
33.7 mktmpdir
Dir.mktmpdir([String], [String]) [{ }]
Creates a new temporary directory. The first argument is a prefix for its name ("d" when omitted), the second the directory to create it in (Dir.tmpdir when omitted). Without a block it returns the path (a String) and removing it is the caller’s job (Dir.rmdir only while it is empty). With a block, the path is passed to the block, the directory is removed with its contents after the block, and the block’s value is returned (as Ruby’s tmpdir). A missing parent directory is an IOError.
t = Dir.mktmpdir("pfx", ".")
p(String.start_with?(File.basename(t), "pfx")) # => true
p(Dir.exist?(t)) # => true
Dir.rmdir(t)
kept = ""
v = Dir.mktmpdir { |dir| File.write(File.join(dir, "f"), "1"); kept = dir; 42 }
p(v) # => 42
p(Dir.exist?(kept)) # => false
34. ENV
ENV is the group of operations that read and write the process’s environment variables. Ruby’s ENV["X"] is written ENV.get("X"), and ENV["X"] = v is ENV.set("X", v) (in Sake ENV is not a value and cannot be indexed, see Built-ins). Names and values are Strings.
A change is seen by this process and by the child processes started afterwards with Open3 or Kernel.system. ENV.to_h is a copy; changing it does not change the environment, and ENV.replace(h) puts a whole Hash back as the environment.
ENV has no operators. The examples use names that start with SAKE_DOC_ and remove them at the end.
34.1 get
ENV.get(String)
The variable’s value (a String), or nil when it is not set (Ruby’s ENV["X"]). The result is String | nil, so --strict (level 2) reports using it unchecked as a nil problem. For a variable that must be present use fetch.
ENV.set("SAKE_DOC_A", "1")
p(ENV.get("SAKE_DOC_A")) # => "1"
p(ENV.get("SAKE_DOC_NOPE")) # => nil
v = ENV.get("SAKE_DOC_A")
if v
p(String.to_i(v) + 1) # => 2
end
ENV.delete("SAKE_DOC_A")
```ruby error home = ENV.get(“HOME”) p(String.size(home)) # !> argument 1 may be nil
## fetch
`ENV.fetch(String, [String])`
The variable's value (a String). When it is not set, the second argument is returned if given, else it is a `KeyError` (Ruby's `ENV.fetch`; there is no block form). The result is never nil.
```ruby
ENV.set("SAKE_DOC_B", "x")
p(ENV.fetch("SAKE_DOC_B")) # => "x"
p(ENV.fetch("SAKE_DOC_NOPE", "dflt")) # => "dflt"
ENV.delete("SAKE_DOC_B")
```ruby error ENV.fetch(“SAKE_DOC_NOPE”) # !> KeyError: ENV.fetch: key not found: “SAKE_DOC_NOPE”
## set
`ENV.set(String, String)`
Sets the variable (replacing an existing value) and returns the value (Ruby's `ENV["X"] = v`). The value must be a String; another type is a `type` problem statically (removing is `delete`). An empty name, or one containing `=`, is refused by the OS: an `ArgumentError` with the OS's message (`Invalid argument - setenv()`), rescuable.
```ruby
p(ENV.set("SAKE_DOC_C", "1")) # => "1"
p(ENV.set("SAKE_DOC_C", "2")) # => "2"
out, status = Open3.capture2("sh", "-c", "echo $SAKE_DOC_C")
p(out) # => "2\n"
ENV.delete("SAKE_DOC_C")
```ruby error ENV.set(“SAKE_DOC_C”, 1) # !> ENV.set: argument 2 must be String, but is Integer
```ruby error
ENV.set("", "v") # !> ArgumentError: ENV.set: Invalid argument - setenv()
34.2 key?
ENV.key?(String)
True when the variable is set (also when its value is the empty string).
ENV.set("SAKE_DOC_D", "")
p(ENV.key?("SAKE_DOC_D")) # => true
p(ENV.key?("SAKE_DOC_NOPE")) # => false
ENV.delete("SAKE_DOC_D")
34.3 delete
ENV.delete(String)
Removes the variable and returns the value it had (a String), or nil when it was not set (String | nil, level 2).
ENV.set("SAKE_DOC_E", "1")
p(ENV.delete("SAKE_DOC_E")) # => "1"
p(ENV.delete("SAKE_DOC_E")) # => nil
p(ENV.key?("SAKE_DOC_E")) # => false
34.4 keys
ENV.keys()
The names of all set variables as an Array of Strings, in no defined order.
ENV.set("SAKE_DOC_F", "1")
p(Array.include?(ENV.keys, "SAKE_DOC_F")) # => true
p(Array.include?(ENV.keys, "SAKE_DOC_NOPE")) # => false
ENV.delete("SAKE_DOC_F")
34.5 to_h
ENV.to_h()
A copy of the whole environment as a Hash (String to String). Being a copy, changing the Hash does not change the environment, and a later ENV.set does not show in the Hash.
ENV.set("SAKE_DOC_G", "1")
h = ENV.to_h
p(h["SAKE_DOC_G"]) # => "1"
h["SAKE_DOC_H"] = "2"
p(ENV.key?("SAKE_DOC_H")) # => false
ENV.delete("SAKE_DOC_G")
p(Hash.key?(h, "SAKE_DOC_G")) # => true
34.6 replace
ENV.replace(Hash)
Replaces the whole environment with the Hash’s pairs and returns nil (Ruby’s ENV.replace): every variable not in the Hash is removed, every pair is set. Keys and values must be Strings: the checker rejects a Hash whose key or value type is anything else (the value must be String, but is Integer, argument key must be String, but is :a), also through a function’s parameter. Where it cannot see the types (a program run with --strict=0), a non-String key or value is a TypeError at run time, keys and values must be String, got String => Integer, raised before anything changes, so the environment is untouched. The typical use is to restore a copy taken with ENV.to_h.
saved = ENV.to_h
p(ENV.replace(Hash.of("SAKE_DOC_R" => "1"))) # => nil
p(ENV.keys) # => ["SAKE_DOC_R"]
p(ENV.get("SAKE_DOC_R")) # => "1"
ENV.replace(saved)
p(ENV.key?("SAKE_DOC_R")) # => false
p(ENV.to_h == saved) # => true
ruby error
ENV.replace(Hash.of("SAKE_DOC_R" => 1)) # !> ENV.replace: the value must be String, but is Integer [type]
35. Process
Process is the group of operations that read information about this process and its clocks. Only Ruby’s Process.pid and Process.clock_gettime exist; there is no fork, spawn, wait, or kill (child processes are started with Open3 and Kernel.system). Ruby’s constant Process::CLOCK_MONOTONIC is the operation Process.CLOCK_MONOTONIC in Sake (as Math.PI is: Sake has no value constants, see Built-ins).
Process has no operators. The examples print only values that do not depend on the environment.
35.1 pid
Process.pid()
The ID of this process (a positive Integer). A child started with Open3 sees it as $PPID.
p(Process.pid > 0) # => true
out, status = Open3.capture2("sh", "-c", "echo $PPID")
p(String.to_i(out) == Process.pid) # => true
35.2 clock_gettime
Process.clock_gettime(Integer, [Symbol])
The current value of the clock clock (the value of one of the three operations below), Ruby’s Process.clock_gettime. To measure elapsed time use Process.CLOCK_MONOTONIC: it never goes back when the wall clock is adjusted. The second argument is a unit Symbol, as Ruby’s, and decides the result’s type: without it or with :float_second (the default), :float_millisecond, :float_microsecond the result is a Float; with :second, :millisecond, :microsecond, :nanosecond it is an Integer. The checker reads the unit when it is written as a literal Symbol; a unit held in a variable gives Integer | Float, which a case/in has to split before an Integer or Float operation takes it. An unknown unit (unexpected unit: bogus) and an unknown clock number (Invalid argument - clock_gettime(999)) are an ArgumentError.
t0 = Process.clock_gettime(Process.CLOCK_MONOTONIC)
t1 = Process.clock_gettime(Process.CLOCK_MONOTONIC)
p(t1 >= t0) # => true
p(Process.clock_gettime(Process.CLOCK_REALTIME) > 1.0e9) # => true
ms = Process.clock_gettime(Process.CLOCK_MONOTONIC, :millisecond)
p(ms > 0) # => true
p(Integer.to_s(ms) == "#{ms}") # => true
f = Process.clock_gettime(Process.CLOCK_MONOTONIC, :float_millisecond)
p(Float.floor(f) >= 0) # => true
```ruby error Process.clock_gettime(Process.CLOCK_MONOTONIC, :bogus) # !> ArgumentError: Process.clock_gettime: unexpected unit: bogus
```ruby error
Process.clock_gettime(999) # !> ArgumentError: Process.clock_gettime: Invalid argument - clock_gettime(999)
```ruby error unit = :millisecond p(Integer.to_s(Process.clock_gettime(Process.CLOCK_MONOTONIC, unit))) # !> Integer.to_s: argument 1 must be Integer, but can be Float
## CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID
`Process.CLOCK_REALTIME()`
`Process.CLOCK_MONOTONIC()`
`Process.CLOCK_PROCESS_CPUTIME_ID()`
The clock numbers (Integers) for `clock_gettime`. `CLOCK_REALTIME` is the wall clock (seconds since 1970-01-01 UTC, the value of `Time.now`); `CLOCK_MONOTONIC` has an arbitrary origin and never goes back (for elapsed time); `CLOCK_PROCESS_CPUTIME_ID` is the CPU time this process has used. The numbers themselves are the OS's (0, 1, and 2 on Linux). Ruby's other clocks (`CLOCK_THREAD_CPUTIME_ID`, ...) do not exist.
```ruby
p(Process.CLOCK_REALTIME != Process.CLOCK_MONOTONIC) # => true
wall = Process.clock_gettime(Process.CLOCK_REALTIME)
p(Float.abs(wall - Time.to_f(Time.now)) < 1.0) # => true
p(Process.clock_gettime(Process.CLOCK_PROCESS_CPUTIME_ID) >= 0.0) # => true
36. Open3
Open3 is the group of operations that run a child process and collect its output and exit status (capture2, capture2e, and capture3 of Ruby’s open3 library; they are built in because a Sake program has no other way to start a child process. For just true/false without the output use Kernel.system, see Built-ins).
Ruby’s result is an Array [out, status] with a Process::Status; in Sake it is a Tuple and the exit status an Integer (the exit code, or -1 when the child was killed by a signal). Take it apart with multiple assignment, out, status = Open3.capture2(...). A command that is not found or cannot start is an IOError. As in Ruby, a single argument is interpreted by the shell; with two or more, the first is the program and the rest its arguments (no shell). No standard input can be given (there is no stdin_data:).
Open3 has no operators.
36.1 capture2
Open3.capture2(String, *String)
Runs the command and returns the Tuple [stdout, status] (String, Integer). The child’s stderr goes to this process’s stderr.
out, status = Open3.capture2("echo", "hi")
p(out) # => "hi\n"
p(status) # => 0
out2, status2 = Open3.capture2("sh", "-c", "echo $0; exit 3")
p(out2) # => "sh\n"
p(status2) # => 3
```ruby error Open3.capture2(“no_such_command_xyz”) # !> IOError: Open3.capture2: No such file or directory - no_such_command_xyz
## capture2e
`Open3.capture2e(String, *String)`
Runs the command and returns `[output, status]` (String, Integer) with stdout and stderr merged into one String (Ruby's `capture2e`).
```ruby
out, status = Open3.capture2e("sh", "-c", "echo err >&2; echo out; exit 2")
p(out) # => "err\nout\n"
p(status) # => 2
36.2 capture3
Open3.capture3(String, *String)
Runs the command and returns the Tuple [stdout, stderr, status] (String, String, Integer).
out, err, status = Open3.capture3("sh", "-c", "echo err >&2; echo out; exit 2")
p(out) # => "out\n"
p(err) # => "err\n"
p(status) # => 2
p(Open3.capture3("true")) # => ["", "", 0]
killed = Open3.capture3("sh", "-c", "kill -9 $$")
p(killed[2]) # => -1
37. Zlib
Zlib is the group of operations that compress and decompress a String in the zlib and gzip formats (Ruby’s Zlib.deflate, inflate, gzip, and gunzip; built in for speed, see Built-ins). There is no compression level and no stream form (no Zlib::Deflate class).
Both the compressed and the decompressed result are binary Strings (encoding ASCII-8BIT). Decompressing a UTF-8 string gives the same bytes in a different encoding, so the result is not == to the original: pass it through String.force_encoding(s, "UTF-8") to use it as text. Corrupt input, or input of the other format (gzip data given to inflate), is an ArgumentError with zlib’s message (incorrect header check, …); there is no exception type for Ruby’s Zlib::DataError and the like.
Zlib has no operators.
37.1 deflate, inflate
Zlib.deflate(String)
Zlib.inflate(String)
deflate returns the String compressed in the zlib format (RFC 1950); inflate returns the original String. The empty string can be compressed and decompressed, but inflate("") is an ArgumentError (buffer error).
s = String.*("hello ", 100)
z = Zlib.deflate(s)
p(String.bytesize(z) < String.bytesize(s)) # => true
p(Zlib.inflate(z) == s) # => true
p(String.encoding(Zlib.inflate(z))) # => "ASCII-8BIT"
j = Zlib.inflate(Zlib.deflate("日本"))
p(j == "日本") # => false
p(String.force_encoding(j, "UTF-8") == "日本") # => true
```ruby error Zlib.inflate(“not compressed”) # !> ArgumentError: Zlib.inflate: incorrect header check
## gzip, gunzip
`Zlib.gzip(String)`
`Zlib.gunzip(String)`
`gzip` returns the String compressed in the gzip format (RFC 1952, the format of the `gzip` command and of `.gz` files; the first two bytes are `0x1f 0x8b`); `gunzip` returns the original String. The result of `deflate` cannot be given to `gunzip`, nor that of `gzip` to `inflate` (`ArgumentError`).
```ruby
g = Zlib.gzip("hello")
p(String.getbyte(g, 0)) # => 31
p(String.getbyte(g, 1)) # => 139
p(Zlib.gunzip(g)) # => "hello"
File.write("a.gz", g)
out, status = Open3.capture2("sh", "-c", "gunzip -c a.gz")
p(out) # => "hello"
ruby error
Zlib.gunzip(Zlib.deflate("x")) # !> ArgumentError: Zlib.gunzip:
38. Thread
Thread is the type of a thread that runs a block concurrently. Thread.new { ... } makes one; Thread.value(t) or Thread.join(t) waits for its end. It is Ruby’s Thread (a Ruby thread), but only the operations listed here exist: no Thread#[], status, priority, or Thread.pass. There is no Thread literal; values come from Thread.new and Thread.current (Values and types, Built-ins).
- Variables. A block shares the variables around it, but for a thread the variables of the blocks around
Thread.new(their parameters and locals) and the thread block’s own are copied when the thread starts; the function’s (or the top level’s) variables stay shared. A shared counter is updated insideMutex.synchronize(Mutex). - Leaving the block.
breakandreturnout of a thread block are static errors (the block runs afterThread.newhas returned);next vends it withv. - Errors. An exception inside a thread is not reported there; it is raised in the caller of
Thread.valueorThread.join. A thread that ended with an error that noThread.value/Thread.joinread is reported on stderr when the program ends (the exit status is not changed):PATH: a thread ended with an error that no Thread.value / Thread.join read:followed by the error’s report, indented. Every thread stops when the main program ends; one that is still running then is not reported. - Checking. The checker runs the block once, at
Thread.new, and makes its value the type ofThread.value. Interleavings are not analyzed; each operation still checks its arguments at run time.
The only operators on Threads are == and != (equal for the same thread).
38.1 new
Thread.new() { }
Starts the block in a new thread and returns the Thread at once. The block’s value is the thread’s value (Thread.value). The block is required and takes no parameters.
t = Thread.new { 1 + 2 }
p(t) # => #<Thread>
p(Thread.value(t)) # => 3
ws = Array.map(Any[1, 2, 3]) { |i| Thread.new { i * 10 } }
p(Array.map(ws) { |w| Thread.value(w) }) # => [10, 20, 30]
```ruby error
t = Thread.new { return 1 } # !> return outside a function
## value
`Thread.value(x)`
Waits for the thread to end and returns the block's value (Ruby's `t.value`), the same value on every call. When the thread ended with an exception, that exception is raised here (on every call). A thread stopped by `Thread.kill` has the value nil. The result has the block's type; a block that may return nil gets the `nil` check. A thread cannot wait for itself (`Thread.current`): that is a `ThreadError` (`Target thread must not be current thread`).
```ruby
t = Thread.new { "done" }
p(Thread.value(t)) # => "done"
p(Thread.value(t)) # => "done"
e = Thread.new { raise ArgumentError, "boom" }
begin
Thread.value(e)
rescue ArgumentError => err
p(Exception.message(err)) # => "boom"
end
```ruby error t = Thread.new { raise ArgumentError, “boom” } Thread.value(t) # !> ArgumentError: boom
```ruby error
Thread.value(Thread.current) # !> ThreadError: Thread.value: Target thread must not be current thread
When the program ends without any value or join having read a thread that raised, the error is reported on stderr (the program’s exit status stays 0). The checker requires an empty stderr, so this is shown as it appears in a terminal:
t = Thread.new { raise ArgumentError, "boom" }
Thread.join(Thread.new { 1 })
p(1) # 1, then on stderr:
# ex.sake: a thread ended with an error that no Thread.value / Thread.join read:
# ex.sake:1: in <main>: ArgumentError: boom
38.2 join
Thread.join(x, [Integer|Float|Rational])
Waits for the thread to end and returns the thread. With a number of seconds as the second argument, it stops waiting after that time and returns nil when the thread is still running (the thread goes on). Only the two-argument form is Thread | nil, checked at level 2: if Thread.join(t, 5) tells whether the thread finished in time (Timeout.timeout of sakelib is built this way). The thread’s exception is raised here, as with value. A thread cannot wait for itself (ThreadError).
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.join(t, 0.01)) # => nil
p(Thread.alive?(t)) # => true
Queue.push(q, :go)
p(Thread.join(t) == t) # => true
p(Thread.join(t, 1) == t) # => true
38.3 alive?
Thread.alive?(x)
True while the thread is running (has not ended).
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.alive?(t)) # => true
Queue.push(q, 1)
Thread.join(t)
p(Thread.alive?(t)) # => false
p(Thread.alive?(Thread.current)) # => true
38.4 current
Thread.current()
The running thread (the main program’s, or one made by Thread.new). All values for the same thread are ==.
me = Thread.current
p(Thread.current == me) # => true
t = Thread.new { Thread.current }
p(Thread.value(t) == t) # => true
p(Thread.value(t) == me) # => false
38.5 kill
Thread.kill(x)
Stops the thread where it is and returns the thread (Ruby’s t.kill). Its ensure clauses run. A killed thread’s value is nil and its alive? false. A thread that has already ended is left alone.
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.kill(t) == t) # => true
Thread.join(t)
p(Thread.alive?(t)) # => false
p(Thread.value(t)) # => nil
38.6 raise
Thread.raise(x, String)
Raises a RuntimeError with the message in the thread, where it is, and returns the thread. Unless the thread rescues it, the thread ends with it and the caller of value or join gets that RuntimeError (Timeout.timeout of sakelib interrupts a block this way). A thread that has already ended is left alone. In a program that calls Thread.raise, the checker counts Thread.value and Thread.join as raising RuntimeError, so rescue RuntimeError => e around them is accepted (in a program without Thread.raise it is a rescue problem: the body never raises it).
q = Queue.new
t = Thread.new { Queue.pop(q) }
p(Thread.raise(t, "stop") == t) # => true
begin
Thread.value(t)
rescue RuntimeError => e
p(e) # => #<RuntimeError: stop>
end
begin
Thread.join(t)
rescue RuntimeError => e
p(Exception.message(e)) # => "stop"
end
done = Thread.new { 1 }
Thread.join(done)
Thread.raise(done, "late")
p(Thread.value(done)) # => 1
38.7 ==, !=
Thread.==(x, Any)
Thread.!=(x, Any)
Two values are equal when they are the same thread (!= is the negation). A value that is not a Thread is never equal.
t = Thread.new { 1 }
p(t == t) # => true
p(t != Thread.new { 2 }) # => true
p(t == 1) # => false
39. Mutex
Mutex is the type of a lock for mutual exclusion between threads (Ruby’s Thread::Mutex). Mutex.new makes one; Mutex.synchronize(m) { ... } holds it for the block. It is for updating state that threads share (a function’s or the top level’s variables, the contents of an Array or Hash) from several threads (Thread, Built-ins). There is no literal.
The lock is not reentrant: a lock by the thread that holds it is a ThreadError (deadlock; recursive locking). An unlock by a thread that does not hold it is a ThreadError too. ThreadError is a built-in exception that can be rescued (Exceptions).
The only operators on Mutexes are == and != (equal for the same Mutex).
39.1 new
Mutex.new()
A new Mutex that nobody holds.
m = Mutex.new
p(m) # => #<Mutex>
p(Mutex.locked?(m)) # => false
39.2 synchronize
Mutex.synchronize(x) { }
Takes the lock, runs the block, releases the lock when the block ends (also by an exception), and returns the block’s value. While another thread holds the lock it waits. Usually this form is all that is needed. Nesting synchronize on a Mutex the thread already holds is a ThreadError (deadlock; recursive locking), as with lock; it can be rescued, and the outer synchronize releases the lock on the way out.
m = Mutex.new
count = 0
ths = Array.map(Any[1, 2, 3, 4]) { |i|
Thread.new { Array.each(Array.new(100, 0)) { |_| Mutex.synchronize(m) { count += 1 } } }
}
Array.each(ths) { |t| Thread.join(t) }
p(count) # => 400
r = Mutex.synchronize(m) { p(Mutex.locked?(m)); "val" } # => true
p(r) # => "val"
p(Mutex.locked?(m)) # => false
begin
Mutex.synchronize(m) { Mutex.synchronize(m) { 1 } }
rescue ThreadError => e
p(Exception.message(e)) # => "deadlock; recursive locking"
end
p(Mutex.locked?(m)) # => false
```ruby error m = Mutex.new Mutex.synchronize(m) { Mutex.synchronize(m) { 1 } } # !> ThreadError: Mutex.synchronize: deadlock; recursive locking
## lock
`Mutex.lock(x)`
Takes the lock and returns the Mutex, waiting while another thread holds it. A `ThreadError` when this thread already holds it. It pairs with `unlock`; `synchronize` is the usual form, since it cannot forget to release on an exception.
```ruby
m = Mutex.new
p(Mutex.lock(m) == m) # => true
p(Mutex.locked?(m)) # => true
Mutex.unlock(m)
```ruby error m = Mutex.new Mutex.lock(m) Mutex.lock(m) # !> ThreadError: Mutex.lock: deadlock; recursive locking
## unlock
`Mutex.unlock(x)`
Releases the lock and returns the Mutex. A Mutex this thread does not hold (held by nobody, or by another thread) is a `ThreadError`.
```ruby
m = Mutex.new
Mutex.lock(m)
p(Mutex.unlock(m) == m) # => true
p(Mutex.locked?(m)) # => false
```ruby error m = Mutex.new Mutex.unlock(m) # !> ThreadError: Mutex.unlock: Attempt to unlock a mutex which is not locked
## try_lock
`Mutex.try_lock(x)`
Takes the lock and returns true when it is free; returns false without waiting when someone holds it (also when this thread holds it; no error).
```ruby
m = Mutex.new
p(Mutex.try_lock(m)) # => true
p(Mutex.try_lock(m)) # => false
other = Thread.new { Mutex.try_lock(m) }
p(Thread.value(other)) # => false
Mutex.unlock(m)
p(Mutex.try_lock(m)) # => true
39.3 locked?
Mutex.locked?(x)
True when any thread holds the lock.
m = Mutex.new
p(Mutex.locked?(m)) # => false
Mutex.lock(m)
p(Mutex.locked?(m)) # => true
p(Thread.value(Thread.new { Mutex.locked?(m) })) # => true
39.4 owned?
Mutex.owned?(x)
True when the current thread holds the lock; false when another thread does.
m = Mutex.new
p(Mutex.owned?(m)) # => false
Mutex.lock(m)
p(Mutex.owned?(m)) # => true
p(Thread.value(Thread.new { Mutex.owned?(m) })) # => false
39.5 ==, !=
Mutex.==(x, Any)
Mutex.!=(x, Any)
Equal for the same Mutex (!= is the negation). Every Mutex.new is a different value.
m = Mutex.new
p(m == m) # => true
p(m == Mutex.new) # => false
p(m != Mutex.new) # => true
40. Queue
Queue is a thread-safe first-in, first-out queue for passing values between threads (Ruby’s Thread::Queue). Queue.push(q, x) adds at the end; Queue.pop(q) takes from the front, waiting until someone pushes when the queue is empty. Queue.close(q) says that nothing more will come, and pop on a closed, empty Queue is nil. This is the producer-consumer pattern (Thread, Built-ins). There is no literal and no size limit (no SizedQueue).
Element type. To the checker, a Queue’s element type is the union of everything pushed to it. The type of Queue.pop is that plus nil, and --strict (level 2) reports using it unchecked as a nil problem: while (x = Queue.pop(q)) is the usual form.
The only operators on Queues are == and != (equal for the same Queue).
40.1 new
Queue.new()
A new, empty Queue.
q = Queue.new
p(q) # => #<Queue>
p(Queue.empty?(q)) # => true
40.2 push
Queue.push(x, Any)
Adds the value at the end and returns the Queue. Any value can be pushed, nil too, but a nil cannot be told from “closed and empty” by pop. A push to a closed Queue is an IOError (Ruby’s ClosedQueueError).
q = Queue.new
p(Queue.push(q, 1) == q) # => true
Queue.push(q, "two")
p(Queue.size(q)) # => 2
```ruby error q = Queue.new Queue.close(q) Queue.push(q, 1) # !> IOError: Queue.push: push to a closed Queue
## pop
`Queue.pop(x, [Integer|Float|Rational])`
Takes the value at the front and returns it. When the Queue is empty it waits until someone pushes or the Queue is closed; closed and empty, it returns nil without waiting. With a number of seconds as the second argument it returns nil when nothing arrives within that time (0 does not wait). The result is always "the element type | nil", checked at level 2.
```ruby
q = Queue.new
Queue.push(q, 1)
Queue.push(q, 2)
p(Queue.pop(q)) # => 1
p(Queue.pop(q, 5)) # => 2
p(Queue.pop(q, 0.01)) # => nil
workers = Array.map(Any[1, 2]) { |i|
Thread.new { total = 0; while (x = Queue.pop(q)); total += x; end; total }
}
Array.each(Any[1, 2, 3, 4, 5, 6]) { |i| Queue.push(q, i) }
Queue.close(q)
p(Array.sum(Array.map(workers) { |w| Thread.value(w) })) # => 21
```ruby error q = Queue.new Queue.push(q, 1) p(Queue.pop(q) + 1) # !> the operands may be nil
## close
`Queue.close(x)`
Closes the Queue and returns it. Afterwards `push` is an `IOError`, `pop` returns the remaining values in order and then nil, and every waiting `pop` wakes up with nil. Closing again does nothing.
```ruby
q = Queue.new
Queue.push(q, 1)
p(Queue.close(q) == q) # => true
p(Queue.pop(q)) # => 1
p(Queue.pop(q)) # => nil
40.3 closed?
Queue.closed?(x)
True when the Queue has been closed.
q = Queue.new
p(Queue.closed?(q)) # => false
Queue.close(q)
p(Queue.closed?(q)) # => true
40.4 size
Queue.size(x)
The number of values currently in the Queue (an Integer).
q = Queue.new
p(Queue.size(q)) # => 0
Queue.push(q, :a)
Queue.push(q, :b)
p(Queue.size(q)) # => 2
Queue.pop(q)
p(Queue.size(q)) # => 1
40.5 empty?
Queue.empty?(x)
True when nothing is in the Queue.
q = Queue.new
p(Queue.empty?(q)) # => true
Queue.push(q, 1)
p(Queue.empty?(q)) # => false
40.6 ==, !=
Queue.==(x, Any)
Queue.!=(x, Any)
Equal for the same Queue (!= is the negation). Every Queue.new is a different value.
q = Queue.new
p(q == q) # => true
p(q == Queue.new) # => false
p(q != Queue.new) # => true
41. Socket
Socket is the type of a TCP connection. Values come from Socket.connect(host, port) (plain), Socket.connect_ssl(host, port) (TLS), and, on the server side, TCPServer.accept (TCPServer). All are the same type Socket and are read and written with the same operations (Ruby’s TCPSocket and OpenSSL::SSL::SSLSocket, see Built-ins). The reading operations have the shape of IO’s (gets, read), but a Socket is not an IO and cannot be passed to IO.puts and the like.
A network failure (cannot connect, connection lost, name not resolved) is an IOError (Ruby’s Errno::ECONNREFUSED, SocketError, OpenSSL::SSL::SSLError, and the like, all in one; see Exceptions). How long a read or write waits is set by set_timeout; past it, an IOError. The Strings that gets and read return are binary (encoding ASCII-8BIT); pass them through String.force_encoding(s, "UTF-8") to use them as text. There is no UDP and no Unix domain socket.
The only operators on Sockets are == and != (equal for the same connection). The examples start a TCPServer on port 0 of 127.0.0.1 and connect from a Thread of the same program.
41.1 connect
Socket.connect(String, Integer, [Integer|Float|Rational])
Connects by TCP to port of host (a name or an IP address) and returns a Socket. The third argument is how many seconds to wait for the connection; past it, an IOError (when omitted, the OS’s default applies). A refused connection is an IOError too, and so is a name that cannot be resolved (Ruby’s Socket::ResolutionError; the message is the resolver’s, getaddrinfo(3): Name or service not known or the like).
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
client = Thread.new {
s = Socket.connect("127.0.0.1", port, 5)
Socket.write(s, "ping\n")
reply = Socket.gets(s)
Socket.close(s)
reply
}
conn = TCPServer.accept(srv)
p(Socket.gets(conn)) # => "ping\n"
Socket.write(conn, "pong\n")
Socket.close(conn)
p(Thread.value(client)) # => "pong\n"
TCPServer.close(srv)
```ruby error Socket.connect(“127.0.0.1”, 1) # !> IOError: Socket.connect: Connection refused
```ruby error
Socket.connect("no-such-host.invalid", 80) # !> IOError: Socket.connect: getaddrinfo
41.2 connect_ssl
Socket.connect_ssl(String, Integer, [Integer|Float|Rational])
Connects by TCP to port of host, performs the TLS handshake over it, and returns a Socket. The peer’s certificate is verified against the OS’s trust store, and host is used for SNI and the host name check (Ruby’s OpenSSL::SSL::SSLSocket with VERIFY_PEER). The third argument is how many seconds to wait for the TCP connection. A failed connection and a failed handshake (a peer that does not speak TLS, a certificate that cannot be verified) are both an IOError. close closes TLS and TCP. An example that reaches an outside host cannot run under the checker, so only its shape is shown:
s = Socket.connect_ssl("example.com", 443, 10)
Socket.write(s, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
p(Socket.gets(s)) # "HTTP/1.0 200 OK\r\n" or the like
Socket.close(s)
Against a server that does not speak TLS the handshake is an IOError:
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) port = TCPServer.port(srv) t = Thread.new { c = TCPServer.accept(srv); Socket.close(c) } Socket.connect_ssl(“127.0.0.1”, port) # !> IOError: Socket.connect_ssl:
## set_timeout
`Socket.set_timeout(x, Integer|Float|Rational|Nil)`
Sets how many seconds the following `gets`, `read`, and `write` wait, and returns the Socket (Ruby's `IO#timeout=`). Past the time, an `IOError` (`Blocking operation timed out!`). nil restores no limit (the default).
```ruby
srv = TCPServer.new("127.0.0.1", 0)
s = Socket.connect("127.0.0.1", TCPServer.port(srv))
p(Socket.set_timeout(s, 0.05) == s) # => true
begin
Socket.gets(s)
rescue IOError => e
p(Exception.message(e)) # => "Blocking operation timed out!"
end
Socket.set_timeout(s, nil)
Socket.close(s)
TCPServer.close(srv)
41.3 gets
Socket.gets(x)
The next line including its newline, or nil once the peer has closed its writing side (close_write or close) and the data is used up. The result is String | nil; --strict (level 2) reports using it unchecked as a nil problem. Past the set_timeout time, an IOError.
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
t = Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, "a\nb\n")
Socket.close(s)
}
c = TCPServer.accept(srv)
while (line = Socket.gets(c))
p(line) # => "a\n"
# => "b\n"
end
p(Socket.gets(c)) # => nil
Thread.join(t)
TCPServer.close(srv)
41.4 read
Socket.read(x, Integer)
Reads exactly n bytes and returns them. It waits until n bytes have arrived; when the peer closes first it returns what is left, and nil when nothing is left (String | nil, level 2; with n 0 it is always ""). A negative n is an ArgumentError. Unlike Ruby’s read there is no form without a length (read to the end).
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
t = Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, "hello")
Socket.close(s)
}
c = TCPServer.accept(srv)
p(Socket.read(c, 2)) # => "he"
p(Socket.read(c, 10)) # => "llo"
p(Socket.read(c, 10)) # => nil
p(Socket.read(c, 0)) # => ""
Thread.join(t)
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) s = Socket.connect(“127.0.0.1”, TCPServer.port(srv)) Socket.read(s, -1) # !> ArgumentError: Socket.read: negative size -1
## write
`Socket.write(x, String)`
Sends the string and returns the number of bytes sent (an Integer). No newline is added. A closed Socket, or one after `close_write`, is an `IOError`.
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
t = Thread.new {
s = Socket.connect("127.0.0.1", port)
n = Socket.write(s, "日本\n")
Socket.close(s)
n
}
c = TCPServer.accept(srv)
got = Socket.gets(c)
if got
p(String.encoding(got)) # => "ASCII-8BIT"
p(String.force_encoding(got, "UTF-8")) # => "日本\n"
end
p(Thread.value(t)) # => 7
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) s = Socket.connect(“127.0.0.1”, TCPServer.port(srv)) Socket.close(s) Socket.write(s, “x”) # !> IOError: Socket.write: closed stream
## close_write
`Socket.close_write(x)`
Closes only the writing side and returns nil (a TCP half-close). The peer's `gets` and `read` get nil once the data is used up, while this side can still read the peer's reply. It is for protocols of the form "send everything, then wait for the answer". A `write` afterwards is an `IOError`.
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
client = Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, "1 2 3")
Socket.close_write(s)
reply = Socket.gets(s)
Socket.close(s)
reply
}
c = TCPServer.accept(srv)
body = Socket.read(c, 100)
if body
Socket.write(c, String.upcase(body) + "\n")
end
Socket.close(c)
p(Thread.value(client)) # => "1 2 3\n"
TCPServer.close(srv)
41.5 close
Socket.close(x)
Closes the connection and returns nil (for TLS, the TCP connection too). Closing again does nothing. Reading or writing afterwards is an IOError.
srv = TCPServer.new("127.0.0.1", 0)
s = Socket.connect("127.0.0.1", TCPServer.port(srv))
p(Socket.close(s)) # => nil
p(Socket.close(s)) # => nil
TCPServer.close(srv)
41.6 ==, !=
Socket.==(x, Any)
Socket.!=(x, Any)
Two values are equal when they are the same Socket (!= is the negation). The two ends of one connection (the client’s connect and the server’s accept) are different Sockets. A value that is not a Socket is never equal; the function form gives false for it too (no error).
srv = TCPServer.new("127.0.0.1", 0)
s = Socket.connect("127.0.0.1", TCPServer.port(srv))
c = TCPServer.accept(srv)
p(s == s) # => true
p(s == c) # => false
p(s != c) # => true
p(Socket.==(s, 1)) # => false
Socket.close(s)
Socket.close(c)
TCPServer.close(srv)
42. TCPServer
TCPServer is the type of a listening socket that accepts TCP connections (Ruby’s TCPServer). TCPServer.new(host, port) opens the port; TCPServer.accept(s) waits for the next connection and returns it as a Socket. An accepted connection is read and written with the Socket operations (Built-ins). There is no literal.
A failure (the port cannot be opened because it is in use or not permitted, accept on a closed server, …) is an IOError (Exceptions).
The only operators on TCPServers are == and != (equal for the same server). The examples listen on port 0 of 127.0.0.1 (the OS picks a free port) and connect from a Thread of the same program.
42.1 new
TCPServer.new(String, Integer)
Starts listening on port at the address host and returns a TCPServer. host is "127.0.0.1" (local only), "0.0.0.0" (every interface), "localhost", and so on; it cannot be omitted (there is no TCPServer.new(port) as in Ruby). With port 0 the OS picks a free port, which TCPServer.port reports. A port in use, or one not permitted (below 1024), is an IOError.
srv = TCPServer.new("127.0.0.1", 0)
p(srv) # => #<TCPServer>
p(TCPServer.port(srv) > 0) # => true
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) TCPServer.new(“127.0.0.1”, TCPServer.port(srv)) # !> IOError: TCPServer.new: Address already in use
## port
`TCPServer.port(x)`
The port number being listened on (an Integer), to learn the actual port after `TCPServer.new` with 0. A closed server is an `IOError` (`closed stream`).
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
p(port >= 1024) # => true
p(TCPServer.port(srv) == port) # => true
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) TCPServer.close(srv) TCPServer.port(srv) # !> IOError: TCPServer.port: closed stream
## accept
`TCPServer.accept(x)`
Waits for the next connection and returns it as a `Socket`. Connections queue up in the OS from `TCPServer.new` on, so a peer that connects before `accept` is called is not lost. A closed server is an `IOError`. There is no time limit, so connect from another Thread, or combine with `Thread.join(t, limit)` to bound the wait.
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
clients = Array.map(Any["a", "b"]) { |name|
Thread.new {
s = Socket.connect("127.0.0.1", port)
Socket.write(s, name + "\n")
reply = Socket.gets(s)
Socket.close(s)
reply
}
}
served = Any[]
Array.each(clients) { |_|
c = TCPServer.accept(srv)
p(c) # => #<Socket>
# => #<Socket>
line = Socket.gets(c)
if line
Array.push(served, String.chomp(line))
Socket.write(c, "hi " + line)
end
Socket.close(c)
}
replies = Array.compact(Array.map(clients) { |t| Thread.value(t) }) # Socket.gets may be nil: compact before sorting
p(Array.sort(replies)) # => ["hi a\n", "hi b\n"]
p(Array.sort(served)) # => ["a", "b"]
TCPServer.close(srv)
```ruby error srv = TCPServer.new(“127.0.0.1”, 0) TCPServer.close(srv) TCPServer.accept(srv) # !> IOError: TCPServer.accept: closed stream
## close
`TCPServer.close(x)`
Stops listening, releases the port, and returns nil. Sockets already returned by `accept` stay usable. Closing again does nothing.
```ruby
srv = TCPServer.new("127.0.0.1", 0)
port = TCPServer.port(srv)
p(TCPServer.close(srv)) # => nil
p(TCPServer.close(srv)) # => nil
again = TCPServer.new("127.0.0.1", port)
p(TCPServer.port(again) == port) # => true
TCPServer.close(again)
42.2 ==, !=
TCPServer.==(x, Any)
TCPServer.!=(x, Any)
Two values are equal when they are the same server (!= is the negation). Every TCPServer.new is a different value. A value that is not a TCPServer is never equal; the function form gives false for it too (no error).
srv = TCPServer.new("127.0.0.1", 0)
other = TCPServer.new("127.0.0.1", 0)
p(srv == srv) # => true
p(srv == other) # => false
p(srv != other) # => true
p(TCPServer.==(srv, 1)) # => false
TCPServer.close(srv)
TCPServer.close(other)
A. Differences from Ruby and what is not supported
Sake’s syntax is Ruby’s, but several Ruby spellings are static errors and have a Sake spelling in their place. This appendix gathers those replacements in one table and gives the reasons below it. When a program written the Ruby way gets a static error, look it up here first; each error’s hint also gives the form on the Sake side of the table.
A.1 From Ruby to Sake
Each row is the Ruby spelling (left), the Sake spelling (middle), and the reason in a few words (right). The sections under the table explain the reasons, with examples.
| Ruby | Sake | Why |
|---|---|---|
s.upcase |
String.upcase(s) |
the type is written on the operation |
p.x, p.x = v |
Point.x(p), p.Point.x = v; inside the functions, @x, @x = v |
field reads and writes name their type too |
x.nil? |
x == nil |
no calls on values |
def self.f |
def f; in a module, module_function |
one name in a namespace is one function |
class B < A (inheritance) |
class B < A (copies A’s definitions; no subtyping) |
every call target is static |
attr_reader :x |
attr_reader x |
field names are written bare |
def initialize(x) = @x = x |
C.new(x) stores the field; initialize(c) only checks and converts |
the parameter is the new instance |
@items = [] (a default) |
@items = Any[] inside initialize |
fields have no default values |
Data.define |
Struct.new |
classes are mutable |
[1, 2] (a growable array) |
Any[1, 2]; [1, 2] is a Tuple (fixed length) |
a literal’s shape fixes its type |
{a: 1} (a Hash) |
Hash.of(a: 1); {a: 1} is a Record |
same |
%w[a b], %i[a b] |
String["a", "b"], Symbol[:a, :b] |
undecided (Tuple or Array) |
PI = 3.14 |
def pi = 3.14; a table is once { ... } |
no value constants |
Math::PI, ARGV, $stdout |
Math.PI (or Math::PI), ARGV, IO.stdout |
read as operations |
case x when Integer |
case x in Integer |
there is no === |
for x in xs |
Array.each(xs) { \|x\| } |
iteration is an operation |
$1, $~ |
m = String.match(s, re), then m[1] |
no global variables |
proc, lambda, ->, &:sym, method(:f) |
none; a block is only passed, with yield or &b |
blocks are second-class |
obj.send(:f), define_method, method_missing, eval |
none | call targets are static |
g(**opts) |
none; pass the Hash positionally | an open design question (D11) |
rescue A catching the subclasses of A |
rescue M, a module the types include |
class B < A is a copy |
1 + money |
define coerce(m, other) in Money |
the left operand decides |
A.1.1 The type is written on the operation
In Sake only operations name a type. A method call on a value such as s.upcase is a static error, and the checker’s hint gives String.upcase(s) (for s.strip.upcase, the chain form String.strip(s).upcase; Program structure and name resolution). Field reads and writes are the same: p.x is Point.x(p) and p.x = v is p.Point.x = v. Inside a class’s functions, @x means field x of the first parameter (Classes).
Point = Struct.new(:x, :y)
class Point
def shift(pt, d)
@x = @x + d # Point.set_x(pt, Point.x(pt) + d)
@x
end
end
pt = Point.new(1, 2)
p(Point.x(pt)) # => 1
pt.Point.x = 5
p(Point.shift(pt, 1)) # => 6
p(pt) # => #<struct Point x=6, y=2>
x.nil? is a method call on a value too, so it gets the same error, with the hint x == nil.
A.1.2 Classes
A type a program defines is a class. Declaring it with attr_* lines in class C and making it with the shorthand Struct.new(:x, :y) give the same thing. These are the points where it differs from a Ruby class (Classes).
- No
def self.f. Sake has noself;def finsideclass CisC.fas it is. Ruby’s class methods and instance methods both become the samedef fin Sake. In a module, writemodule_function. - No inheritance.
class B < Ais shorthand for writing A’s definitions (fields, functions, includes) into B. Afterwards A and B are unrelated types, and passing a B toA.f(b)is a type error. - Field names are bare.
attr_reader :xis an error telling you to writexwithout the:. initializetakes no value. AfterC.new(x)has stored the fields,def initialize(c)runs with the new instance. Ruby’sdef initialize(x) = @x = xwould store the instance in its own field, so it is a static error.- Defaults go in
initialize.attr_reader items = Any[]is an error; the first value is set insideinitializewith@items = Any[].C.newmay then leave that field out. - No
Data.define. Sake’s classes are mutable, so useStruct.new, the counterpart of Ruby’sStruct.
class A
attr_reader x
def double(a) = @x * 2
end
class B < A # copies A's x and double into B
attr_reader y
end
b = B.new(1, 2)
p(B.double(b)) # => 2
p(b) # => #<struct B x=1, y=2>
```ruby error
class Bag
attr_reader name, items
def initialize(x) = @items = x # !> @items = x stores the new Bag in its own field: x is the instance, not a value
end
### Literals and constants
A literal's shape fixes its type ([Values and types](03-values.md)).
- **Ruby's `Array[1, 2]`, `Hash[...]` and `Set[...]` are Arrays of Arrays, of Hashes, of Sets** in Sake, as `T[...]` is an Array of T for every T. Make an Array of any values with `Any[...]`, a Hash with `Hash.of(k => v)` or `Hash.new`, a Set with `Set.of(a, b)` or `Set.new(xs)`.
- **`[1, 2]` is a Tuple**, of fixed length. A growable array is written `Any[1, 2]`, and the empty one `Any[]`. Passing a Tuple to `Array.push` is a type error whose hint says to write `Any[...]`.
- **`{a: 1}` is a Record**; `Hash.of(a: 1)` is a Hash. A Record's fields are read with a pattern, `r => {a:}`.
- **`%w[a b]` and `%i[a b]` are not supported.** Whether they are a Tuple or an Array is undecided. `String["a", "b"]` and `Symbol[:a, :b]` give Arrays with an element type.
- **No value constants.** `PI = 3.14` is a static error whose hint gives `def pi = 3.14`. A table computed once and kept is `def table = once { ... }` ([Functions and blocks](04-functions.md)). Only `Struct.new` and `Exception.new` may be assigned to a constant.
- **Built-in constants are operations.** Call them as `Math.PI`, `ARGV`, `IO.stdout`. A few built-in ones such as `Math::PI` are also read in Ruby's form. There are no global variables such as `$stdout`.
```ruby
t = [1, 2]
xs = Any[1, 2]
Array.push(xs, 3)
p(t, xs) # => [1, 2]
# => [1, 2, 3]
h = Hash.of(a: 1)
h[:b] = 2
p(h) # => {a: 1, b: 2}
def pi = 3.14
p(pi, Math.PI) # => 3.14
# => 3.141592653589793
```ruby error xs = [1, 2] Array.push(xs, 3) # !> Array.push: argument 1 must be Array, but is [Integer, Integer] [type]
### Control and iteration
- **No `case`/`when`; use `case`/`in`.** Ruby's `when` dispatches `===` on the receiver, and Sake has no `===`. `case x in Integer` branches on the type, and the checker looks at exhaustiveness ([Control flow and patterns](06-control.md)).
- **No `for`; iterate with an operation.** `Array.each(xs) { |x| ... }`, `Range.each(1..3) { |i| ... }`: the operation of the Array or Range hands out the elements ([Functions and blocks](04-functions.md)).
- **No `$1`, `$~`.** Keep the match in a variable: `m = String.match(s, re)`, then `m[1]` when `m` is not nil.
### Blocks and dynamic features
- **Blocks are second-class.** A block is passed to a function and called with `yield`, or passed on with `&b`; it cannot be stored in a variable. There is no `proc`, `lambda`, `->` or `method(:f)`, and `&:sym` cannot be passed ([Functions and blocks](04-functions.md)).
- **Call targets are static.** There is no `send`, `define_method`, `method_missing` or `eval`. `eval` is an error saying it defeats static analysis.
- **Passing keywords on.** `def f(**opts)` can collect them, but `g(**opts)` cannot pass them to the next function (`**` is not supported). Pass the Hash as a positional argument. This is open as design question D11.
### Exceptions
`rescue A, B => e` catches the listed types ([Exceptions and errors](08-exceptions.md)). Writing `class MyErr < StandardError` does not make MyErr a subtype of StandardError; the `<` is a copy, so `rescue A` does not catch a `class B < A`. To catch a group, include a module in the types and write `rescue M`. The built-in types keep Ruby's parents (`rescue IndexError` catches KeyError). The three forms `rescue => e`, `rescue StandardError => e` and `rescue Exception => e` keep Ruby's meaning of "any exception" and catch every rescuable exception.
```ruby
class MyErr < StandardError
end
begin
raise MyErr, "boom"
rescue KeyError, IndexError => e
p(1)
rescue MyErr => e
p(Exception.message(e)) # => "boom"
end
A.1.3 Operators
a + b is shorthand for Arithmetic.+(a, b), resolved by the type of the left operand (Operators and indexing). 1 + money dispatches on Integer, so Money’s + is never reached. With Ruby’s protocol, a coerce(m, other) defined in Money that returns the Tuple [left, right] has the pair converted first, and then the operator runs.
class Money
include Arithmetic
attr_reader cents
def +(a, b) = Money.new(@cents + Money.cents(b))
def coerce(m, other) = [Money.new(other * 100), m]
end
m = Money.new(50)
p(2 + m) # => #<struct Money cents=250>
A.2 Not yet supported
Each of these is rejected statically with a “not supported” error. Most wait on a design decision.
- Writing to Record fields.
r[:a] = 2is an error saying a Record cannot be indexed. Reading is by pattern,r => {a:}. - The type scope
Integer.(a + b). case/when. Usecase/in.%w[],%i[]. Tuple or Array is undecided.for. Not planned for now. Iterate with an operation such asRange.each(1..3) { |i| ... }.- Other pattern forms.
*restin a Tuple pattern, find patterns, pins^x, guardsin Integer if c. The patterns that work are in Control flow and patterns. - First-class blocks. Storing a block,
proc,lambda. g(**opts). Passing collected keywords on.begin ... end while. Write thewhilefirst.
A.3 Design material
DESIGN.md(Japanese): the design notes, with the reasons behind each decision.docs/comparison.md(Japanese): what is new in Sake and what is not, axis by axis against Elm, Crystal, Rust, Elixir, TypeScript, Clojure and the type-inference literature.TODO.md: open items and design questions (D1 to D13).experiments/: each experiment with its method, results, and limits (README).