Haskell型システムをこれほどまでに尊敬する理由は何ですか(たとえば、Java)。


204

私はHaskellを学び始めています。私は非常に新しいので、基本的な構成を理解するために、オンラインブックを数冊読んでいます。

よく知っている「ミーム」の1つは、「コンパイルすれば機能します*」というもの全体です。これは型システムの強さに関係していると思います。

この点で、Haskellが他の静的に型付けされた言語より正確に優れている理由を理解しようとしています。

別の言い方をすれば、Javaでは、ArrayList<String>()実際にあるべきものを含めるために、埋めるような凶悪なことを行うことができると思い ますArrayList<Animal>()。ここでは凶悪な事はあなたがいることであるstring含まれているelephant, giraffeなどと、誰かが中に入れた場合Mercedes-あなたのコンパイラは、あなたを助けにはなりません。

私は場合はやったArrayList<Animal>()私は私のプログラムは、それが車についてです、動物については本当にないことにした場合、時間内にいくつかの後の時点で、その後、私は、たとえば、生産機能に変更することができますArrayList<Animal>生成することをArrayList<Vehicle>、私のIDEがどこでも私に教えてくださいコンパイルの中断です。

私の推測では、これは人々が強力な型システムによって意味するものですが、Haskellの方が優れている理由は私には明らかではありません。別の言い方をすれば、良いJavaまたは悪いJavaを書くことができます。Haskellでも同じことができると思います(つまり、実際にファーストクラスのデータ型であるはずの文字列/ intに詰め込みます)。

重要/基本的なものが不足していると思われます。
私のやり方の間違いを見せていただければ幸いです!


31
本当の答えを書くよりも多くの人に知識を与えますが、その要点は次のとおりです。C#のような静的型付け言語には、防御可能なコードの記述を支援する型システムがあります。Haskellのような正しい(つまり、証明可能な)コードの記述を支援するシステムを入力します。職場での基本原則は、チェックできるものをコンパイル段階に移動することです。Haskellは、コンパイル時にさらに多くのものをチェックします。
ロバートハーヴェイ

8
Haskellについてはあまり知りませんが、Javaについて話すことはできます。強く型付けされているように見えますが、それでもあなたが言ったように「凶悪な」ことをすることができます。型システムに関してJavaが行うほぼすべての保証には、回避方法があります。

12
なぜすべての回答Maybeが最後にしか言及していないのか分かりません。より人気のある言語がHaskellから借用すべきものを1つだけ選択しなければならなかった場合、これがそれです。これは非常に単純なアイデアです(したがって、理論的な観点からはあまり面白くありません)が、これだけで仕事が非常に簡単になります。
ポール

1
ここには素晴らしい答えがありますが、支援するために、タイプシグネチャを学習してください。彼らは人間とプログラムができるようにする理由のタイプ:Javaはどろどろ真ん中再である方法を説明します方法でプログラムについて。
マイケルイースター

6
公平を期すために、「コンパイルすれば、全体がうまくいく」というのは、文字通りの事実ではなくスローガンであることを指摘しなければなりません。はい、Haskellプログラマーは、型チェッカーに合格すると、正確性のいくつかの限定された概念について、正確性の良いチャンスを与えることを知っていますが、それは確かに文字通り、普遍的な「真」のステートメントではありません!
トム・エリス

回答:


230

