矢印の目的は何ですか?


63

私はHaskellを使って関数型プログラミングを学んでいますが、なぜそれらが必要なのかを最初に理解して概念をつかもうとしています。

関数型プログラミング言語における矢印の目標を知りたいです。彼らはどのような問題を解決しますか?http://en.wikibooks.org/wiki/Haskell/Understanding_arrowsおよびhttp://www.cse.chalmers.se/~rjmh/afp-arrows.pdfを確認しました。私が理解しているのは、それらが計算用のグラフを記述するために使用され、より簡単なポイントフリースタイルのコーディングを可能にすることです。

この記事では、一般に、ポイントフリースタイルの方が理解しやすく、書きやすいと想定しています。これは非常に主観的なようです。別の記事(http://en.wikibooks.org/wiki/Haskell/StephensArrowTutorial#Hangman:_Main_program)では、絞首刑執行人のゲームが実装されていますが、この実装が矢印によってどのように自然になるかはわかりません。

コンセプトを説明する論文はたくさんありましたが、その動機については何も見つかりませんでした。

私は何が欠けていますか?

回答:


43

私はパーティーに遅刻していることに気づきましたが、あなたはここで2つの理論的な答えを持っているので、噛むための実用的な代替手段を提供したかったのです。私はこれに相対的なHaskell noobとして来ていますが、彼は現在、私が現在取り組んでいるプロジェクトのためにArrowsの主題を通して強制的に行進しました。

まず、Haskellのほとんどの問題をArrowsに手を伸ばさずに生産的に解決できます。いくつかの有名なHaskellerは、実際にそれらを好まないため、使用していません(詳細についてこちらこちら、およびこちらをご覧ください)。あなたが自分に「ねえ、私はこれらを必要としない」と言っているなら、あなたは本当に正しいかもしれないことを理解してください。

Arrowsについて最初に学んだときに最もイライラさせられたのは、このテーマに関するチュートリアルが回路の類推のために必然的に到達した方法でした。少なくとも砂糖の種類であるArrowコードを見ると、ハードウェア定義言語ほどではありません。入力は右側に並び、出力は左側に並び、それらをすべて正しく配線しないと、単に発火しません。私は自分自身に考えました:本当に?これは私たちが終わった場所ですか?銅線とはんだで構成されるほど完全に高レベルの言語を作成しましたか?

私が判断できた限り、これに対する正しい答えは次のとおりです。実際、はい。Arrowsの現在のキラーユースケースはFRPです(Yampa、ゲーム、音楽、リアクティブシステム全般を考えてください)。FRPが直面している問題は、他のすべての同期メッセージングシステムが直面している問題とほぼ同じです。つまり、関連情報を漏らしたり漏れを引き起こしたりすることなく、入力の連続ストリームを出力の連続ストリームに配線する方法です。ストリームをリストとしてモデル化できます-最近のいくつかのFRPシステムはこのアプローチを使用していますが、入力が多い場合、リストを管理するのはほとんど不可能になります。あなたは現在から自分を隔離する必要があります。

FRPシステムでArrowsが許可しているのは、関数をネットワークに構成すると同時に、それらの関数によって渡される基になる値への参照を完全に抽象化することです。FPを初めて使用する場合、これは最初は混乱する可能性があり、次にその意味を吸収したときに驚くべきことがあります。関数を抽象化できるという考えと[(*), (+), (-)]、typeのようにリストを理解する方法を吸収したのはごく最近のことです[(a -> a -> a)]。Arrowsを使用すると、抽象化を1レイヤーだけ進めることができます。

抽象化するこの追加機能には、それ自体の危険が伴います。1つには、GHCを、型の仮定を何にすべきかわからないコーナーケースに追い込む可能性があります。型レベルで考える準備をする必要があります。これは、種類やRankNTypesなどのトピックについて学ぶ絶好の機会です。

私が「愚かなアロースタント」と呼ぶものの例もいくつかあります。コーダーは、タプルできちんとしたトリックを披露したいという理由で、アローコンビネーターに手を伸ばします。(狂気への私自身のささいな貢献はここにあります。)あなたが野生でそれに出くわすとき、そのようなホットドッギングを無視してください。

注:上記のように、私は比較的初心者です。上記の誤解を公布した場合は、お気軽に修正してください。


2
まだ何も受け入れていなかったことを嬉しく思います。この回答を提供していただきありがとうございます。ユーザーに焦点を当てています。例は素晴らしいです。主観的な部分は明確に定義され、バランスが取れています。この質問に賛成した人々が戻ってきて、これを見ることを願っています。
サイモンベルゴット

矢印は間違いなくあなたのリンクされたソリューションにとって間違ったツールですが、私はそれremoveAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnAをもっと簡潔にそして明確に書くことができることに言及する必要があると感じていremoveAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++))ます。
cemper93

30

これは一種の「ソフト」な答えであり、実際にこのような方法で言及されている参照があるかどうかはわかりませんが、これは矢印を考えるようになった方法です。

矢印型A b cは基本的に関数ですb -> cが、モナド値M aが単純な旧型よりも多くの構造を持つのと同じように、より多くの構造を持ちますa

さて、その余分な構造がどうなるかは、あなたが話している特定の矢印インスタンスに依存します。モナドIO aと同様に、Maybe aそれぞれが異なる追加構造を持っています。

