web-dev-qa-db-ja.com

Haskellの大規模設計?

特にHaskellで、大規模な機能プログラムを設計/構築する良い方法は何ですか?

私はたくさんのチュートリアルを経験しました(筆者は自分の好きなSchemeを書いて、Real World Haskellがすぐ近くにあります)-しかし、ほとんどのプログラムは比較的小さく、単目的です。さらに、それらの一部は特にエレガントであるとは考えていません(たとえば、WYASの膨大なルックアップテーブル)。

さまざまなソースからのデータの取得、クリーニング、さまざまな方法での処理、ユーザーインターフェイスでの表示、永続化、ネットワーク経由の通信など、より多くの可動部分を備えたより大きなプログラムを作成したいと考えています。このようなコードを読みやすく、保守しやすく、変化する要件に適応できるようにする最適な構造はありますか?

大規模なオブジェクト指向の命令型プログラムに関するこれらの質問を扱った非常に多くの文献があります。 MVC、デザ​​インパターンなどのアイデアは、懸念の分離やOOスタイルの再利用性などの幅広い目標を実現するための適切な処方箋です。さらに、新しい命令型言語は、「成長に合わせて設計する」スタイルのリファクタリングに適しています。これは、私の初心者の意見では、Haskellはあまり適していないようです。

Haskellの同等の文献はありますか?この目的のために最もよく採用されている関数型プログラミング(モナド、矢印、適用など)で、エキゾチックな制御構造の動物園はどのように利用できますか?どのようなベストプラクティスをお勧めしますか?

ありがとう!

編集(これはドン・スチュワートの答えのフォローアップです):

@donsが述べた:「モナドは、主要な建築デザインをタイプでキャプチャします。」

私の質問は、純粋な関数型言語の主要なアーキテクチャ設計についてどう考えるべきかということだと思います。

いくつかのデータストリームといくつかの処理ステップの例を考えてみましょう。データストリームのモジュールパーサーを一連のデータ構造に書き込むことができ、各処理ステップを純粋な関数として実装できます。 1つのデータに必要な処理手順は、その値と他のデータによって異なります。手順の一部には、GUIの更新やデータベースクエリなどの副作用が続く必要があります。

データと解析ステップを素敵な方法で結びつける「正しい」方法は何ですか?さまざまなデータ型に対して正しいことを行う大きな関数を作成できます。または、モナドを使用して、これまでに処理されたものを追跡し、各処理ステップで次に必要なものをモナド状態から取得することができます。または、大きく分けてプログラムを作成してメッセージを送信することもできます(このオプションはあまり好きではありません)。

彼がリンクしたスライドには、「必要なもの」の箇条書き「デザインを型/関数/クラス/モナドにマッピングするイディオム」があります。イディオムは何ですか? :)

566
Dan

Haskellでの大規模プロジェクトのエンジニアリング および XMonadの設計と実装 でこれについて少しお話します。大規模なエンジニアリングとは、複雑さを管理することです。 Haskellの複雑さを管理するための主要なコード構造化メカニズムは次のとおりです。

タイプシステム

  • 型システムを使用して抽象化を実施し、相互作用を簡素化します。
  • 型を介して主要な不変条件を強制する
    • (たとえば、特定の値が一部のスコープをエスケープできないこと)
    • 特定のコードがIOを実行せず、ディスクに触れない
  • 安全性の強化:チェック済み例外(多分/いずれか)、概念の混合(Word、Int、Address)を避ける
  • 優れたデータ構造(ジッパーなど)を使用すると、一部のクラスのテストが不要になる可能性があります。静的に範囲外エラー。

プロファイラー

  • プログラムのヒープおよび時間プロファイルの客観的な証拠を提供します。
  • 特に、ヒーププロファイリングは、不必要なメモリ使用を確実に防ぐ最良の方法です。

純度

  • 状態を削除して、複雑さを劇的に減らします。純粋に機能的なコードは、構成的であるためスケーリングします。必要なのは、コードの使用方法を決定するタイプだけです。プログラムの他の部分を変更しても、不思議なことに壊れることはありません。
  • 多くの「モデル/ビュー/コントローラー」スタイルのプログラミングを使用します。外部データをできるだけ早く純粋に機能するデータ構造に解析し、それらの構造を操作し、すべての作業が完了したら、レンダリング/フラッシュ/シリアル化します。ほとんどのコードを純粋に保ちます

テスト

  • QuickCheck + Haskell Code Coverage。タイプでチェックできないものをテストしていることを確認します。
  • GHC + RTSは、GCの実行に多くの時間を費やしているかどうかを確認するのに最適です。
  • QuickCheckは、モジュールのクリーンで直交するAPIを識別するのにも役立ちます。コードのプロパティを記述するのが難しい場合は、おそらくあまりにも複雑です。コードをテストでき、適切に構成できるプロパティのクリーンなセットができるまで、リファクタリングを続けます。次に、コードもおそらく適切に設計されています。