Haskellで利用でき、Javaで利用できないかあまり役に立たないタイプシステム機能の順不同のリストを示します(私の知る限り、Javaに関しては明らかに弱い)

  • 安全。Haskellのタイプには、かなり優れた「タイプセーフティ」プロパティがあります。これはかなり具体的ですが、本質的には、あるタイプの値が別のタイプに変換することだけができないことを意味します。これは、可変性と矛盾する場合があります(OCamlの値の制限を参照)
  • 代数データ型。Haskellの型は、本質的に高校の数学と同じ構造を持っています。これはとてつもなくシンプルで一貫性がありますが、結局のところ、可能な限り強力です。これは、型システムの優れた基盤にすぎません。
    • データ型汎用プログラミング。これは、ジェネリック型とは異なります(一般化を参照)。代わりに、前述の型構造の単純さにより、その構造上で一般的に動作するコードを書くのは比較的簡単です。後にEq、Haskellコンパイラーによってユーザー定義型のualityのようなものがどのように自動派生されるかについて説明します。基本的に、これを行う方法は、ユーザー定義型の基礎となる共通の単純な構造を調べ、値間で一致させることです。これは非常に自然な構造の平等です。
  • 相互に再帰的な型。これは、自明ではない型を書くための重要なコンポーネントです。
    • ネストされたタイプ。これにより、異なる型で再帰する変数に対して再帰型を定義できます。たとえば、バランスツリーの1つのタイプはdata Bt a = Here a | There (Bt (a, a))です。の有効な値について慎重に検討し、Bt aそのタイプがどのように機能するかを確認します。トリッキーです!
  • 汎化。これは、型システム(ahem、あなたを見ている、Go)に含めるにはあまりにも愚かなことです。型変数の概念と、その変数の選択に依存しないコードについて話す能力を持つことが重要です。Hindley MilnerはSystem Fから派生した型システムです。Haskellの型システムはHMタイピングの精緻化であり、System Fは本質的に一般化の炉床です。私が言いたいのは、Haskellには非常に優れた一般化ストーリーがあるということです。
  • 抽象型。ここでのHaskellの話は素晴らしいものではありませんが、存在しません。パブリックインターフェースを持ち、プライベートな実装を持つタイプを記述することは可能です。これにより、実装コードへの変更を後から許可することができます。また、重要なのは、Haskellのすべての操作の基礎であるため、などの明確に定義されたインターフェイスを持つ「マジック」タイプを書くことIOです。正直なところ、Javaには実際には、より優れた抽象型のストーリーがありますが、インターフェイスがより一般的になるまで、それは本当のことだとは思いません。
  • パラメトリック。Haskellの値は持たない任意のユニバーサル操作を。Javaは、参照の平等やハッシュなどのことでこれに違反し、さらに強制ではさらにひどくなります。これが意味するのは、操作または値の意​​味をその型から完全にかなりの程度まで知ることができる型についての自由定理を得るということです。
  • より複雑なタイプをエンコードすると、より種類の高いタイプがすべてのタイプを表示します。Functor / Applicative / Monad、Foldable / Traversable、mtlエフェクトタイピングシステム全体、一般化されたFunctorフィックスポイント。リストは延々と続く。上位の種類で最もよく表現されるものはたくさんあり、ユーザーがこれらのことについて話すことができるタイプのシステムは比較的少数です。
  • 型クラス。型システムをロジックと考える場合(これは便利です)、多くの場合、物事を証明することが求められます。多くの場合、これは本質的に回線ノイズです。正しい答えは1つしかなく、プログラマーがこれを述べるのは時間と労力の無駄です。タイプクラスは、Haskellが証明を生成する方法です。より具体的に言えば、これにより、「どの型で(+)一緒に物事をしようとしているのか?ああInteger、OK!今すぐ正しいコードをインラインしましょう!」のような単純な「型方程式システム」を解くことができます。より複雑なシステムでは、より興味深い制約を設定している場合があります。
    • 制約計算。Haskellの制約(タイプクラスプロローグシステムに到達するためのメカニズム)は、構造的に型付けされています。これにより、複雑な制約をより単純な制約から組み立てることができる、サブタイプ関係の非常に単純な形式が提供されます。mtlライブラリ全体はこの考えに基づいています。
    • 派生。タイプクラスシステムの標準性を促進するために、ユーザー定義型がインスタンス化する必要のある制約を記述するために、多くの場合簡単なコードを書く必要があります。Haskell型の非常に通常の構造を行うと、多くの場合、コンパイラにこの定型句を実行するよう依頼することができます。
    • class class prologと入力します。Haskell型クラスソルバー(前述の「証明」を生成しているシステム)は、本質的に、より良いセマンティックプロパティを持つPrologの不自由な形式です。これは、タイププロローグで本当に毛むくじゃらのものをエンコードし、コンパイル時にすべて処理されることを期待できることを意味します。良い例は、順序を忘れた場合に2つの異種リストが同等であるという証明を解くことです。これらは同等の異種「セット」です。
    • マルチパラメータータイプのクラスと機能の依存関係。これらは、基本型クラスプロローグに対する非常に有用な改良です。プロローグを知っていれば、複数の変数の述語を書くことができると、表現力がどれだけ増加するか想像できます。
  • かなり良い推論。Hindley Milner型システムに基づく言語は、かなり良い推論をしています。HM自体には完全な推論があるため、型変数を記述する必要はありません。Haskellの最も単純な形式であるHaskell 98は、非常にまれな状況で既にそれを捨てています。一般に、現代のHaskellは、HMにさらに力を加え、ユーザーが不満を言うのを見ながら、完全な推論の空間をゆっくりと減らす実験でした。人々が文句を言うことはめったにありません—Haskellの推論はかなり良いです。
  • 非常に、非常に、非常に弱いサブタイピングのみ。タイプクラスのプロローグからの制約システムには、構造的なサブタイピングの概念があることを以前に述べました。それがHaskellのサブタイプの唯一の形式です。サブタイピングは推論と推論のためにひどいです。これにより、これらの問題のそれぞれが著しく困難になります(平等のシステムではなく、不平等のシステム)。また、誤解しやすいです(サブクラス化はサブタイピングと同じですか?もちろんそうではありません!しかし、人々はそれを非常に頻繁に混乱させ、多くの言語がその混乱を助長します!
    • なお、最近では(初期の2017年)スティーブン・ドーラン氏は、彼の公表論文をMLsub、持ちMLとヒンドリー-ミルナー型推論の変種とても素敵なサブタイプの話を(も参照します)。これは、私が上で書いたもの(ほとんどのサブタイピングシステムが壊れていて推論が悪い)を取り除くものではありませんが、完全な推論とサブタイピングがうまく連携する有望な方法を今日発見したことを示唆しています。さて、完全に明確にするために、Javaのサブタイピングの概念は、Dolanのアルゴリズムとシステムを利用することはできません。サブタイピングの意味を再考する必要があります。
  • 上位ランクタイプ。私は一般化以前の話、それは変数を一般化している種類について話すことができて便利です単なる一般化よりも、それらの中に。たとえば、これらの構造が「含む」ものに気づかない高次構造間のマッピング(パラメトリック性を参照)は、のようなタイプを持ち(forall a. f a -> g a)ます。ストレートHMでは、この型で関数を記述できますが、上位型では、そのような関数を次のような引数として要求しますmapFree :: (forall a . f a -> g a) -> Free f -> Free ga変数は引数内でのみバインドされることに注意してください。これは、関数の定義者が、のユーザーではなく、使用時にインスタンス化されるmapFreeものを決定aすることを意味しますmapFree
  • 存在タイプ。上位タイプは、私たちが普遍的な定量化の話をすることができますが、実存的なタイプは、私たちが存在量化についてお話しましょう:単にそこにあるという考えに存在するいくつかの方程式を満たすいくつかの未知のタイプを。これは結局有用であり、それについてもっと長く続けるには長い時間がかかるでしょう。
  • タイプファミリー。Prologで常に考えるとは限らないため、タイプクラスのメカニズムが不便な場合があります。タイプファミリを使用すると、タイプ間の機能的な関係を簡単に記述できます。
    • 閉じたタイプファミリ。タイプファミリはデフォルトで開いていますが、これは面倒です。なぜなら、いつでも拡張できますが、成功を期待して「反転」することはできないからです。これは、単射性を証明できないためですが、閉じたタイプファミリでは可能です。
  • インデックス付きの種類と型の昇格。私はこの時点で本当にエキゾチックになっていますが、これらは時々実用的です。開いているか閉じているタイプのハンドルを作成する場合は、非常にうまく行うことができます。次のスニペットでStateは、非常に単純な代数型であり、その値も型レベルに昇格されています。次に、のような特定の種類の引数を取るような型コンストラクタについてHandle説明します。すべての詳細を理解するのは混乱しますが、非常に正しいことでもあります。State

    data State = Open | Closed
    
    data Handle :: State -> * -> * where
      OpenHandle :: {- something -} -> Handle Open a
      ClosedHandle :: {- something -} -> Handle Closed a
    
  • 動作するランタイム型表現。Javaは、型を消去し、一部の人々のパレードでその機能の雨を降らせることで有名です。型消去正しい方法です。ただし、機能がある場合は、getRepr :: a -> TypeRepr少なくともパラメーターに違反しています。さらに悪いことに、それが実行時に安全でない強制をトリガーするために使用されるユーザー生成関数である場合は、安全上の大きな懸念があります。HaskellのTypeableシステムでは、金庫を作成できますcoerce :: (Typeable a, Typeable b) => a -> Maybe b。このシステムはTypeable、コンパイラー(ユーザーランドではなく)に実装されていることに依存しており、Haskellのタイプクラスメカニズムおよび従うことが保証されている法律がなければ、このような素晴らしいセマンティクスを与えることはできません。

しかし、これらだけでなく、Haskellの型システムの価値は、型が言語をどのように記述するかに関係しています。型システムを通じて価値を高めるHaskellのいくつかの機能を以下に示します。

  • 純度。Haskellでは、「副作用」の非常に非常に広い定義に対して、副作用は許可されていません。タイプは入力と出力を管理し、副作用なしに入力と出力ですべてを考慮する必要があるため、これにより、より多くの情報をタイプに入れる必要があります。
    • IO。その後、Haskellは任意の実際のプログラムは、いくつかの-そう型クラス、高いkinded種類、および抽象型の組み合わせと呼ばれる特定の超特殊なタイプを使用しての概念を生み出した含める必要がありますので、エフェクト側について話をする方法必要IO a表すためにtypeの値をもたらす副作用の計算a。これは、純粋な言語の内部に組み込まれた非常に優れたエフェクトシステムの基盤です。
  • の欠如null。誰もがそれnullが現代のプログラミング言語の10億ドルの間違いであることを知っています。代数型、特に型を型Aに変換することで、「存在しない」状態を単に型に追加する機能はMaybe A、の問題を完全に軽減しますnull
  • 多態的な再帰。これにより、独自の一般化の各再帰呼び出しで異なる型で変数を使用するにもかかわらず、型変数を一般化する再帰関数を定義できます。これについて話すのは難しいですが、ネストされた型について話すのに特に役立ちます。Bt a前から型を振り返り、サイズを計算する関数を記述してみてくださいsize :: Bt a -> Int。これは、ビットのように見ていきますsize (Here a) = 1size (There bt) = 2 * size bt。操作上はそれほど複雑ではありませんがsize、最後の式での再帰呼び出しは異なる型で発生ますが、全体の定義には優れた一般化型があることに注意してsize :: Bt a -> Intください。これは完全な推論を壊す機能ですが、タイプシグネチャを指定すると、Haskellが許可することに注意してください。

続けていくことができますが、このリストはあなたを始めて、そしていくらかを得るべきです。


7
Nullは10億ドルの「間違い」ではありませんでした。意味のあるもの存在する前にポインタが間接参照されないことを静的に検証できない場合があります。このような場合に逆参照トラップを試みることは、ポインターが最初に無意味なオブジェクトを識別することを要求するよりも良い場合があります。nullに関連した最大の間違いは、与えられた、トラップされるが、トラップされないchar *p = NULL;*p=1234char *q = p+5678;*q = 1234;
実装でした-supercat

37
それはトニー・ホアから直接引用されただけです:en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractionsnullポインター演算に必要な場合があると確信していますが、代わりに、ポインター演算は、nullがまだ間違いではないということではなく、言語のセマンティクスをホストするのに悪い場所だと解釈します。
J.アブラハムソン

18
@supercat、あなたは確かにヌルなしで言語を書くことができます。それを許可するかどうかは選択です。
ポールドレーパー

6
@supercat-この問題はHaskellにも存在しますが、形式は異なります。Haskellは通常怠laで不変であるため、評価されないp = undefined限り書くことができますp。さらに便利なのは、undefined評価しない限り、何らかの可変参照を追加できることです。より深刻な課題は、終了しないかもしれない遅延計算であり、もちろん決定できません。主な違いは、これらはすべて明確なプログラミングフォールトであり、通常のロジックの表現には決して使用されないことです。
クリスチャンコンクル

6
@supercat Haskellは、参照セマンティクスを完全に欠いています(これは、参照の透明性の概念であり、参照を参照先に置き換えることですべてが保持されることを意味します)。したがって、あなたの質問は不適切だと思います。
J.アブラハム

78
  • 完全型推論。実際に複雑な型を「神聖なくだらない、型の署名を書くだけ」のように感じることなく、ユビキタスに使用できます。
  • 型は完全に代数的であるため、複雑なアイデアを非常に簡単に表現できます。
  • Haskellには型クラスがあります。これはインターフェイスのようなものですが、1つの型のすべての実装を同じ場所に置く必要はありません。ソースへのアクセスを必要とせずに、既存のサードパーティタイプの独自のタイプクラスの実装を作成できます。
  • 高階関数および再帰関数は、型チェッカーの範囲内により多くの機能を配置する傾向があります。たとえば、filterを考えます。命令型言語ではfor、同じ機能を実装するループを作成できますが、forループには戻り型の概念がないため、同じ静的型の保証はありません。
  • サブタイプの欠如により、パラメトリック多型が大幅に簡素化されます。
  • Haskellでは、より種類の高いタイプ(タイプのタイプ)を指定して使用するのが比較的簡単です。

7
いい答え-上位の親切なタイプの簡単な例を教えてもらえますか。Javaでできない理由を理解するのに役立つと思います。
-phatmanace

3
ここにいくつかの良い例があります
カールビーレフェルト

3
パターンマッチングも非常に重要です。つまり、オブジェクトのタイプを使用して、非常に簡単に決定を下すことができます。
ベンジャミングリュンバウム

2
@BenjaminGruenbaum私はそれを型システム機能と呼ぶとは思わない。
ドーバル

3
ADTとHKTは間違いなく答えの一部ですが、この質問をする人がなぜ役に立つのかはわからないと思いますが、これを説明するには両方のセクションを拡張する必要があることをお勧めします
jk。

62
a :: Integer
b :: Maybe Integer
c :: IO Integer
d :: Either String Integer

Haskellでは、整数、nullの可能性のある整数、外部の世界からの値の整数、代わりに文字列の可能性のある整数はすべて別個のタイプであり、コンパイラーはこれを強制します。これらの区別を尊重しないHaskellプログラムをコンパイルすることはできません。

(ただし、型宣言を省略することができます。ほとんどの場合、コンパイラーは変数の最も一般的な型を決定できるため、コンパイルが成功します。それは適切ではありませんか?)


11
1この答えは、私はそれがはるかに良い質問のレベルで投げていると思う完了していない間に
JK。

1
+1他の言語にもあるMaybe(たとえばJava OptionalやScalaのOption)ことを説明するのに役立ちますが、それらの言語では中途半端なソリューションnullです。そのタイプの変数にいつでも割り当てることができ、実行時にプログラムを爆発させることができます。時間。これはHaskell [1]では起こりえません。null値がないため、単純にチートすることはできません。([1]:実際、があるfromJustときなどの部分的な関数を使用してNullPointerExceptionと同様のエラーを生成できますが、Nothingそれらの関数はおそらく眉をひそめています)。
アンドレスF.

2
「値が外の世界から来た整数」-「IO Integer実行されると整数を与えるサブプログラム」に近づかないでしょうか?a)main = c >> cfirstによって返される値はc2番目と異なる場合がcありaますが、その位置に関係なく同じ値になります(単一のスコープ内にある限り)b)外界からの値をサナタイゼーションを実施するために示す型があります(つまり、直接入力するのではなく、ユーザーからの入力が正しいか悪意がないかを最初に確認します)。
マチェイピエチョトカ

