一部の言語は、「実行時例外がない」と主張している他の言語よりも明らかに優れています。
私はそのことについて混乱しています。
ランタイム例外は、私が知る限り、適切に使用した場合の単なるツールです。
一方、例外を「飲み込む」ソフトウェアをデバッグするのは本当に難しいと思います。例えば。
try {
myFailingCode();
} catch {
// no logs, no crashes, just a dirty state
}
それで問題は、「ランタイム例外がない」ことの強力で理論的な利点は何ですか?
実際には実行時エラーはありません。 nullなし。 undefinedは関数ではありません。
例外はセマンティクスを非常に制限します。これらは、スローされる場所、または直接の呼び出しスタックで正確に処理する必要があり、コンパイル時にプログラマが忘れた場合は通知されません。
エラーが Results または Maybes としてエンコードされているElmとこれを比較してください。どちらもvaluesです。つまり、エラーを処理しないと、コンパイラエラーが発生します。それらを変数またはコレクションに格納して、処理を都合のよい時間まで延期することができます。非常によく似た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")
]
calculate
関数のString.toInt
は失敗する可能性があることに注意してください。 Javaでは、これによりランタイム例外がスローされる可能性があります。ユーザー入力を読み取るため、その可能性はかなり高くなります。 Elmは代わりにResult
を返すことで対処するように強制しますが、すぐに対処する必要がないことに注意してください。入力を2倍にして文字列に変換し、、次にgetDefault
関数の不正な入力をチェックします。この場所は、エラーが発生したポイントまたはコールスタックの上方よりも、チェックに適しています。
コンパイラーが私たちの手を強制する方法も、Javaのチェック例外よりはるかに細かいです。必要な値を抽出するには、Result.withDefault
のような非常に具体的な関数を使用する必要があります。技術的にはそのようなメカニズムを悪用する可能性がありますが、あまり意味がありません。設定する適切なデフォルト/エラーメッセージがわかるまで決定を延期できるため、それを使用しない理由はありません。
このステートメントを理解するには、まず静的型システムが何を購入するかを理解する必要があります。本質的に、静的型システムが提供するものは保証です:iffプログラム型チェック、特定のクラスのランタイム動作は発生しません。
それは不吉に聞こえます。型チェッカーは定理チェッカーに似ています。 (実際には、カレーハワード同型写像によれば、これらは同じものです。)理論について非常に独特なことの1つは、定理を証明するとき、定理が言うことを正確に証明することです。 (たとえば、そのため、誰かが「このプログラムが正しいことを証明した」と言ったときは、常に「「正しい」と定義してください」と尋ねるべきです。)同じことが型システムにも当てはまります。 「プログラムはタイプセーフである」と言うとき、私たちが意味することはnotであり、エラーが発生する可能性はありません。型システムが防止することを約束しているエラーは発生しないと言えます。
したがって、プログラムは無限に多くの異なる実行時の動作を持つことができます。それらのうち、無限に多くのものは有用ですが、無限に多くのものは「正しくない」(「正確さ」のさまざまな定義に対して)。静的型システムを使用すると、これらの無限に多くの不正な実行時動作が発生しない特定の有限の固定セットを証明できます。
異なる型システム間の違いは、基本的に、それらが発生しないことが証明できる実行時の動作の数、数、および程度です。 Javaのような弱い型システムは、非常に基本的なことしか証明できません。たとえば、Javaは、String
を返すように型指定されたメソッドがList
を返すことができないことを証明できます。たとえば、次のことは可能ですnotメソッドが返らないことを証明します。また、メソッドが例外をスローしないことを証明することもできません。また、間違ったString
–すべてのString
はタイプチェッカーを満たします(そしてもちろん、null
もそれを満たします)Javaは証明できないため、ArrayStoreException
、ClassCastException
、またはみんなのお気に入りのNullPointerException
などの例外があります。
Agdaのようなより強力な型システムは、「2つの引数の合計を返す」、「引数として渡されたリストのソートされたバージョンを返す」などのことも証明できます。
Elmの設計者がランタイム例外がないというステートメントで意味することは、Elmの型システムは、他の言語で実行可能なランタイム動作(の大部分)がないことを証明できるということですnotが証明されます発生しないため、実行時に誤った動作が発生する可能性があります(これは、最良の場合は例外を意味し、最悪の場合はクラッシュを意味し、最悪の場合はクラッシュなし、例外なし、そして単に黙って間違った結果を意味します)。
つまり、彼らはnotで「例外は実装していません」と言っています。彼らは、「エルムに来る典型的なプログラマーが経験するであろう典型的な言語で実行時例外になるものは、型システムによって捕らえられる」と言っています。もちろん、イドリス、アグダ、グル、エピグラム、イザベル/ HOL、コック、または同様の言語から来た人は、エルムを比較するとかなり弱いと見なします。このステートメントは、典型的なJava、C♯、C++、Objective-C、PHP、ECMAScript、Python、Ruby、Perlなどのプログラマーを対象としています。
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
です。実際の整数値を取得するには、パターンマッチングによって「アンパック」する必要があります。これにより、両方のケースを処理する必要があります。
結果と例外は基本的に同じことを行いますが、重要な違いは「デフォルト」です。例外はデフォルトでバブルアップしてプログラムを終了します。例外を処理する場合は、明示的にキャッチする必要があります。結果は逆です-デフォルトでそれらを処理することを強制されるので、プログラムを終了させたい場合は、それらを明示的に最上部に渡す必要があります。この動作がどのようにしてより堅牢なコードにつながる可能性があるかは簡単にわかります。
まず、「飲み込む」例外の例は一般にひどい習慣であり、実行時例外がないこととはまったく関係がないことに注意してください。考えてみると、実行時エラーがありましたが、非表示にして何もしませんでした。これにより、理解しにくいバグが発生することがよくあります。
この質問はさまざまな方法で解釈できますが、コメントでElmについて述べたので、コンテキストはより明確です。
Elmは、とりわけ、静的に型付けされたプログラミング言語です。この種の型システムの利点の1つは、プログラムが実際に使用される前に、多くのクラスのエラー(すべてではありません)がコンパイラーによってキャッチされることです。一部の種類のエラーは、例外としてスローされるのではなく、タイプ(ElmのResult
およびTask
など)でエンコードできます。これがElmの設計者が意味することです。多くのエラーは「実行時」ではなくコンパイル時にキャッチされ、コンパイラーはそれらを無視して最良のものを期待する代わりにそれらを処理するように強制します。これが利点である理由は明らかです。ユーザーが気付く前に、プログラマーが問題に気付くほうがよいでしょう。
例外を使用しない場合、エラーは他の驚くべき方法でエンコードされることに注意してください。 Elmのドキュメント から:
Elmの保証の1つは、実際には実行時エラーが発生しないことです。 NoRedInkはElmを実稼働で約1年使用していますが、まだ使用されていません。 Elmのすべての保証と同様に、これは基本的な言語設計の選択に帰着します。この場合、Elmはエラーをデータとして扱うという事実に助けられています。 (ここで私たちは物事のデータをたくさん作ることに気づきましたか?)
Elmデザイナーは "実行時例外なし" を主張するのは少し大胆ですが、 "実際に"と認定します。それらがおそらく意味することは、「JavaScriptでコーディングしている場合よりも予期しないエラーが少ない」ことです。
Elmの主張:
ランタイムなしエラー実際にはありません。 nullなし。 undefinedは関数ではありません。
しかし、ランタイムについて質問しますexceptions。違いがあります。
Elmでは、予期しない結果を返すものはありません。ランタイムエラーを生成する有効なプログラムをElmで作成することはできません。したがって、例外は必要ありません。
したがって、質問は次のようになります。
「実行時エラーがない」ことの利点は何ですか?
ランタイムエラーが発生しないコードを記述できれば、プログラムがクラッシュすることはありません。