Elmの主張のように、「ランタイム例外なし」の利点は何ですか?


16

一部の言語では、「実行時例外がない」と主張している他の言語よりも明確な利点があります。

私はこの問題について混乱しています。

ランタイム例外は、私が知る限り単なるツールであり、適切に使用されている場合:

  • 「ダーティ」状態を通信できます(予期しないデータでスロー)
  • エラーの連鎖を指すことができるスタックを追加する
  • 乱雑(無効な入力で空の値を返すなど)と開発者の注意が必要な安全でない使用(無効な入力で例外をスローするなど)を区別できます。
  • エラーに詳細を追加することができます。例外メッセージを使用して、デバッグ作業に役立つさらに詳細な情報を提供できます(理論的に)

一方、例外を「飲み込む」ソフトウェアをデバッグするのは本当に難しいと感じています。例えば

try { 
  myFailingCode(); 
} catch {
  // no logs, no crashes, just a dirty state
}

質問は、「ランタイム例外なし」の強力で理論的な利点は何ですか?


https://guide.elm-lang.org/

実際には実行時エラーはありません。ヌルなし。未定義は関数ではありません。


あなたが言及している言語の例やそのような主張へのリンクを提供することは役立つと思います。これはいくつかの方法で解釈できます。
ジミージェームズ

私が見つけた最新の例は、C、C ++、C#、Java、ECMAScriptなどと比較したelmです。私の質問@JimmyJames
16

2
これは私が聞いた最初のものです。私の最初の反応はBSを呼び出すことです。イタチの言葉に注意してください。実際には
JimmyJames

@atoth似ているように見える複数の無関係な質問があるため(Javaでは「RuntimeException」と「Exception」のように)、質問のタイトルを編集してわかりやすくします。新しいタイトルが気に入らない場合は、もう一度編集してください。
アンドレス

OK、それが十分に一般的であることを望んでいましたが、それが役立ったら、@ AndresFの貢献に感謝します!
atoth

回答:


28

例外のセマンティクスは非常に制限されています。それらは、スローされた場所、または直接の呼び出しスタックで正確に処理する必要があります。コンパイル時に、プログラマーがそれを忘れた場合は表示されません。

これを、エラーがResultsまたはMaybesとしてエンコードされるElmと比較してください。どちらも値です。つまり、エラーを処理しないと、コンパイラエラーが発生します。変数またはコレクションに保存して、処理を都合の良い時間に延期することができます。非常によく似たtry-catchブロックをあちこちで繰り返す代わりに、アプリケーション固有の方法でエラーを処理する関数を作成できます。それらをすべての部分が成功した場合にのみ成功する計算にチェーンでき、1つのtryブロックに詰め込む必要はありません。組み込みの構文による制限はありません。

これは「例外を飲み込む」ようなものではありません。型システムでエラー条件を明示し、それらを処理するためのはるかに柔軟な代替セマンティクスを提供しています。

次の例を考えてみましょう。動作を確認したい場合は、これをhttp://elm-lang.org/tryに貼り付けることができます。