4
Maciej、それはより正確だろう。シンプルさを目指して努力していました。
-WolfeFan

30

多くの人がHaskellの良い点を挙げています。しかし、「型システムがプログラムをより正確にするのはなぜか」というあなたの特定の質問に対する答えとして、答えは「パラメトリック多相性」だと思います。

次のHaskell関数を検討してください。

foobar :: x -> y -> y

あり、文字通り唯一の1つの可能な方法この機能を実装するには。型シグネチャによって、この関数実行できることは1つしかないため、この関数の実行内容を正確に伝えることができます。[OK、まったくではないが、ほとんど!]

停止して、しばらくの間考えてください。それは実際には本当に大したことです!つまり、このシグネチャを使用して関数を作成した場合、関数が意図したもの以外の操作を実行することは実際には不可能です。(もちろん、型の署名自体はまだ間違っている可能性があります。すべてのバグを防ぐプログラミング言語はありません。)

次の機能を検討してください。

fubar :: Int -> (x -> y) -> y

この機能は不可能です。文字通りこの機能を実装することはできません。タイプシグニチャからそれを知ることができます。

ご覧のとおり、Haskell型の署名は非常に多くのことを教えてくれます!


C#と比較してください。(申し訳ありませんが、私のJavaは少しさびています。)

public static TY foobar<TX, TY>(TX in1, TY in2)

