私は学び始めています Haskell 。私はそれに非常に慣れていないので、オンラインブックをいくつか読んで、その基本的な構成を理解するだけです。
それに慣れ親しんでいる人がよく語っている「ミーム」の1つは、全体が「コンパイルできれば機能する」*ことです。これは、型システムの強さに関連していると思います。
この点で、正確にHaskellが他の静的型付け言語よりも優れている理由を理解しようとしています。
別の言い方をすれば、Javaでは、実際にArrayList<String>()
にする必要があるものを含めるために、bury ArrayList<Animal>()
のような凶悪なことを行うことができると思います。ここでの厄介なことは、あなたのstring
に_elephant, giraffe
_などが含まれていること、そして誰かがMercedes
を入力した場合-コンパイラはあなたを助けません。
Ididdo ArrayList<Animal>()
の場合、その後のある時点で、私のプログラムが本当に動物ではなく、車両に関するものであると判断した場合、たとえば、_ArrayList<Animal>
_を生成する関数を_ArrayList<Vehicle>
_およびmy IDEを生成するように変更できます。 =コンパイルが中断しているところはどこでも教えてくれます。
私の仮定は、これがstrong型システムで意味することですが、Haskellのほうが優れている理由は明らかではありません。別の言い方をすれば、良いJavaでも悪いJavaでも書くことができます。Haskellでも同じことができると思います(つまり、実際にはファーストクラスのデータ型であるはずの文字列/ intにものを詰め込みます)。
重要な/基本的な何かが欠けていると思います。
自分のやり方の誤りを見せてくれてとても嬉しいです!
Haskellで使用できる型システム機能の順序付けされていないリストを次に示しますJava(私の知る限り、これは確かに弱いw.r.t. Javaです)
Eq
ualityのようなものが、Haskellコンパイラーによってユーザー定義型から自動派生される方法について説明します。基本的に、これを行う方法は、ユーザー定義型の基礎となる一般的で単純な構造を調べ、それを値の間で一致させることです。これは、構造的平等の非常に自然な形です。data Bt a = Here a | There (Bt (a, a))
です。 Bt a
の有効な値について慎重に検討し、その型がどのように機能するかに注意してください。トリッキーです!IO
などの明確に定義されたインターフェースを持つ「マジック」タイプを記述することです。 Java正直に言うと、おそらく実際にはより良い抽象型のストーリーがありますが、インターフェイスがより一般的になるまでは、本当にそうだったとは思いません。mtl
エフェクトタイピングシステム全体、一般化されたFunctorフィックスポイント。リストはどんどん続きます。上位の種類で最もよく表現されるものがたくさんあり、ユーザーがこれらのことについて話すことを可能にするタイプのシステムは比較的少数です。(+)
を一緒に実行するつもりですか?ああ、Integer
、OK!今すぐ正しいコードをインライン化しましょう!」のような単純な「タイプ方程式システム」を解くことができます。より複雑なシステムでは、より興味深い制約を確立している可能性があります。mtl
ライブラリ全体は、この考えに基づいています。(forall a. f a -> g a)
のような型に気づかない( parametricity を参照)高次構造間のマッピング。ストレートHMでは、この型で関数を記述できますが、上位の型では、 argument のような関数を要求します:mapFree :: (forall a . f a -> g a) -> Free f -> Free g
。 a
変数は引数内でのみバインドされることに注意してください。つまり、関数mapFree
の definer は、a
のユーザーではなく、使用時にmapFree
がインスタンス化されるものを決定します。種類のインデックス付きタイプとタイププロモーション。この時点で私は本当にエキゾチックになっていますが、これらは時々実用的です。開いているか閉じているハンドルのタイプを記述したい場合は、非常にうまく行うことができます。次のスニペットで、State
は非常に単純な代数型であり、その値も型レベルにプロモートされていることに注意してください。次に、Handle
のような特定の kinds の引数を取ることで、State
のような typeコンストラクターについて話します。すべての詳細を理解するのは紛らわしいですが、非常に正しいです。
data State = Open | Closed
data Handle :: State -> * -> * where
OpenHandle :: {- something -} -> Handle Open a
ClosedHandle :: {- something -} -> Handle Closed a
動作するランタイムタイプの表現。 Javaは、型の消去と、一部の人々のパレードでその機能の雨が降ることで悪名高いです。型の消去 is 正しい方法は、あたかも関数getRepr :: a -> TypeRepr
がある場合、少なくともパラメトリック性に違反します。さらに悪いのは、それが実行時に安全でない強制をトリガーするために使用されるユーザー生成関数である場合、大規模な安全性の懸念事項。HaskellのTypeable
システムでは、安全なcoerce :: (Typeable a, Typeable b) => a -> Maybe b
を作成できます。このシステムは、Typeable
がコンパイラーに実装されていることに依存しており(ユーザーランドではありません)、また、 Haskellの型クラスメカニズムとそれが従うことが保証されている法則。
これらだけでなく、Haskellの型システムの価値は、型が言語をどのように記述するかにも関係しています。型システムを通じて価値を生み出すHaskellのいくつかの機能を次に示します。
IO a
と呼ばれる特定の超特殊型を使用するという概念が生まれましたa
型の値をもたらす副作用のある計算を表します。これは、純粋な言語の内部に埋め込まれた very Niceエフェクトシステムの基盤です。null
がありません。 null
が現代のプログラミング言語の10億ドルの間違いであることは誰もが知っています。代数型、特にA
型をMaybe A
型に変換することにより、「存在しない」状態を型に追加するだけで、null
の問題を完全に緩和できます。Bt a
型を振り返り、サイズを計算する関数size :: Bt a -> Int
を記述してみてください。 size (Here a) = 1
やsize (There bt) = 2 * size bt
のようになります。運用上はそれほど複雑ではありませんが、最後の方程式のsize
への再帰呼び出しは異なる型で発生しますが、全体的な定義にはナイス一般化型size :: Bt a -> Int
があります。これは完全な推論を打破する機能ですが、型シグネチャを提供すると、Haskellはそれを許可します。私は続けることができますが、このリストはあなたを始めて、そしてそれからいくつかを得るはずです。
for
ループを書くことができますが、for
ループには戻り値の型の概念がないため、同じ静的型保証はありません。a :: Integer
b :: Maybe Integer
c :: IO Integer
d :: Either String Integer
Haskellの場合:整数、nullの可能性がある整数、値が外部の世界からのものである整数、および代わりに文字列の可能性がある整数は、すべて異なる型です-およびコンパイラーはこれを強制します =。これらの違いを尊重しないHaskellプログラムをコンパイルすることはできません。
(ただし、型宣言を省略できます。ほとんどの場合、コンパイラーは、変数の最も一般的な型を判別して、コンパイルを成功させることができます。それではうまくいきませんか?)
関連SO質問 。
私はあなたがhaskellで同じことをできると思います(つまり、本当にファーストクラスのデータ型であるべきものを文字列/ intに詰め込みます)
いいえ、実際にはできません。少なくともJavaと同じ方法ではありません。Javaでは、次のようなことが起こります。
String x = (String)someNonString;
そしてJavaは喜んであなたの非文字列を文字列としてキャストします。Haskellはこの種のものを許可せず、ランタイムエラーのクラス全体を排除します。
null
は(Nothing
のように)型システムの一部であるため、明示的に要求して処理する必要があり、他のクラスの実行時エラー全体を排除します。
他にもたくさんの微妙な利点があります-特に再利用と型クラスに関して-通信するのに十分な知識を持っている私には専門知識がありません。
しかし、ほとんどの場合、Haskellの型システムは多くの表現力を許可しているためです。ほんの少しのルールでたくさんのことができます。常に存在するHaskellツリーについて考えてみましょう。
data Tree a = Leaf a | Branch (Tree a) (Tree a)
かなり読みやすい1行のコードで、汎用バイナリツリー全体(および2つのデータコンストラクター)を定義しました。すべていくつかのルールを使用しているだけです( 合計タイプと製品タイプ を持っています)。これは、Javaの3〜4個のコードファイルとクラスです。
特にタイプシステムを崇拝する傾向があるものの中で、この種の簡潔さ/優雅さは高く評価されています。
それに慣れ親しんでいる人がよく語っている「ミーム」の1つは、全体が「コンパイルできれば機能する」*ことです。これは、型システムの強さに関連していると思います。
これは主に小さなプログラムに当てはまります。 Haskellは、他の言語で簡単にできる間違いを防ぐ(例:Int32
とWord32
と何かが爆発します)が、すべてのミスを防ぐことはできません。
Haskellはlotのリファクタリングを実際に容易にします。あなたのプログラムが以前は正しく、それがタイプチェックした場合、マイナーな修正の後でもそれがまだ正しいであろうかなりの可能性があります。
この点で、Haskellが他の静的型付き言語よりも優れている理由を理解しようとしています。
Haskellの型はかなり軽量であり、新しい型を宣言するのは簡単です。これは、Rustのような言語とは対照的です。この言語では、すべてが少し面倒です。
私の仮定では、これは強い型システムによって人々が意味することですが、Haskellのほうが優れている理由は明らかではありません。
Haskellには、単純な和や積タイプ以外の多くの機能があります。普遍的に数量化されたタイプ(例:id :: a -> a
) 同様に。 JavaやRustなどの言語とはかなり異なる関数を含むレコードタイプを作成することもできます。
GHCは、タイプのみに基づいていくつかのインスタンスを導出することもできます。ジェネリックの登場以来、タイプ間でジェネリックな関数を作成できます。これは非常に便利であり、Javaの場合よりも流暢です。
別の違いは、Haskellは(少なくとも執筆時点では)比較的良好なタイプエラーを持つ傾向があることです。 Haskellの型推論は洗練されており、何かをコンパイルするために型注釈を提供する必要があることは非常にまれです。これはRustとは対照的です。Rustは、コンパイラーが原則的に型を推定できる場合でも、型の推論でアノテーションが必要になる場合があります。
最後に、Haskellにはタイプクラスがあり、その中に有名なモナドがあります。モナドはたまたまエラーを処理する特に良い方法です。それらは基本的に、恐ろしいデバッグをせず、タイプセーフをあきらめることなく、null
のほぼすべての便利さを提供します。したがって、これらの型にfunctionsを書き込む機能は、実際にそれらを使用するように促すときにかなり重要です!
別の言い方をすれば、良いJavaでも悪いJavaでも書くことができます。Haskellでも同じことができると思います
それは本当かもしれませんが、重要なポイントが欠けています。Haskellで足で自分を撃ち始めるポイントは、Javaで足で自分を撃ち始めるポイントよりもはるかに進んでいます。