import Html exposing (Html, Attribute, beginnerProgram, text, div, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import String

main =
  beginnerProgram { model = "", view = view, update = update }

-- UPDATE

type Msg = NewContent String

update (NewContent content) oldContent =
  content

getDefault = Result.withDefault "Please enter an integer" 

double = Result.map (\x -> x*2)

calculate = String.toInt >> double >> Result.map toString >> getDefault

-- VIEW

view content =
  div []
    [ input [ placeholder "Number to double", onInput NewContent, myStyle ] []
    , div [ myStyle ] [ text (calculate content) ]
    ]

myStyle =
  style
    [ ("width", "100%")
    , ("height", "40px")
    , ("padding", "10px 0")
    , ("font-size", "2em")
    , ("text-align", "center")
    ]

関数String.toIntcalculateで失敗する可能性があることに注意してください。Javaでは、これによりランタイム例外がスローされる可能性があります。ユーザー入力を読み取るため、かなりの可能性があります。代わりに、エルムはを返すことで対処するように強制しますがResult、すぐに対処する必要はないことに注意してください。私は入力を倍増し、文字列に変換し、することができ、その後に不正な入力をチェックするgetDefault機能。この場所は、エラーが発生したポイントやコールスタックの上方よりも、チェックに適しています。

コンパイラが私たちの手を強制する方法も、Javaのチェックされた例外よりもはるかにきめ細かいです。必要なResult.withDefault値を抽出するなど、非常に特殊な関数を使用する必要があります。技術的にはそのようなメカニズムを悪用する可能性がありますが、あまり意味はありません。置くべき適切なデフォルト/エラーメッセージを知るまで決定を延期できるので、それを使用しない理由はありません。


8
That means you get a compiler error if you don't handle the error.-まあ、それがJavaのチェック例外の背後にある理由でしたが、私たちは皆、それがうまくいったことを知っています。
ロバートハーベイ

4
@RobertHarveyある意味では、Javaのチェックされた例外は、この貧しい人のバージョンでした。残念ながら、それらは「飲み込まれる」可能性があります(OPの例のように)。また、これらは実際の型ではないため、コードフローへの余分な追加パスになります。より優れた型システムを持つ言語では、エラーをファーストクラスの値としてエンコードできます。これはMaybe、HaskellでEither、などで行うことです。Elmは、ML、OCaml、Haskellなどの言語からページを取得しているようです。
アンドレスF.

3
@JimmyJamesいいえ、彼らはあなたを強制しません。値を使用する場合にのみ、エラーを「処理」する必要があります。行う場合、の値を調べる必要がない限り、何もするx = some_func()必要はありません。xその場合、エラーまたは「有効な」値があるかどうかを確認できます。さらに、一方を他方の代わりに使用しようとするのは静的型エラーであるため、実行できません。エルムの種類は、他の関数型言語のようなものを作業している場合は、私が実際には異なる機能からコンの値のようなものを行うことができます前に、私も彼らがエラーであるかどうかを知っています!これはFP言語の典型です。
アンドレスF.

6
@atothしかし、あなたに大きな利益があり、非常に正当な理由があります(あなたの質問に対する複数の回答で説明されているように)。MLに似た構文を持つ言語を学ぶことを本当にお勧めします。Cに似た構文の問題(MLは、70年代前半に開発されたので、おおよそCの同時代人。この種の型システムを設計した人々は、Cとは異なり、この種の構文を通常と見なします:)あなたがそれに取り組んでいる間、Lispを学ぶことも害になりません:)
Andres F.

6
@atothこのすべてから1つを取りたい場合は、これを取ります。常に、Blub Paradoxの餌食にならないようにしてください。新しい構文にイライラしないでください。たぶん、あなたがなじみのない強力な機能のためにそこにあるでしょう:)
Andres F.

10

この声明を理解するためには、まず静的型システムが何を買うかを理解する必要があります。本質的には、静的型システムは、私たちを与えるもの、保証です:場合に限っプログラムタイプをチェックし、実行時の動作の特定のクラスが発生することはできません。