この方法でできることはいくつかあります。

  • in2結果として返します。
  • 永遠にループし、何も返しません。
  • 例外をスローし、何も返しません。

実際、Haskellにはこれら3つのオプションもあります。ただし、C#には追加のオプションもあります。

  • nullを返します。(Haskellにはnullはありません。)
  • in2返す前に変更します。(Haskellにはインプレース変更はありません。)
  • リフレクションを使用します。(Haskellにはリフレクションがありません。)
  • 結果を返す前に複数のI / Oアクションを実行します。(Haskellは、ここでI / Oを実行することを宣言しない限り、I / Oを実行させません。)

反射は特に大きなハンマーです。反射を使用しTYて、薄い空気から新しいオブジェクトを構築し、それを返すことができます!両方のオブジェクトを検査し、見つけたものに応じて異なるアクションを実行できます。渡された両方のオブジェクトに任意の変更を加えることができます。

I / Oも同様に大きなハンマーです。コードは、ユーザーにメッセージを表示したり、データベース接続を開いたり、ハードディスクなどを実際に再フォーマットしたりできます。


foobar対照的に、Haskell 関数は一部のデータのみを取得し、そのデータを変更せずに返すことができます。コンパイル時に型が不明なため、データを「見る」ことはできません。新しいデータを作成することはできません。なぜなら...まあ、どのようなタイプのデータをどのように構築するのですか?そのためには反射が必要です。タイプシグネチャはI / Oが実行されていることを宣言しないため、I / Oは実行できません。そのため、ファイルシステムやネットワークと対話したり、同じプログラムでスレッドを実行したりすることもできません!(つまり、スレッドセーフが100%保証されています。)

