Skip to content

標準ライブラリの構成

Emela の標準ライブラリはコンパイラに同梱された .emel のソースです。import なしで使える小さな core と、Std の下に置いた std に分かれていて、ホストに触れる機能はすべて std のエフェクトとして提供されます。

import Std.Platform.{Platform}
import Std.Console.{Console}
import Std.Env.{Env}
fn greeting(name: Option[String]) -> String {
name |> Option.map(fn(n) { "Hello, #{n}!" }) |> Option.or("Hello!")
}
fn main() {
with Platform {
(use Console).print_line(greeting((use Env).get("USER")))
}
}
Terminal window
$ USER=emela emela run main.emel
Hello, emela!

Option.map と Option.or は core なので import が要りません。Console と Env は std のエフェクトで、Std.Platform の Platform がホストのハンドラを供給します。

区分名前使い方中身
Prelude修飾なしimport なしで見える基本の型、Some などの構成子、panic / todo / dbg、ParseError
coreList、String など短い名前import なしで List.map と修飾して呼ぶコンパイラが構文として知っている型のモジュール(Int、Int64、Float、String、List、Option)
stdStd.Console など Std の下import して使うそれ以外のすべて

Prelude には純粋なものしか入っておらず、Prelude と core だけで書いた関数は純粋です。core はコンパイラの組み込み関数と一対一で対応していて、小さく保ちます。std に入れるのは、ほとんどのプログラムが使い、形が1つに決まるもの(ホストの境界、並行と状態、基本の値の型、テストの部品など)です。

ページ扱うモジュール
corePrelude、Int、Int64、Float、String、List、Option
Console・Clock・Random・EnvStd.Console、Std.Clock、Std.Random、Std.Env、Std.Instant、Std.Duration、Std.Platform
FileSystem と PathStd.FileSystem、Std.Path
HttpStd.Http
State・Fiber・DeferredStd.State、Std.Fiber、Std.Cause、Std.Deferred

ソースのルート直下に、list.emel や string.emel のような core のモジュールと同じ名前のファイルは置けません。std のモジュールまで短い名前にすると、std にモジュールを足すたびに、利用者のファイル名が1つずつ予約名になっていきます。

std を Std の下に置いたので、予約名は std.emel と std/ だけで済みます。自分のプロジェクトに http.emel を書いても Std.Http とはぶつかりません。モジュール名の決まりは モジュール にあります。

platform のエフェクトにはホストを使う本物のハンドラと、テスト用のハンドラがあります。名前は前に付ける語で分け、どちらもエフェクトと同じモジュールに置きます。

エフェクト本物のハンドラテスト用のハンドラ
ConsoleSystemConsoleTestConsole
ClockSystemClockTestClock
RandomSystemRandomなし
EnvSystemEnvなし
FileSystemSystemFileSystemTestFileSystem
HttpSystemHttpTestHttp
import Std.Clock.{Clock, SystemClock, TestClock}

ハンドラの名前にターゲットの名前は入りません。1つのハンドラがターゲットごとの実装を持ち、ビルドするターゲットで使う実装が決まります。0.20 では Http に C の実装がなく、C で send を呼ぶとコンパイルのエラーになります。呼ばなければ検査しないので、with Platform は C でも使えます。

Std.Platform の Platform は、System のハンドラを全部束ねた layer です。

pub layer Platform {
SystemConsole
SystemClock
SystemRandom
SystemEnv
SystemFileSystem
SystemHttp
}

main はふつう with Platform { ... } と書きます。一部だけ差し替えるときは、差し替えるハンドラを後ろに並べます(with Platform, TestClock { ... })。後に書いたハンドラが同じエフェクトのハンドラを置き換えます。

テスト用のハンドラを束ねた layer はありません。テスト用のハンドラはそれぞれ別の型の State に依存するので、束ねても State.run を重ねる手間は減らないためです。使い方は Console・Clock・Random・Env にあります。

core と std の関数は次の約束に従って名前と引数を決めています。自分のライブラリを書くときも、そろえておくと読みやすくなります。

操作の対象は最初の引数に置きます。そのためパイプでつなげます。

fn main() {
dbg([1, 2, 3] |> List.map(fn(x) { x * 2 }) |> List.filter(fn(x) { x > 2 }))
dbg(["a", "b"] |> String.join(", "))
}

関数を受ける引数の名前は f で、名前でなく位置で渡します(List.map(xs, fn(x) { ... }))。畳み込みの初期値の引数名は from です。

fn main() {
dbg(List.fold([1, 2, 3], from: 0, f: fn(acc, x) { acc + x }))
}
働き名前例
2つをつなぐappendList.append(xs, ys)、String.append(a, b)
並びを平らにするflatten / concatList.flatten(xss)、String.concat(parts)
区切りを挟んでつなぐjoinString.join(parts, sep)

場面で返し方を分けます。

場面返し方例
探して無い、範囲の外Option を返すList.get、List.find、Env.get
入力の形が正しくないerror を宣言して fail するInt.parse(s) -> Int fails ParseError
外の世界の失敗エフェクトの操作の fails に書いて fail するFileSystem.read_text の FileNotFound
呼んだ側の前提が崩れたdefectInt.shift_left に負のシフト量

parse に Option の版はありません。欲しいときは escape で変えます。

fn port(text: String) -> Option[Int] {
Some(Int.parse(text)) escape {
ParseError(..) -> None
}
}
fn main() {
dbg(port("8080")) # Some(8080)
dbg(port("80a")) # None
}

入力の形の誤りはどれも Prelude の error ParseError(input: String) で表します。input は入力の全体です。エラーの書き方は エラー にあります。

形意味例
to_*失敗しない変換Int.to_string、Int64.to_int
from_*失敗しない作成Float.from_int、Instant.from_unix_milliseconds
parse失敗しうる文字列からの変換Int.parse、Float.parse
is_*Bool を返す判定List.is_empty、Option.is_some

Option の既定値は Option.or(opt, default)、Option を返す関数をつなぐのは Option.and_then です。

2つの型の間の変換は後から足した型(依存する側)のモジュールに、行きと帰りをまとめて置きます。基本の型は後から足した型を知らないためです。

  • Int と Int64 の間は Int64.from_int と Int64.to_int
  • Int と Float の間は Float.from_int(戻りは Float.floor などの丸め)
  • Instant と Int64 の間は Instant.from_unix_milliseconds と Instant.to_unix_milliseconds

ただし String への変換だけはどの型も自分のモジュールの to_string に置きます(Int.to_string、Duration.to_string)。同じ変換を両方の型に置くことはしません。

形がまだ固まっていない純粋な Emela の部品は、std に入れる前に Pome github.com/emela-lang/x(ルート名 X)で先に出し、使われて固まったら std に取り込みます。フレームワークのように好みの分かれるものは std に入れず、独立した Pome にします。