それは不吉に聞こえます。タイプチェッカーは定理チェッカーに似ています。(実際、カリー-ハワード-同型写像によれば、それらは同じものです。)定理について非常に独特なことの1つは、定理を証明するとき、定理が言うことを正確に証明することです。(例えば、だから、誰かが「このプログラムが正しいことを証明した」と言うとき、あなたは常に「 '正しい'を定義してください」と尋ねるべきです。同じことが型システムにも当てはまります。「プログラムはタイプセーフです」と言うとき、私たちが意味するのは、起こりうるエラーが発生しないということではありません。型システムが防止することを約束するエラーは発生しないとしか言​​えません。

そのため、プログラムにはさまざまなランタイム動作が無限に存在する可能性があります。これらのうち、無限に多くのものが有用ですが、無限に多くのものも「不正」です(「正確さ」のさまざまな定義に対して)。静的型システムを使用すると、これらの無限に多くの誤った実行時動作の特定の有限で固定されたセットが発生しないことを証明できます。

異なる型システムの違いは、基本的に、それらが発生しないことが証明できる実行時の動作の数、数、および複雑さです。Javaのような弱い型のシステムは、非常に基本的なことしか証明できません。たとえば、Javaは、を返すように入力されたメソッドがをString返せないことを証明できListます。しかし、たとえば、メソッドが戻らないことを証明することはできません。メソッドが例外をスローしないことも証明できません。そして、それが間違った ものを返さないことを証明することはできません。String どれもString型チェッカーを満足させるでしょう。(そして、もちろん、でもnull同様にそれを満足させます。)Javaは証明できないことも、非常にシンプルなものがありますが、私たちのような例外が持っている理由であるArrayStoreExceptionClassCastExceptionまたはみんなの大好きな、NullPointerException

Agdaのようなより強力な型システムは、「2つの引数の合計を返す」または「引数として渡されたリストのソートされたバージョンを返す」などのことも証明できます。

さて、エルムの設計者がランタイム例外がないというステートメントによって意味するのは、エルムの型システムは、他の言語では発生しないことが証明できないため、その結果、実行時の誤った動作(最良の場合は例外を意味し、最悪の場合はクラッシュを意味し、最悪の場合はクラッシュなし、例外なし、静かに間違った結果を意味します)。

そのため、彼らは「例外を実装しない」と言っているわけではありません。彼らは「エルムに来る典型的なプログラマーが経験するであろう典型的な言語の実行時例外となるものは、型システムに捕らえられる」と言っています。もちろん、イドリス、アグダ、グル、エピグラム、イザベル/ HOL、Coq、または同様の言語から来た人は、エルムを比較するとかなり弱いと見なします。このステートメントは、典型的なJava、C♯、C ++、Objective-C、PHP、ECMAScript、Python、Ruby、Perlなどのプログラマーを対象としています。


5
潜在的な編集者への注意:ダブルネガティブやトリプルネガティブの使用について非常に残念です。しかし、私はそれらを意図的に残しました:型システムは、特定の種類の実行時の動作がないことを保証します。つまり、特定の事柄が発生しないことを保証します。そして、その定式化を「発生しないことを証明する」ままにしたかったのですが、残念ながら「メソッドが戻らないことを証明できない」というような構造になります。これらを改善する方法を見つけることができたら、先に進みますが、上記に留意してください。ありがとうございました!
ヨルグWミットタグ

2
全体的には良い答えですが、1つの小さな要点があります。「型チェッカーは定理証明器に似ています。」実際、型チェッカーは定理チェッカーに似ています。両方とも推論ではなく検証を行います。
ガーデンヘッド

4

Elmは同じ理由でランタイム例外がないことを保証できます。Cはランタイム例外がないことを保証できます。言語は例外の概念をサポートしていません。

Elmには実行時にエラー状態を通知する方法がありますが、このシステムは例外ではなく、「結果」です。失敗する可能性のある関数は、通常の値またはエラーを含む「結果」​​を返します。Elmsは強く型付けされているため、これは型システムで明示されています。関数が常に整数を返す場合、そのタイプはになりIntます。ただし、整数を返すか失敗した場合、戻り値の型はResult Error Intです。(文字列はエラーメッセージです。)これにより、呼び出しサイトで両方のケースを明示的に処理する必要があります。

ここで紹介の例は、(ビットの簡略化):

view : String -> String 
view userInputAge =
  case String.toInt userInputAge of
    Err msg ->
        text "Not a valid number!"

    Ok age ->
        text "OK!"

toInt入力が解析可能でない場合、関数は失敗する可能性があるため、戻り値の型はResult String intです。実際の整数値を取得するには、パターンマッチングを介して「アンパック」する必要があり、これにより両方のケースを処理する必要があります。

結果と例外は基本的に同じことを行いますが、重要な違いは「デフォルト」です。デフォルトでは、例外はバブルアップしてプログラムを終了します。例外を処理するには、例外を明示的にキャッチする必要があります。結果は別の方法です-デフォルトでそれらを処理することを余儀なくされるので、プログラムを終了させたい場合は、それらを一番上まで明示的に渡す必要があります。この動作がより堅牢なコードにどのようにつながるかは簡単にわかります。


2
@atoth次に例を示します。言語Aが例外を許可すると想像してください。次に、関数が与えられますdoSomeStuff(x: Int): Int。通常Int、が返されることを期待していますが、例外もスローできますか?そのソースコードを見ないで、あなたは知ることができません。対照的に、型を介してエラーをエンコードする言語Bは、次のように宣言された同じ関数を持つ場合があります doSomeStuff(x: Int): ErrorOrResultOfType<Int>(Elmでは、この型は実際に名前が付けられますResult)。前者の場合とは異なり、関数が失敗するかどうかはすぐに明らかになり、明示的に処理する必要があります。
アンドレスF.

1
@RobertHarveyが別の答えのコメントで暗示しているように、これは基本的にJavaのチェック例外のようです。ほとんどの例外がチェックされた初期のJavaでの作業から学んだことは、エラーが発生した時点で常にエラーを発生させるコードを強制的に書きたくないことです。
ジミージェームズ16

2
@JimmyJames例外は構成されず、無視(「飲み込む」)でき、一流の値ではないため、チェックされた例外とは異なります。これは、エルムが作り上げた新しいものではありません。これは、MLやHaskellなどの言語でプログラミングする方法であり、Javaとは異なります。
アンドレスF.

2
@AndresF。this is how you program in languages such as ML or HaskellHaskellでは、はい。ML、いいえ。標準MLおよびプログラミング言語研究者の主要な貢献者であるロバートハーパーは、例外は有用であると考えています。エラーが発生しないことを保証できる場合は、エラーの種類が関数構成の邪魔になることがあります。例外のパフォーマンスも異なります。毎回値、および例外はいくつかのアルゴリズムでバックトラック表現する自然な方法ですあなたがスローされていない例外のために払っていないが、あなたはエラーをチェックするために支払う
Doval

2
@JimmyJamesうまくいけば、チェックされた例外と実際のエラータイプが表面的に似ているだけであることがわかります。チェック済み例外は適切に結合されず、使用するのが面倒であり、式指向ではありません(したがって、Javaで発生するように、それらを単純に「飲み込む」ことができます)。チェックされていない例外はそれほど面倒ではないため、Java以外のすべての場所で標準となっていますが、それらはトリップする可能性が高く、関数宣言を見てスローされるかどうかがわからないため、プログラムが難しくなります理解する。
アンドレスF.

2

まず、「飲み込む」例外の例は一般にひどい習慣であり、実行時例外がないこととはまったく関係がないことに注意してください。考えてみると、実行時エラーがありましたが、それを非表示にして何もしないことにしました。これにより、理解しにくいバグがしばしば発生します。

この質問はさまざまな方法で解釈できますが、コメントでElmについて言及したので、コンテキストはより明確です。

Elmは、とりわけ静的に型付けされたプログラミング言語です。この種の型システムの利点の1つは、プログラムが実際に使用される前に、多くのクラスのエラー(すべてではないが)がコンパイラーによってキャッチされることです。いくつかの種類のエラーは、例外としてスローされる代わりに、型(Elm ResultTask)などでエンコードできます。これがElmの設計者が意味することです。多くのエラーは「実行時」ではなくコンパイル時にキャッチされ、コンパイラはそれらを無視して最善を期待するのではなく、それらの処理を強制します。これが利点である理由は明らかです。ユーザーが気付く前にプログラマーが問題に気付く方が良いです。

例外を使用しない場合、エラーは他の驚くほど簡単な方法でエンコードされます。Elmのドキュメントから:

Elmの保証の1つは、実際には実行時エラーが発生しないことです。NoRedInkは、生産でElmを約1年間使用していますが、まだ持っていません!Elmのすべての保証と同様に、これは基本的な言語設計の選択に帰着します。この場合、Elmはエラーをデータとして扱うという事実に助けられます。(ここでデータをたくさん作成していることに気づきましたか?)

エルムのデザイナーは、「実行時の例外なし」を主張するのに少し大胆です。それらが意味する可能性が高いのは、「javascriptでコーディングしている場合よりも予期しないエラーが少ない」ことです。


私はそれを読み違えているのでしょうか、それともセマンティックゲームをしているだけなのでしょうか?彼らは「実行時例外」という名前を禁止していますが、それを単にエラー情報をスタックに伝える別のメカニズムに置き換えるだけです。これは、例外の実装を、エラーメッセージを異なる方法で実装する同様の概念またはオブジェクトに変更するだけのように聞こえます。それはほとんど大したことではありません。これは、静的に型指定された言語のようなものです。COM HRESULTから.NET例外への切り替えを比較してください。異なるメカニズムですが、それが何と呼ばれようとも、実行時例外です。
マイクはモニカをサポートします

@Mike正直に言うと、エルムを詳しく見たことはありません。ドキュメントから判断すると、彼らはタイプを持っているResultし、Taskそれはより身近に非常によく似た外観をEitherし、Future他の言語から。例外とは異なり、これらのタイプの値は組み合わせることができ、ある時点でそれらを明示的に処理する必要があります。有効な値またはエラーを表しますか?私は心を読みませんが、この驚くべきプログラマーの欠如はおそらく、エルムのデザイナーが「実行時例外なし」を意味したことです:)
Andres F.