あなたが見ることができるように、ではないあなたがものの全体の束を行うせる、Haskellはあなたのコードが実際に何をするかについて非常に強い保証を行うことができるようにされます。実際、非常にタイトであるため(実際に多態性コードの場合)、通常、ピースを1つにまとめる方法は1つしかありません。

(明確にするために:タイプシグネチャがあまり伝えないHaskell関数を書くことはまだ可能です。ほとんどInt -> Int何でも可能です。しかしそれでも、同じ入力が常に100%の確実性で同じ出力を生成することを知っています。 Javaはそれさえ保証しません!)


4
+1すばらしい回答です!これは非常に強力で、多くの場合Haskellの初心者には過小評価されています。ところで、より単純な「不可能」な関数はそうfubar :: a -> bでしょう?(はい、私は承知しているunsafeCoerce私たちはその名に「安全でない」と何の話をされていないと仮定し、どちらも新規参入者はそれを心配する必要がありません:。!D)
アンドレスF.

書くことのできない単純な型シグネチャがたくさんあります、はい。たとえば、foobar :: xかなり実装できません
MathematicalOrchid

実際、純粋なコードをスレッドセーフにすることはできませんが、マルチスレッドにすることはできます。オプションは、「これを評価する前に、これを評価する」、「これを評価するとき、別のスレッドでこれを評価することもできます」、「これを評価するとき、これを評価することもできます。別のスレッドで」。デフォルトは「必要に応じて実行」であり、これは本質的に「できるだけ遅く評価する」ことを意味します。
ジョンドヴォルザーク

