データ型
データは type(直積)、enum(直和)、タプルで定義します。宣言も構築もパターンも同じ ( ) の形なので、作るときと取り出すときで書き方が変わりません。
type User(id: Int, name: String)
enum Shape { Circle(radius: Float) Rect(Float, Float) Empty}
fn main() { user = User(id: 1, name: "alice") dbg(user.name) # "alice"
shapes = [Circle(radius: 1.0), Rect(2.0, 3.0), Empty] dbg(List.length(shapes)) # 3
pair = (user.id, "admin") dbg(pair) # (1, "admin")}type User(id: Int, name: String)
type Pair[A, B](first: A, second: B)
type Todo( id: Int, title: String, completed: Bool,)
fn main() { todo = Todo(id: 1, title: "Write docs", completed: False) dbg(todo.title) # "Write docs" dbg(Todo(2, "Review", completed: True)) # Todo(id: 2, title: "Review", completed: True) dbg(Pair(first: 1, second: "one")) # Pair(first: 1, second: "one")}type を宣言すると、型と同じ名前の構成子が1つできます。構成子も関数と同じ規則で呼ぶので、Todo(2, "Review", completed: True) のように位置引数と名前付き引数を混ぜられます(束縛と関数)。フィールドは todo.title で読みます。
型は名前で区別します。フィールドが同じでも、名前が違えば別の型です。
type Point(x: Int, y: Int)type Vec(x: Int, y: Int)
fn norm(p: Point) -> Int { p.x * p.x + p.y * p.y}
fn main() { dbg(norm(Vec(x: 1, y: 2)))}type mismatch: expected `Point`, found `Vec`{ x: Int, y: Int } のような無名のレコード型はないので、名前を付けて宣言します。
フィールドを読むときは、その時点で値の型が分かっている必要があります。フィールドの名前から型を逆算しないので、無名関数の引数でフィールドを読むときは、引数の型が呼び出し先から決まるか、型を書きます。
一部のフィールドだけを変えた値は、構成子の呼び出しの末尾に ..式 を書いて作ります。書いたフィールド以外は .. の後の値から引き継ぎます。
type Todo(id: Int, title: String, completed: Bool)
type Pair[A, B](first: A, second: B)
fn main() { todo = Todo(id: 1, title: "Write docs", completed: False) done = Todo(completed: True, ..todo) dbg(done) # Todo(id: 1, title: "Write docs", completed: True) dbg(todo) # Todo(id: 1, title: "Write docs", completed: False)
p = Pair(first: 1, second: True) q = Pair(first: "s", ..p) dbg(q) # Pair(first: "s", second: True)}元の todo は変わらず、新しい値ができます。変えるフィールドは名前付きで書きます。型引数は変わってよく、上の q は Pair[Int, Bool] から作った Pair[String, Bool] です。
部分更新は、構成子が1つの type や error に使えます。enum のバリアントには使えません。
enum Shape { Circle(radius: Float)}
fn main() { c = Circle(radius: 1.0) d = Circle(radius: 2.0, ..c)}cannot update enum variant `Circle` with `..`c の型は Shape で、中身が Circle かどうかは型で分かりません。
enum Shape { Circle(radius: Float) # 名前付き Rect(Float, Float) # 位置引数 Empty # 中身なし}
enum Color { Red, Green, Blue }
fn area(shape: Shape) -> Float { shape match { Circle(radius:) -> 3.14 * radius * radius Rect(w, h) -> w * h Empty -> 0.0 }}
fn main() { dbg(area(Rect(2.0, 3.0))) # 6.0 dbg([Red, Green]) # [Red, Green]}enum は閉じた直和型で、宣言の後からバリアントを足すことはできません。バリアントの中身は、なし、位置引数、名前付きのどれでもよく、1つの enum に違う形を並べられます。区切りは改行かカンマです。
Int | String のような一般の union 型はありません。union が現れるのはエラーの集合だけです(エラー)。
バリアントの中身を取り出す
Section titled “バリアントの中身を取り出す”バリアントは親の型の値を作る構成子で、それ自体は型ではありません。fn f(c: Circle) とは書けないので、Shape を受けて match で分けます。
enum Shape { Circle(radius: Float) Empty}
fn radius(c: Circle) -> Float { c.radius}cannot find type `Circle` in this scope1つのバリアントの中身を型として扱いたいときは、type を宣言してバリアントに持たせます(type Circle(radius: Float) と enum Shape { CircleShape(Circle), Empty })。
enum の値は名前付きのフィールドでも . で読めません。中身は match で取り出します。
enum Shape { Circle(radius: Float)}
fn main() { c = Circle(radius: 1.0) dbg(c.radius)}type `Shape` has no fieldsOption
Section titled “Option”値があるかないかは、Prelude の enum Option で表します。null はありません。
enum Option[A] { Some(A) None}fn main() { xs = [10, 20, 30] dbg(List.get(xs, 0)) # Some(10) dbg(List.get(xs, 5)) # None dbg(List.head(xs) |> Option.map(fn(n) { n + 1 })) # Some(11) dbg(List.get(xs, 5) |> Option.or(0)) # 0
label = List.find(xs, fn(n) { n > 15 }) match { Some(n) -> "found #{n}" None -> "none" } dbg(label) # "found 20"}Option.map、and_then、or、is_some、is_none、or_fail が core にあります(core のモジュール)。Option.or_fail(opt, err) は None をエラーに変えます(エラー)。
xs[0] のような添字の構文はないので、リストの要素は上のように List.get(xs, 0) で取ります。範囲外なら None が返ります。
タプルと Unit
Section titled “タプルと Unit”fn min_max(xs: List[Int]) -> (Int, Int) { List.fold(xs, from: (0, 0), f: fn(acc, x) { (lo, hi) = acc (if x < lo { x } else { lo }, if x > hi { x } else { hi }) })}
fn main() { (lo, hi) = min_max([3, -1, 4]) dbg(lo) # -1 dbg(hi) # 4 t: (Int, String, Bool) = (1, "x", True) dbg(t) # (1, "x", True) dbg(()) # ()}型は (Int, String)、値は (1, "a")、パターンは (n, s) と書き、要素は2つ以上です。t.0 のような番号での参照はないので、要素はパターンで取り出します。要素0個の () が Unit です。
type Config(host: String, port: Int)
const MAX_RETRY = 3const DEFAULT_CONFIG = Config(host: "localhost", port: 8080)const LIMITS = [10, 100, 1000]
fn main() { dbg(MAX_RETRY + 1) # 4 dbg(DEFAULT_CONFIG.port) # 8080 dbg(List.length(LIMITS)) # 3}const の名前は MAX_RETRY のような大文字名で書きます。右辺に書けるのは純粋な式で、const TIMEOUT = Duration.seconds(5) のように純粋な関数は呼べますが、失敗する関数やエフェクトを使う関数は呼べません。型を書かなければ汎化するので、const EMPTY = [] はどの要素型のリストとしても使えます。
右辺は最初に参照したときに1回だけ評価します。
const LIMIT = compute()
fn compute() -> Int { dbg("computing") 100}
fn main() { dbg("start") dbg(LIMIT) dbg(LIMIT)}"start""computing"100100起動時に全部を評価しないので、使っていない const の defect がプログラムやほかのテストを止めることはありません。評価中の defect は最初に参照したところで起き、文面に const の名前が入ります。
const RATIO = 10 / zero()
fn zero() -> Int { 0}
fn main() { dbg(RATIO)}defect: while evaluating const RATIO: division by zeroconst どうしが名前だけで互いを参照しているとエラーです。
const A = Bconst B = A
fn main() {}constants refer to each other: `A` -> `B` -> `A`パターンに const を書くと、その値との照合(==)になります。
const ORIGIN = (0, 0)
fn where_is(p: (Int, Int)) -> String { p match { ORIGIN -> "origin" (0, _) -> "on the y axis" _ -> "elsewhere" }}
fn main() { dbg(where_is((0, 0))) # "origin" dbg(where_is((0, 5))) # "on the y axis"}評価の細かい規則は 実行時の意味 にあります。