構造化のためのモナド

  • モナドは、主要なアーキテクチャ設計をタイプでキャプチャします(このコードはハードウェアにアクセスし、このコードはシングルユーザーセッションなどです)
  • 例えば。 xmonadのXモナドは、システムのどのコンポーネントにどの状態が見えるかの設計を正確にキャプチャします。

型クラスと存在型

  • 型クラスを使用して抽象化を提供します:ポリモーフィックインターフェイスの背後に実装を隠します。

並行性と並列性

  • プログラムにparを忍び込ませて、簡単で構成可能な並列処理で競合他社に勝ちます。

リファクタリング

  • Haskella lotでリファクタリングできます。型を賢く使用している場合、型は大規模な変更が安全であることを保証します。これは、コードベースのスケーリングに役立ちます。リファクタリングが完了するまでタイプエラーを引き起こすことを確認してください。

FFIを賢く使用する

  • FFIを使用すると、外部コードを簡単に操作できますが、外部コードは危険です。
  • 返されるデータの形状についての仮定には非常に注意してください。

メタプログラミング

  • テンプレートHaskellまたはジェネリックのビットは、定型句を削除できます。

包装と流通

  • Cabalを使用します。独自のビルドシステムを使用しないでください。 (編集:実際には、おそらく Stack を今すぐ使いたいと思うでしょう。)。
  • 優れたAPIドキュメントにHaddockを使用する
  • graphmod などのツールは、モジュール構造を表示できます。
  • 可能な限り、Haskell Platformバージョンのライブラリとツールに依存してください。これは安定したベースです。 (編集:繰り返しますが、最近では安定したベースを立ち上げて実行するために Stack を使用したいと思うでしょう。)

警告

  • -Wallを使用して、コードの臭いをきれいに保ちます。また、Agda、Isabelle、またはCatchを見て、より確実に確認することもできます。リントのようなチェックについては、改善を提案する素晴らしい hlint を参照してください。

これらすべてのツールを使用すると、複雑さを解消し、コンポーネント間の相互作用を可能な限り削除できます。理想的には、純粋なコードの非常に大きなベースがあります。これは、構成が非常に簡単なため、保守が非常に簡単です。それは常に可能というわけではありませんが、目標とする価値があります。

一般に:decomposeシステムの論理ユニットを可能な限り最小の参照透過コンポーネントに分解し、モジュールに実装します。コンポーネントのセット(または内部コンポーネント)のグローバルまたはローカル環境は、モナドにマッピングされる場合があります。代数データ型を使用して、コアデータ構造を記述します。これらの定義を広く共有します。

519
Don Stewart

ドンは上記のほとんどの詳細を説明しましたが、Haskellのシステムデーモンのような非常にきめの細かいステートフルプログラムを実行して得た2セントです。

  1. 最終的に、あなたはモナド変換スタックに住んでいます。一番下はIOです。その上で、すべての主要なモジュール(ファイル内のモジュールの意味ではなく、抽象的な意味で)は、そのスタック内のレイヤーに必要な状態をマップします。したがって、モジュールにデータベース接続コードが隠されている場合、MonadReader Connection m => ...-> m ...というタイプを介してすべてを書くと、データベース関数は常に他の関数から関数なしで接続を取得できますモジュールはその存在に注意する必要があります。データベース接続、別の構成、3番目のさまざまなセマフォと並列処理と同期の解決のためのmvar、別のログファイルハンドルなどを保持する1つの層になることがあります。

  2. エラー処理を理解しますfirst。大規模なシステムでのHaskellの現時点での最大の弱点は、Maybeのようなお粗末な方法を含むエラー処理方法が多すぎることです(これは間違っていることに関する情報を返すことができないため間違っています。単に欠損値を意味します)。最初にそれを行う方法を理解し、ライブラリやその他のコードが使用するさまざまなエラー処理メカニズムから最終的なアダプターにアダプターをセットアップします。これは後で悲しみの世界を救うでしょう。

補遺(コメントから抜粋; Liiliminalisht )のおかげで—
大きなプログラムをスタックのモナドにスライスするさまざまな方法についての詳細な議論:

Ben Kolera は、このトピックの実用的な入門書です。 Brian Hurt は、カスタムモナドへのliftingモナドアクションの問題の解決策について説明します。 George Wilson は、mtlを使用して、カスタムモナドの種類ではなく、必要なタイプクラスを実装するモナドで動作するコードを記述する方法を示します。 Carlo Hamalainen は、ジョージの講演を要約した短い有用なメモを書いています。

118
user349653

Haskellで大規模なプログラムを設計することは、他の言語で行うこととそれほど変わりません。大規模なプログラミングとは、問題を管理可能な部分に分割し、それらをどのように組み合わせるかです。実装言語はそれほど重要ではありません。

とは言っても、大規模なデザインでは、型システムを活用して、正しい方法でしかピースを合わせられないようにすることをお勧めします。これには、同じタイプを持つように見えるものを異なるものにするために、newtypeまたはファントムタイプが含まれる場合があります。