より一般的には、副作用があるin1またはin2でインスタンスメソッドを呼び出すことができます。または、グローバル状態を変更することができます(これは、HaskellではIOアクションとしてモデル化されていますが、ほとんどの人がIOと考えているものではないかもしれません)。
ダグマックリーン

2
@isomorphismes型x -> y -> yは完全に実装可能です。タイプ(x -> y) -> yは違います。この型x -> y -> yは2つの入力を受け取り、2番目の入力を返します。タイプは(x -> y) -> yかかる機能に動作しx、何とか作るために持っているyことのうちに...
MathematicalOrchid

17

関連するSOの質問

私はあなたがhaskellで同じことをできると仮定しています(つまり、本当にファーストクラスのデータ型でなければならない文字列/整数に物事を詰め込みます)

いいえ、少なくともJavaと同じ方法ではできません。Javaでは、次のようなことが起こります。

String x = (String)someNonString;

Javaは喜んで非文字列を文字列としてキャストしようとします。Haskellはこの種のことを許可せず、ランタイムエラーのクラス全体を排除します。

null型システムの一部であるNothingため(as )、明示的に要求および処理する必要があり、ランタイムエラーの他のクラス全体を排除します。

他にも、特に再利用や型クラスに関しては、コミュニケーションをとるのに十分な知識を持っていないという微妙な利点がたくさんあります。

