web-dev-qa-db-ja.com

Haskellに「明らかな」型クラスがないのはなぜですか

オブジェクト指向言語を検討する:

オブジェクト指向プログラミングのバックグラウンドを持っているほとんどの人は、Javaの本質を捉えたさまざまな言語の一般的で直感的なインターフェースに精通しています Collection &- List インターフェース。 Collectionは、必ずしも自然な順序付け/インデックス付けを備えていないオブジェクトのコレクションを指します。 Listは、自然な順序付け/索引付けを持つコレクションです。これらのインターフェイスは、他の言語の同等のインターフェイスと同様に、Javaの多くのライブラリデータ構造を抽象化します。ほとんどのライブラリデータ構造を効果的に使用するには、これらのインターフェイスを深く理解する必要があります。

Haskellへの移行:

Haskellには、オブジェクトのインターフェースと同様に型に作用する型クラスシステムがあります。 Haskellは、型が機能を考慮している場合、ファンクター、アプリケーション、モナドなどに関して 適切に設計された型クラス階層 を持っているようです。彼らは明らかに 正しくてよく抽象化された型クラス を望んでいます。しかし、多くのHaskellのコンテナを見ると( ListMapSequenceSetVector )それらはほとんどすべて非常に類似した(または同一の)関数を持っていますが、型クラスによって抽象化されていません。

いくつかの例:

  • null「空」をテストするため
  • length/size要素数
  • elem/memberセットを含める場合
  • emptyおよび/またはsingletonデフォルトの構築
  • union for set union
  • (\\)/diffセット差
  • (!)/(!!)安全でない索引付け(部分関数)
  • (!?)/lookup安全なインデックス作成(関数全体)

上記の関数のいずれかを使用したいが、2つ以上のコンテナーをインポートした場合は、インポートしたモジュールから関数を非表示にするか、モジュールから必要な関数のみを明示的にインポートするか、インポートしたモジュールを修飾する必要があります。しかし、すべての関数が同じ論理機能を提供するため、面倒なように思えます。関数が各モジュールで個別にではなく型クラスから定義されている場合、コンパイラの型推論メカニズムでこれを解決できます。また、型クラスを共有している限り、基になるコンテナーの切り替えが簡単になります(つまり、ランダムアクセスの効率を高めるためにSequenceの代わりにListを使用できます)。

Haskellにこれらの関数のいくつかを統合および一般化するためのCollectionおよび/またはIndexable型クラスがないのはなぜですか?

55
recursion.ninja

部分的には、モナドと矢印がHaskellの新しい革新的な機能であるのに対し、コレクションは比較的平凡であるためです。 Haskellは研究言語として長い歴史があります。興味深いリサーチクエスチョン(モナドインスタンスの設計とモナドのジェネリック操作の定義)は、「産業強度」の研磨(コンテナAPIの定義)よりも多くの開発努力を要します。

部分的には、これらのタイプが3つの異なるパッケージ(ベース、コンテナー、およびベクター)からのものであり、3つの別個の履歴と設計者があるためです。そのため、設計者が単一の型クラスのインスタンスを提供する際の調整が難しくなります。

部分的には、その理由は、あなたが言及した5つのコンテナーすべてをカバーする単一の型クラスを定義することが非常に難しいためです。 List、Sequence、Vectorは比較的似ていますが、MapとSetには完全に異なる制約があります。 List、Sequence、Vectorの場合、単純なコンストラクタークラスが必要ですが、Setの場合は、要素タイプにOrdインスタンスが必要なため、機能しません。さらに悪いことに、Mapはほとんどのメソッドをサポートできますが、そのシングルトン関数には2つのパラメーターが必要であり、残りのパラメーターは1つだけです。

20
Jonathan Cast

他の回答が指摘しているように、Haskellは異なる語彙を使用する傾向があります。しかし、違いについては理由をうまく説明していないと思います。

Javaのような言語では、関数は「第一級市民」ではありません。匿名関数が最新バージョンで利用できるのは事実ですが、このスタイルのインターフェース(Collection、Indexable、Interableなど)はそれ以前に設計されました。

これにより、コードを渡すのが面倒になるため、他の人のデータコードに渡すことをお勧めします。例えば:

  • JavaのIterableを実装するデータにより、s write for (Foo x : anIterable) { ... }
  • PHPのArrayAccessを実装するデータにより、s書き込みanArrayAccess[anIndex]

このスタイルは、ジェネレーターを実装するOO言語でも見られます。これは、sfor yieldedElement in aGenerator: ...を記述する別の方法だからです。

Haskellはその型クラスで異なるアプローチを取ります:私たちは私たちのコード他の人のデータに渡されることを好みます。いくつかの(簡略化された)例:

  • Functorsはコードを受け入れ、「含まれている」すべての要素に適用します
  • Monadsはコードを受け入れ、ある種の「シーケンス」で適用します
  • Foldablesはコードを受け入れ、それを使用してコンテンツを「削減」します

weourIterableループでコードを呼び出す必要があるため、Javaはforのみを必要とします。これにより、正しく呼び出されることを確認できます。 他の誰かのコードが私たちを呼び出すので、Haskellはより具体的な型クラスを必要とします。したがって、howを呼び出す必要があります。 mapfoldunfoldなどですか?

ありがたいことに、型システムは私たちが正しい方法を選択するのに役立ちます;)

