Skip to content

モジュールと公開範囲

1つのファイルが1つのモジュールです。モジュール名はファイルのパスから決まるので、ファイルの中でモジュールを宣言する構文はありません。宣言は既定で非公開で、pub を付けたものだけがほかのモジュールから見えます。

app/
├── Pome.toml
└── src/
├── main.emel # モジュール Main(エントリ)
├── email.emel # モジュール Email
└── http/
└── json_client.emel # モジュール Http.JsonClient
src/main.emel
import Email
import Http.JsonClient.{get}
fn main() {
dbg(get("/users")) # "GET /users"
email = Email.parse("[email protected]") escape {
Email.InvalidEmail(_) -> panic("invalid")
}
dbg(Email.to_string(email)) # "[email protected]"
}

Email と Http.JsonClient の中身はこのページの pub と opaque の節にあります。

ソースのルートからのパスを、区切りごとに snake_case から PascalCase へ変えて . でつなぎます。

ファイルモジュール名
src/email.emelEmail
src/json_codec.emelJsonCodec
src/http/client.emelHttp.Client
src/http_2.emelHttp2

ファイル名とディレクトリ名は小文字の snake_case にします。Foo.emel や My-Dir/ はエラーです。list.emel や string.emel のように core のモジュールと同じ名前になるファイルも置けません。

Pome.toml を置いたディレクトリがプロジェクトで、src/ がソースのルートです。エントリは src/main.emel で、ライブラリなら src/lib.emel がルートになります。

Terminal window
emela run main.emel # 単独ファイル: 隣の greeting.emel を import Greeting で使える
emela run # プロジェクト: Pome.toml を探して src/main.emel を動かす

emela run にファイルを渡して Pome.toml が見つからなければ、そのファイルを単独で動かします。このときは同じディレクトリの直下の .emel だけをモジュールとして import できます。

Pome.toml の書き方と依存の扱いは Pome にあります。今のコンパイラは Pome.toml の有無と [build].target だけを見るので、空のファイルでもプロジェクトになります。

import Http.Client # Client.get(...) と呼ぶ
import Data.Json.{Json, decode} # Json と decode を修飾なしで使う

import Http.Client は最後の名前 Client をスコープに入れるので、関数は Client.get(...) と呼びます。波括弧で列挙した名前は修飾なしで使えます。全部を取り込む * や、別名を付ける as はありません。

import はファイルの先頭に置きます。

fn main() {}
import Std.Console
imports must come before declarations

List、String、Int、Option のような core のモジュールは import なしで List.map(...) と書けます。std のモジュールは Std の下にあり、import が要ります。

import Std.Console.{Console, SystemConsole}
fn main() {
xs: Option[List[Int]] = Some([1, 2])
with SystemConsole {
out = use Console
out.print_line("#{List.length([1, 2, 3])}") # 3
}
dbg(xs) # Some([1, 2])
}

std のモジュールの一覧は 標準ライブラリ にあります。

モジュールどうしが互いを import する循環はエラーです。

import cycle: AMod → BMod → AMod

2つのモジュールが互いの定義を必要とするときは、共通の部分を3つ目のモジュールに出します。

src/http/json_client.emel
pub fn get(url: String) -> String {
"GET #{url}"
}
fn secret() -> Int { # 非公開
1
}

pub はトップレベルの宣言に付けます。フィールドやバリアントを個別に公開する指定はなく、type の構成子と enum のバリアントは親の型に従います。ほかのモジュールから secret を呼んだときのエラーは `secret` is private to module `Http.JsonClient` です。

仮引数名も公開 API の一部です。名前付き引数で呼べるので、名前を変えると呼ぶ側が壊れます。

opaque は型名だけを公開して、中身を隠します。値を作る方法をモジュールの関数に限れるので、「Email 型の値なら検証を通っている」ことを型で保証できます。

src/email.emel
pub opaque type Email(value: String)
pub error InvalidEmail(input: String)
pub fn parse(input: String) -> Email fails InvalidEmail {
if String.contains(input, "@") {
Email(value: input)
} else {
fail InvalidEmail(input:)
}
}
pub fn to_string(email: Email) -> String {
email.value
}
src/main.emel
import Email.{Email, InvalidEmail}
fn show(email: Email) -> String {
Email.to_string(email)
}
fn main() {
email = Email.parse("[email protected]") escape {
InvalidEmail(input:) -> panic("invalid: #{input}")
}
dbg(show(email)) # "[email protected]"
}

ほかのモジュールからは Email.parse(...) でしか値を作れません。構成子を呼ぶことも、部分更新も、フィールドを読むことも、パターンで分解することも、外ではエラーです。

# src/main.emel の中で
a = Email(value: "x") # cannot construct opaque `Email` outside module `Email`
c = email.value # cannot access field `value` of opaque type `Email` outside its module

opaque は type と enum にだけ、pub と一緒に付けます。定義したモジュールの中では、ふつうの type と同じに扱えます。

上の例では、モジュール Email の中に型 Email があります。2つは衝突しません。. の左ではモジュールを、型の位置では型を引くので、Email.parse の Email はモジュール、fn show(email: Email) の Email は import した型です。

import Email だけでは型 Email を修飾なしで書けません。

import Email
fn show(email: Email.Email) -> String {
Email.to_string(email)
}

Email.Email と書くか、import Email.{Email} で型を取り込みます。波括弧の形でも Email はモジュールとしてスコープに入るので、Email.parse(...) はそのまま書けます。

名前空間と名前の引き方の細かい規則は モジュールと名前の解決 にあります。

自分のモジュールで Some のような Prelude の名前を定義すると、Prelude の方が隠れて警告が出ます。

enum Maybe {
Some(Int) # `Some` shadows the prelude's `Some`
Nothing
}
fn main() {
dbg(Some(1)) # このモジュールの Some
}

エラーにしないので、後から Prelude に名前が足されても既存のコードは壊れません。

Pome.toml の [dependencies] に書いた Pome のモジュールは、その Pome のルート名の下に見えます(Quince、Quince.Router)。ルート名の決まり方と付け替え方は Pome にあります。