ほとんどの場合、Haskellの型システムは多くの表現力を可能にするためです。ほんの少しのルールでたくさんのことができます。常に存在するHaskellツリーを考えてみましょう。

data Tree a = Leaf a | Branch (Tree a) (Tree a) 

かなり読みやすい1行のコードで、汎用バイナリツリー全体(および2つのデータコンストラクター)を定義しました。いくつかのルール(合計タイプと製品タイプ)を使用するだけです。これは、Javaの3〜4個のコードファイルとクラスです。

特に、敬意を表するタイプのシステムの中で、この種の簡潔さ/優雅さは高く評価されています。


私はあなたの答えからNullPointerExceptionsだけを理解しました。もっと例を挙げていただけますか?
ジェスビンホセ

2
JLS§5.5.1に必ずしも当てはまるわけではありません:Tがクラス型の場合、| S | <:| T |、または| T | <:| S |。そうしないと、コンパイル時エラーが発生します。そのため、コンパイラーは、変換できない型をキャストすることを許可しません-明らかにそれを回避する方法があります。
スパイダーボリス

私の意見では、型クラスの利点を生かすための最も簡単な方法は、それらがinterface事後に追加できるものであり、それらを実装している型を「忘れない」ことです。つまりinterface、2つList<String>のsが異なる実装を持つ可能性があるs とは異なり、関数への2つの引数が同じ型を持つことを保証できます。型パラメーターをすべてのインターフェイスに追加することにより、技術的にはJavaで非常によく似た操作を行うことができますが、既存のインターフェイスの99%はそれを行わず、ピアの混乱を招くでしょう。
ドーバル

