erg/doc/JA/faq_syntax.md
2023-02-13 00:21:58 +09:00

7.3 KiB

Erg design's "Why" and Answers

badge

なぜ型パラメータを囲むカッコが<>や[]ではなく||なのですか?

<>[]では文法の衝突が起きるからです。

# []版
id[T: Type] [t]: [T] = t
y = id[Int] # これは関数?
# <>版
id<T: Type> {t: T} = t
y = (id<Int, 1> 1) # これはタプル?
# {}版
id{T: Type} {t: T} = t
y = id{Int} # これは関数?
# ||版
id|T: Type| t: T = t
y = id|Int| # OK

{i = 1}の型は{i = Int}ですが、OCamlなどでは{i: Int}となっています。なぜErgは前者の構文を採用したのですか?

Ergは型自体も値として扱える設計になっているためです。

A = [Int; 3]
assert A[2] == Int
T = (Int, Str)
assert T.1 == Str
D = {Int: Str}
assert D[Int] == Str
S = {.i = Int}
assert S.i == Int

Ergにマクロが実装される予定はありますか?

現在のところありません。マクロには大きく分けて4つの目的があります。1つ目は、コンパイル時計算です。これは、Ergではコンパイル時関数がその役割を担っています。 2つ目は、コード実行の遅延です。これはdoブロックで代用できます。3つ目は処理の共通化ですが、これについては多相関数と全称型がマクロよりもよい解決策です。4つ目は自動コード生成ですが、これは可読性の低下をもたらすためErgではあえて実現出来ないようにしています。 このようにマクロの持つ機能の大部分はErgの型システムが肩代わりしているため、導入のモチベーションがないのです。

なぜErgには例外機構がないのですか?

多くの場合において、Result型によるエラーハンドリングがより良い解決策であるからです。Result型は比較的新しいプログラミング言語では採用されている事例の多いエラーハンドリング手法です。

Ergでは?演算子によってエラーをあまり意識せずに書けます。

read_file!() =
    f = open!("foo.txt")? # 失敗したらエラーをすぐさま返すので、fはFile型
    f.read_all!()

# tryプロシージャで例外のような捕捉処理も可能である
try!:
    do!:
        s = read_file!()?
        print! s
    e =>
        # エラー発生時に実行されるブロック
        print! e
        exit 1

Pythonの関数を導入する場合は、デフォルトではすべて例外を含む関数とみなされ、戻り値型はResult型となります。 例外を送出しないとわかっている場合は、assertで明示します。

また、Ergが例外機構を導入していない理由として、並列プログラミングのための機能導入を予定しているからというのもあります。 というのも、例外機構は並列実行と相性が悪い(並列実行により複数の例外が発生した場合などの対処が面倒など)のです。

ErgはバッドプラクティスとされているPythonの機能を排除しているように見受けられますが、なぜ継承は廃止しなかったのですか?

Pythonのライブラリには継承されることを前提に設計されているクラスがあり、継承を完全に廃止してしまうとこれらの運用に問題が生じるためです。 とはいえ、Ergではデフォルトでクラスがfinalであり多重・多層継承も原則禁止されているので、継承は比較的安全に使用できます。

なぜ多相関数のサブタイプ推論はデフォルトで記名的トレイトを指すのですか?

デフォルトで構造的トレイトを指すと、型指定が複雑になり、プログラマの意図しない挙動を混入させる恐れがあるためです。

# Tが構造的トレイトの部分型である場合...
# f: |T <: Structural Trait {.`_+_` = Self.(Self) -> Self; .`_-_` = Self.(Self) -> Self}| (T, T) -> T
f|T| x, y: T = x + y - x
# Tは記名的トレイトの部分型である
# g: |T <: Add() and Sub()| (T, T) -> T
g|T| x, y: T = x + y - x

Ergには独自演算子を定義する機能は実装されませんか?

A: その予定はありません。一番の理由は、独自演算子の定義を許可すると、その結合順位をどうするかという問題が立ち上がるからです。独自演算子の定義が可能なScalaやHaskellなどではそれぞれ違った対応をしていますが、これは解釈の違いを生みかねない文法である証拠とみることができます。また独自演算子には可読性の低いコードが作られかねないというデメリットもあります。

なぜErgでは+=のような拡張代入演算子を廃止してしまったのですか?

まず、Ergでは変数の可変性がありません。つまり、再代入ができません。一旦ある変数に紐付けられたオブジェクトは、スコープを外れて解放されるまでずっとその変数に束縛されます。Ergで可変性とはオブジェクトの可変性を意味します。これが分かれば、話は簡単です。例えばi += 1i = i + 1を意味しますが、変数は再代入不可なので、このような構文は不正です。もう一つ、Ergの設計原則として演算子は副作用を持たないというものがあります。Pythonも概ねそうなっていますが、Dictなど一部のオブジェクトでは拡張代入演算子がオブジェクトの内部状態を変更します。これはあまり美しい設計とは言えません。 そういうわけで拡張代入演算子はまるごと廃止されています。

Ergはなぜ副作用のあるオブジェクトを文法的に特別扱いしているのですか?

副作用の局所化は、コードのメンテナビリティにおいて重要な要素です。

しかし、確かに副作用を言語的に特別扱いしないで済む方法もなくはありません。例えば、プロシージャは代数的効果(型システム上の機能)で代用できます。 しかしこのような合一化は常に正しいとは限りません。例えば、Haskellは文字列を特別扱いせず単なる文字の配列としましたが、この抽象化は間違っていました。

どのような場合に、合一化は間違っていたと言えるでしょうか。一つの指標は、「その合一化によってエラーメッセージが見にくくなるか」です。 Erg設計者は副作用を特別扱いしたほうがエラーメッセージは読みやすくなると判断しました。

Ergは強力な型システムを持っていますが、全てを型で支配するわけではないのです。 もしそうしてしまったら、Javaがすべてをクラスで支配しようとしたのと同じ末路を辿るでしょう。