進むにつれてコードをリファクタリングすることになると、純粋さは大きな恩恵になるので、可能な限り純粋なコードを維持するようにしてください。純粋なコードは、プログラムの他の部分との隠れた相互作用がないため、リファクタリングが簡単です。

43
augustss

この本 で初めて構造化された関数型プログラミングを学びました。それはまさにあなたが探しているものではないかもしれませんが、関数型プログラミングの初心者にとって、これは関数型プログラムを構築することを学ぶための最良の最初のステップの1つかもしれません-規模に依存しません。すべての抽象化レベルで、設計には常に明確に配置された構造が必要です。

関数型プログラミングのクラフト

The Craft of Functional Programming

http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/

16
comonad

現在、「Functional Design and Architecture」というタイトルの本を書いています。純粋な機能アプローチを使用して大きなアプリケーションを構築する方法の完全なセットを提供します。宇宙船をゼロから制御するためのSCADAのようなアプリケーション「アンドロメダ」を構築しながら、多くの機能的なパターンとアイデアを説明します。私の第一言語はHaskellです。本は以下をカバーしています:

  • ダイアグラムを使用したアーキテクチャモデリングへのアプローチ。
  • 要件分析;
  • 組み込みDSLドメインモデリング。
  • 外部DSLの設計と実装。
  • 効果を持つサブシステムとしてのモナド。
  • 機能的インターフェースとしての無料モナド。
  • 矢印付きのeDSL。
  • 無料のモナドeDSLを使用した制御の反転。
  • ソフトウェアトランザクションメモリ。
  • レンズ;
  • 状態、リーダー、ライター、RWS、STモナド。
  • 不純な状態:IORef、MVar、STM;
  • マルチスレッドと同時ドメインモデリング。
  • GUI;
  • UML、SOLID、GRASPなどの主流の手法とアプローチの適用可能性。
  • 不純なサブシステムとの相互作用。

この本のコード here 、および 'Andromeda' プロジェクトコードに精通しているかもしれません。

この本は2017年末に完成する予定です。それまでは、私の記事「関数型プログラミングの設計とアーキテクチャ」(Rus) here をお読みください。

UPDATE

本をオンラインで共有しました(最初の5章)。 Redditに投稿 を参照してください

11
graninas

Gabrielのブログ投稿 スケーラブルなプログラムアーキテクチャ は言及する価値があるかもしれません。

Haskellのデザインパターンは、主流のデザインパターンと1つの重要な点で異なります。

  • 従来のアーキテクチャ:タイプAのいくつかのコンポーネントを組み合わせて、タイプBの「ネットワーク」または「トポロジ」を生成します

  • Haskellアーキテクチャ:タイプAのいくつかのコンポーネントを組み合わせて、置換部分と特性が区別できない同じタイプAの新しいコンポーネントを生成します

見たところエレガントなアーキテクチャは、このニースの同質性を示すライブラリからボトムアップで抜け落ちがちであることがよくあります。 Haskellでは、これは特に明らかです。伝統的に「トップダウンアーキテクチャ」と見なされるパターンは、 mvcNetwire 、および Cloud Haskellのようなライブラリでキャプチャされる傾向があります 。つまり、この答えがこのスレッドの他のいずれかを置き換える試みとして解釈されることはないことを願っています。ドメインの専門家がライブラリ内で構造的な選択を抽象化することができます。私の意見では、大規模システムを構築する際の本当の難しさは、これらのライブラリーを、そのアーキテクチャーの「良さ」とあなたの実際的な懸念のすべてで評価することです。

liminalisht がコメントの中で言及しているように、 The category design pattern は、同様の方法でトピックに関するGabrielによる別の投稿です。

7
Rehno Lindeque

アレハンドロセラーノによる論文 "Takeing Software Architecture Using Haskell"(pdf) は、大規模な思考に役立つHaskellの構造。

5
haroldcarr

おそらく、あなたは最初に戻って、最初に問題の説明を設計に変換する方法を考える必要があります。 Haskellは非常にレベルが高いため、問題の説明をデータ構造の形で、アクションをプロシージャとして、純粋な変換を関数としてキャプチャできます。次に、デザインがあります。このコードをコンパイルすると、コードのフィールド、インスタンス、モナド変換子の欠落に関する具体的なエラーが見つかると、開発が開始されます。たとえば、IO内で特定の状態モナドを必要とするライブラリからデータベースアクセスを実行する場合手順。そして出来事、プログラムがあります。コンパイラはメンタルスケッチをフィードし、設計と開発に一貫性を与えます。

このように、最初からHaskellの助けを借りることができ、コーディングは自然です。あなたが念頭に置いていることが具体的な普通の問題であるなら、私は何か「機能的」または「純粋な」または十分に一般的なことをする気にしないでしょう。過剰なエンジニアリングはITで最も危険なことだと思います。問題が関連する一連の問題を抽象化するライブラリを作成することである場合、状況は異なります。

3
agocorona