@マイク私はそれが地球を粉砕しないことに同意します。実行時例外との違いは、型が明示的ではないことです(つまり、ソースコードを見ずにコードがスローされるかどうかを知ることはできません)。型のエンコードエラーは非常に明示的であり、プログラマがそれらを見落とすことを防ぎ、より安全なコードにつながります。これは多くのFP言語によって行われ、実際に新しいものではありません。
アンドレスF.

1
あなたのコメントによると、「静的型チェック」以上のものがあると思います。Typescriptを使用してJSに追加できます。これは、新しい "make-or-break"エコシステムよりも制約が少ないです。
atoth

1
@AndresF .:技術的に言えば、Javaの型システムの最新の機能は、60年代後半に生まれたパラメトリックポリモーフィズムです。したがって、ある意味では、「Javaではない」という意味で「モダン」と言うのは多少正しいです。
ヨルグWミットタグ

0

エルムの主張:

実際には実行時エラーはありません。ヌルなし。未定義は関数ではありません。

しかし、実行時例外について尋ねます。違いがあります。

Elmでは、予期しない結果を返すものはありません。実行時エラーを生成する有効なプログラムをElmで作成することはできません。したがって、例外は必要ありません。

したがって、質問は次のようになります。

「実行時エラーなし」の利点は何ですか?

実行時エラーが発生しないコードを作成できれば、プログラムはクラッシュしません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.