Skip to content

基本の構文

セミコロンは書きません。文は改行で終わり、名前が変数か型か定数かは大文字と小文字で決まります。

## 2つの数を足す
fn add(x: Int, y: Int) -> Int {
x + y # 最後の式が戻り値になる
}
const LIMIT = 10
fn main() {
total = add(3, 4)
dbg(total) # 7
dbg(total < LIMIT) # True
}

dbg(value) は値を標準エラーに1行出して、その値を返す Prelude の関数です。このガイドの例では出力をコメントに書いています。

ソースファイルの拡張子は .emel で、符号化は UTF-8 です。1つのファイルが1つのモジュールになります。名前に使える文字は ASCII だけですが、文字列とコメントの中には何を書いてもかまいません。

## 登録した利用者
type User(
## 一意の番号。0 は管理者
id: Int,
name: String, # 表示名
)
## 図形
enum Shape {
## 半径で決まる円
Circle(radius: Float)
Empty
}
fn main() {
# 行の全体をコメントにする
dbg(User(id: 1, name: "a")) # User(id: 1, name: "a")
}

# から行末までがコメントです。## で始めると文書コメントになり、直後の宣言やバリアント、フィールドに付いて、emela doc が API の文書にします。ブロックコメント(/* */)はないので、複数行は各行に # を付けます。

改行で文が終わるので、長い式を折り返すときは、次の行を演算子や . で始めます。

fn main() {
total = 1
+ 2
+ 3
dbg(total) # 6
doubled = [1, 2, 3]
|> List.map(fn(n) { n * 2 })
# コメントだけの行と空行は飛ばして、次の行を見る
|> List.fold(from: 0, f: fn(acc, n) { acc + n })
dbg(doubled) # 12
point = (
1,
2,
)
dbg(point) # (1, 2)
label = if total > 5 {
"big"
}
else {
"small"
}
dbg(label) # "big"
}

前の行に続くのは、次の行が二項演算子、.、escape、else、->、| で始まるときと、開いた ( や [ がまだ閉じていないときです。判定するのは次の行の頭だけで、前の行の終わり方は見ません。

長いシグネチャも ->、fails、|、use の前で折り返せます。本体の { はシグネチャの最後の行に書きます。

import Std.Console.{Console, SystemConsole}
error Empty
error TooLong(length: Int)
fn check_name(name: String)
-> String
fails Empty
| TooLong
use Console {
out = use Console
out.print_line("checking #{name}")
n = String.length(name)
if n == 0 { fail Empty }
if n > 10 { fail TooLong(length: n) }
name
}
fn main() {
with SystemConsole {
name = check_name("emela") escape {
Empty -> "?"
TooLong(_) -> "?"
}
dbg(name) # "emela"
}
}

fails と use で行が続くのはシグネチャの中だけです。本体の中で use から始まる行は、新しい文になります。

( で始まる行は常に新しい文です。関数を呼ぶときは、開き括弧を名前と同じ行に書きます。

fn add(x: Int, y: Int) -> Int {
x + y
}
fn main() {
n: Int = add
(1, 2) # add の呼び出しではなく、タプルの式の文
}
type mismatch: expected `Int`, found `fn(Int, Int) -> Int`

n に入ったのは関数 add そのもので、次の行の (1, 2) は別の文です。

- で始まる行も引き算の続きではなく単項マイナスの新しい文です。

fn main() {
a = 10
-3 # a とは別の文。値は捨てられる
dbg(a) # 10
b = (10
- 3) # 括弧の中なので続く
dbg(b) # 7
}

引き算を次の行に続けるときは、式を括弧で囲みます。

括弧の中でも、その内側に { を開けば、改行はまた文を区切ります。

fn main() {
xs = List.map([1, 2, 3], fn(n) {
m = n * 10 # { の中なので、ここで文が終わる
m + 1
})
dbg(xs) # [11, 21, 31]
}

細かい規則は 字句と予約語 にあります。

クラス形使うもの例
小文字名小文字か _ で始まる snake_case変数、関数、フィールド、引数名、エフェクトの操作user_id find
型名大文字で始まり、小文字を1つ以上含む PascalCase型、構成子、エラー、エフェクト、ハンドラ、layer、Trait、モジュール、型引数User Http Db Item
大文字名大文字、数字、_ だけconst、型引数MAX_SIZE A

クラスに合わない名前は構文エラーです。HTTP は型名にならないので、Http と書きます。

type HTTP(code: Int)

型名と大文字名は、すでにある定義を指す名前で、パターンの中で新しい変数になることはありません。パターンで Nonee と打ち間違えても、新しい変数として何にでもマッチしたりせず、「未定義の名前」のエラーになります。

名前のクラスが字句で決まるので、. の左側を見れば何への参照か分かります。

type Config(port: Int)
const DEFAULT_CONFIG = Config(port: 8080)
fn main() {
config = Config(port: 3000)
dbg(config.port) # 3000 左が小文字名: 値のフィールド
dbg(DEFAULT_CONFIG.port) # 8080 左が大文字名: const の値のフィールド
dbg(List.length([1, 2])) # 2 左が型名: モジュールの関数
}

左が型名なら List.map のような静的な参照で、小文字名か大文字名なら user.name のような値へのアクセスです。

fn、type、if、match、use、with など25個の予約語は、識別子に使えません。return、while、let、var、try なども将来のために確保してあります。

fn main() {
return = 1 # return は将来のための予約語
}

fails、derive、init などは決まった位置でだけ働く語なので、それ以外ではふつうの名前に使えます。True、False、Some、None はキーワードではなく、Prelude の enum の構成子です。

全部の一覧は 字句と予約語 にあります。