あなたがモナドで取得する事がから行くことができないことであるM aa。これは制限のように思えるかもしれませんが、実際には機能です。型システムは、モナド値を単純な古い値に変えることからあなたを保護しています。>>=特定のモナドインスタンスのプリミティブ操作を介してモナドに参加することでのみ、値を利用できます。

同様に、あなたが得るものA b cは、新しいbを消費するcを生成する「関数」を構築できないことです。矢印は、さまざまな矢印コンビネーターに参加するか、特定の矢印インスタンスのプリミティブ操作を使用するbことで、cを消費して作成することを防ぎます。

たとえば、Yampaの信号関数は大まかに(Time -> a) -> (Time -> b)ですが、さらに、特定の因果律の制約に従うt必要があります。時間の出力は、入力信号の過去の値によって決定されます。未来を見ることはできません。したがって、それらは(Time -> a) -> (Time -> b)、でプログラミングする代わりに、でプログラミングし、SF a bプリミティブからシグナル関数を構築します。とても以来のことが起こるSF a b一般的な構造は「矢印」と呼ばれるものであるように、関数のように多くのことを振る舞います。


「矢印は、さまざまな矢印コンビネーターに参加するか、特定の矢印インスタンスのプリミティブ操作を使用することによりbcを消費して作成することを防ぎます。」この古代の答えに謝罪して:この文は線形型、つまりリソー​​スを複製したり消滅させたりすることはできないと考えさせられました。接続があると思いますか?
glaebhoerl

14

MonadsやFunctorsのようなArrowsは、プログラマーが関数のエキゾチックな構成を行えるようにするものだと思います。

MonadsまたはArrows(およびFunctors)がなければ、関数型言語での関数の構成は、ある関数を別の関数の結果に適用することに制限されます。モナドとファンクターを使用して、2つの関数を定義し、それらの関数が特定のモナドのコンテキストで相互に、および渡されるデータと相互作用する方法を指定する再利用可能なコードを個別に作成できます。このコードは、Monadのバインドコード内に配置されます。したがって、モナドは1つのビューであり、再利用可能なバインドコードのコンテナにすぎません。関数は、あるモナドのコンテキスト内で別のモナドとは異なる構成をします。

簡単な例は、Maybeモナドです。Maybeモナド内で関数Aが関数Bで構成され、BがNothingを生成する場合、バインドコードはバインド関数にコードがあり、バインドコードは2つの関数は、Bから出力されるNothing値にAを適用することなく、Nothingを出力します。モナドがない場合、プログラマはNothing入力をテストするためにコードをAに書き込む必要があります。

モナドは、プログラマーが各関数が必要とするパラメーターをソースコードに明示的に入力する必要がないことも意味します-バインド関数はパラメーターの受け渡しを処理します。したがって、モナドを使用すると、ソースコードは、関数AがパラメーターCおよびDを使用して関数Bを「呼び出す」のではなく、関数名の静的チェーンのように見えるようになります。移動機械-命令型よりも機能的。

矢印は、関数をバインド関数と一緒に接続し、再利用可能な機能を提供し、パラメーターを非表示にします。ただし、Arrowはそれ自体を接続して構成することができ、オプションで実行時に他のArrowにデータをルーティングできます。これで、データに対して「異なる処理を行う」2つの矢印のパスにデータを適用し、結果を再構築できます。または、データの値に応じて、データを渡す矢印のブランチを選択できます。結果として得られるコードは、スイッチ、遅延、統合などを備えた電子回路にさらに似ています。プログラムは非常に静的に見えるため、進行中のデータをあまり操作できないはずです。考えるパラメーターはますます少なくなり、パラメーターが取る値と受け取らない値を考える必要が少なくなります。

Arrowizedプログラムの作成には、スプリッター、スイッチ、ディレイ、インテグレーターなどの既製の矢印を選択し、それらのArrowに関数をリフティングし、Arrowを接続して大きなArrowを形成することがほとんどです。Arrowized Functional Reactive Programmingでは、矢印はループを形成し、世界からの入力がプログラムの最後の反復からの出力と組み合わされて、出力が実世界の入力に反応します。

実世界の価値の1つは時間です。Yampaでは、Signal Function Arrowは目に見えないようにコンピュータープログラムを介して時間パラメーターをスレッド化します-時間値にアクセスすることはありませんが、積分矢印をプログラムに接続すると、時間の経過とともに統合された値を出力し、それを使用して渡すことができます他の矢印。


しかし、これはアプリカティブファンクタのように聞こえます(特定のコンテキストで、既存の関数をラップ型に再利用するためのヘルパー関数を提供する関数のラッパー)。私は間違いなく理解するためにもっと読む必要がありますが、多分あなたは私が行方不明になっているものを指摘することによって助けることができる
-Belun

3

他の答えへの追加:個人的には、そのような概念が(数学的に)何であるか、そしてそれが私が知っている他の概念にどのように関係するかを理解するのに役立ちます。

矢印の場合、私は次の論文が役立つことを発見しました-モナド、適用ファンクター(イディオム)、および矢印を比較します:イディオムは忘れられ、矢印は細かく、モナドは無差別です、サム・リンドリー、フィリップ・ワドラー、ジェレミー・ヤロップ。

また、このリンクについて言及しいる誰もこのテーマに関するアイデアや文献を提供していないと思います。

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