2
@BoristheSpider True、ただし例外のキャストには、ほとんどの場合、スーパークラスからサブクラスへのダウンキャスト、またはインターフェイスからクラスへのダウンキャストが含まれますObject。スーパークラスがになることは珍しくありません。
ドーバル

2
文字列に関する質問のポイントは、キャストや実行時の型エラーとは関係ありませんが型を使用したくない場合、Javaはあなたを作らない-実際のように、データをシリアル化して保存するフォーム、アドホックanyタイプとして文字列を悪用します。Haskellは、これをやめることもありません。Haskellはツールを提供できますが、ネストされたコンテキストで再発明するのに十分なGreenspunningインタープリターを主張する場合、愚かなことを強制的に止めることはできませんnull。言語はできません。
ルーシェンコ

0

よく知っている「ミーム」の1つは、「コンパイルすれば機能します*」というもの全体です。これは型システムの強さに関係していると思います。

これは、小さなプログラムではほとんど当てはまります。Haskellは、他の言語では簡単に間違いを犯すことを防ぎます(an Int32とa Word32と何かが爆発するなど)。しかし、すべての間違いを防ぐことはできません。

Haskellは実際にリファクタリングをはるかに簡単にします。あなたのプログラムが以前に正しかったのに型チェックされた場合、マイナーな修正を加えても正しい可能性があります。

この点で、Haskellが他の静的に型付けされた言語より正確に優れている理由を理解しようとしています。

Haskellの型はかなり軽量で、新しい型を簡単に宣言できます。これは、Rustのような、すべてが少し面倒な言語とは対照的です。

私の推測では、これは人々が強力な型システムによって意味するものですが、Haskellの方が優れている理由は私には明らかではありません。

Haskellには、単純な合計や製品タイプ以外の多くの機能があります。同様に普遍的に数量化されたタイプ(例id :: a -> a)もあります。JavaやRustなどの言語とはまったく異なる関数を含むレコードタイプを作成することもできます。

GHCは、型だけに基づいていくつかのインスタンスを派生させることもできます。ジェネリックの出現以来、型の間で汎用的な関数を作成できます。これは非常に便利で、Javaの場合よりも流です。

もう1つの違いは、Haskellには比較的良い型エラーがある傾向があることです(少なくとも執筆時点では)。Haskellの型推論は洗練されており、コンパイルするために型注釈を提供する必要があることは非常にまれです。これは、Rustとは対照的です。Rustでは、コンパイラが原則として型を推定できた場合でも、型推論で注釈が必要になることがあります。

最後に、Haskellには型クラスがあり、その中でも有名なモナドがあります。モナドは、エラーを処理する特に良い方法です。基本的にnullは、恐ろしいデバッグをせずに、タイプセーフティをあきらめることなく、ほぼすべての利便性を提供します。したがって、これらのタイプで関数を作成する機能は、実際にそれらを使用するよう奨励する際に非常に重要です!

別の言い方をすれば、良いまたは悪いJavaを書くことができます。Haskellでも同じことができると思います

それはおそらく真実かもしれませんが、重要な点が欠けています。Haskellで自分の足で撮影を開始するポイントは、Javaで自分の足で撮影を開始するポイントよりもずっと先にあります。

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