39
Warbo

lens パッケージは、この一部を提供します。

  • 空のテスト、空のコンテナの作成これらは両方とも Control.Lens.EmptyAsEmpty型クラスによって提供されます=。

  • キー/インデックスによる要素へのアクセスControl.Lens.AtAtおよびIxed型クラス。

  • セットのようなコンテナのメンバーシップを確認していますControl.Lens.AtContains型クラス。

  • シーケンスのようなコンテナへの要素の追加と削除Control.Lens.ConsConsおよびSnoc型クラス。

また、pureタイプクラスのApplicativeメソッドは、「シングルトン」コンテナを作成するためによく使用されます。 Setのように、Haskellのファンクター/アプリケーションではないものについては、おそらくpoint from Data.Pointed を使用できます。

36
danidiaz

Haskellには、基本パッケージのコレクションを操作するためのいくつかの型クラスがあります。 FunctorFoldable および Traversable は、コレクションの操作に役立ちます。 MonoidApplicative および/または Alternative 型クラスはコレクションの構築に役立ちます。

一緒に、これらのクラスは質問で言及された操作のほとんどをカバーしますが、よりコンテナ固有の関数よりも効率が悪いかもしれません(これらの多くはクラスメソッドであり、そのデフォルト定義は必要に応じてオーバーライドできます)。

null「空」をテストするため

base4.8(any (const True)は以前のバージョンの代替)であるため、Foldableはnullをサポートします。

要素数の長さ/サイズ:

base4.8(getSum . foldMap (const 1)は以前のバージョンの代替)であるため、Foldableはlengthをサポートします。

セットを含めるためのelem/member

折りたたみ式はelemnotElemおよびmemberをサポートします。

デフォルトの構造の場合は空および/またはシングルトン

空の場合、MonoidのmemptyとAlternativeのemptyがあります。シングルトンの場合、Applicativeからpureがあります。

セットユニオンのユニオン

Monoidからはmappend、Alternativeからは<|>があります。それらは必ずしも集合和集合を実装するわけではありませんが、空と、通常はシングルトンと検索と一緒にうまく機能する何らかの形の和集合を実装します。

(\)/セット差の差分

残念ながら、これはサポートされていません。

(!)/(!!)安全でない索引付け(部分関数)

fromJustを、安全なインデックス作成のための関数と一緒に使用できます。

(!?)/安全な索引付けの検索(全機能)

Foldableからfindがあります。

26
Toxaris

このような型クラスは標準のHaskellに存在しますが、同等のOO対応するものと同じ名前ではありません。たとえば、Collection型クラスはHaskellではFoldableと呼ばれます。これを使用してテストできます構造が空の場合(foldr (const False) True x)、要素の数をカウントする場合(foldMap (const 1) x)、またはセットのメンバーシップをテストする場合(foldr (\e' present -> (e==e') || present) False x for some e)。

要素ルックアップなどの操作の場合、シーケンシャルデータで機能する可能性のあるArrayタイプクラスがあります。柔軟性を高めるために、たとえば次のような独自のIndexableクラスを作成できます(レンズに注意してください)。

class Indexable m k a where
  at :: k -> Lens' m (Maybe a)

Null要素と集合和集合はMonoid型クラスに属します(ここでmappend == union)。この観点から、セットの違いは独自の型クラスDifferentiable(数十のHaskellライブラリにすでに存在していると確信しています)に実装することもでき、命令型言語と完全に互換性があります。

Haskellは、数学者などによって設計されているため、他のほとんどの言語と同じ語彙を使用していませんが、安心してください。素晴らしい言語であることに加えて、実用的な言語ではないという意味ではありません:-)

13
user3340662

法律。良い型クラスには法則があります。優れた型クラスは十分なパラメトリシティを持っているので、その法則は「無料の定理」です。法則のない型クラスは、アドホックな名前のオーバーロードです。

また、 classy-prelude および Edison-API も確認してください。

さまざまなコレクションの側面に対応する型クラスがあります。

  1. 構成:モノイド(モジュールData.Monoid)

  2. シーケンシャルコントロール:Applicative、Monad(モジュールControl.Applicative、Control.Monad)

  3. シーケンシャルコンポジション:Alternative、MonadPlus(モジュールControl.Applicative、Control.Monad)

  4. 非シーケンシャルマッピングとリダクション:Functor(mod。Data.Functor)、Foldable(mod。Data.Foldable)

  5. シーケンシャルマッピングとリダクション:トラバーサブブル(モジュールData.Traversable)

  6. シリアル化:バイナリ(mod。Data.Binary)

  7. 比較:Eq、Ord(mod。Data.Eq、Data.Ord)

  8. テキスト化:表示、読み取り

  9. 深い評価(通常の形式へ):NFData(mod。Control.DeepSeq)

  10. 一般的なデータ型のトラバーサビリティ:データ(mod。Data.Data)

モノモーフィックコレクション(ByteString、IntSet、Text)がFunctorとFoldableを実装できないことを除いて(タイプarity == 1(種類:*-> *)が必要です)

また どちらの(Set a)もFunctorを実装していません

パッケージ mono-traversable は、単形型を除外せずに一部のクラスを再定義します。

更新。パッケージ mono-traversable および classy-prelude を使用して、ほとんどの関数を型クラスに配置しようとしています。

ライブラリ参照プラットフォーム

9
Gabriel Riba