1. ホーム
  2. haskell

矢印とは何ですか、どのように使えばいいですか?

2023-07-26 14:53:32

質問

の意味を知ろうとしました。 矢印 の意味を知ろうとしましたが、よくわかりませんでした。

私はWikibooksのチュートリアルを使いました。ウィキブックの問題は、主に、そのトピックをすでに理解している人向けに書かれているように思えることだと思います。

誰か矢印が何であるか、そしてどのようにそれを使うことができるかを説明してもらえますか?

どのように解決するのですか?

チュートリアルは知りませんが、具体的な例を見ればarrowsを理解するのは一番簡単だと思います。 私が矢印の使い方を学ぶときに一番困ったのは、どのチュートリアルや例も、実際にどのようにして を使う を使う方法を示していないことです。 そこで、このことを念頭に置いて、私のミニチュートリアルを紹介します。 関数とユーザー定義の矢印タイプという2つの異なる矢印を調べます。 MyArr .

-- type representing a computation
data MyArr b c = MyArr (b -> (c,MyArr b c))

1) アローは、指定された型の入力から指定された型の出力への計算である。 arrow型クラスは3つの型引数を取る: arrow型、入力型、出力型である。 arrowインスタンスのインスタンスヘッドを見てみると、以下のようになる。

instance Arrow (->) b c where
instance Arrow MyArr b c where

アロー(どちらかというと (->) または MyArr ) は計算を抽象化したものです。

関数に対して b -> c , b は入力で c が出力です。

の場合は MyArr b c , b は入力で c が出力である。

2) 実際に矢印の計算を実行するには、矢印の種類に応じた関数を使用します。 関数の場合は、単に引数に関数を適用するだけです。 他のアローの場合は、別の関数が必要です(ちょうど runIdentity , runState など)。

-- run a function arrow
runF :: (b -> c) -> b -> c
runF = id

-- run a MyArr arrow, discarding the remaining computation
runMyArr :: MyArr b c -> b -> c
runMyArr (MyArr step) = fst . step

3) 矢印は入力のリストを処理するために頻繁に使用されます。 関数の場合、これらは並行して行うことができますが、いくつかの矢印の場合、任意のステップでの出力は以前の入力に依存します(例えば、入力の実行合計を維持する)。

-- run a function arrow over multiple inputs
runFList :: (b -> c) -> [b] -> [c]
runFList f = map f

-- run a MyArr over multiple inputs.
-- Each step of the computation gives the next step to use
runMyArrList :: MyArr b c -> [b] -> [c]
runMyArrList _ [] = []
runMyArrList (MyArr step) (b:bs) = let (this, step') = step b
                                   in this : runMyArrList step' bs

これがArrowsが有用である理由の一つです。 アローは、プログラマに状態を見せることなく、暗黙的に状態を利用することができる計算モデルを提供します。 プログラマーはアロー化された計算を使用し、それらを組み合わせて洗練されたシステムを作ることができるのです。

ここに、受け取った入力の数をカウントするMyArrがあります。

-- count the number of inputs received:
count :: MyArr b Int
count = count' 0
  where
    count' n = MyArr (\_ -> (n+1, count' (n+1)))

ここで、関数 runMyArrList count は長さnのリストを入力とし、1〜nのIntsのリストを返します。

私たちはまだquot;arrow"関数、つまりArrowクラスのメソッドやそれを使って書かれた関数を一切使っていないことに注意してください。

4) 上記のコードのほとんどは、各Arrowインスタンスに固有のものです[1]。 すべての Control.Arrow (そして Control.Category ) は、矢印を合成して新しい矢印を作ることです。 もし、Categoryが別のクラスではなく、Arrowの一部であると仮定するならば。

-- combine two arrows in sequence
>>> :: Arrow a => a b c -> a c d -> a b d

-- the function arrow instance
-- >>> :: (b -> c) -> (c -> d) -> (b -> d)
-- this is just flip (.)

-- MyArr instance
-- >>> :: MyArr b c -> MyArr c d -> MyArr b d

>>> 関数は2つの矢印を受け取り、1番目の出力を2番目の入力として使用します。

これは一般的に"fanout"と呼ばれる別の演算子です。

-- &&& applies two arrows to a single input in parallel
&&& :: Arrow a => a b c -> a b c' -> a b (c,c')

-- function instance type
-- &&& :: (b -> c) -> (b -> c') -> (b -> (c,c'))

-- MyArr instance type
-- &&& :: MyArr b c -> MyArr b c' -> MyArr b (c,c')

-- first and second omitted for brevity, see the accepted answer from KennyTM's link
-- for further details.

以降 Control.Arrow は計算を組み合わせる手段を提供するので、ここに一つの例を示します。

-- function that, given an input n, returns "n+1" and "n*2"
calc1 :: Int -> (Int,Int)
calc1 = (+1) &&& (*2)

のような関数を頻繁に見かけるようになりました。 calc1 のような関数が、複雑な折り返しや、ポインタを操作するような関数で有用であることをよく見かけます。

Monad 型クラスは、モナド計算を1つの新しいモナド計算に結合する手段を提供します。 >>= 関数を使って、モナド計算を一つの新しいモナド計算にまとめる手段を提供します。 同様に Arrow クラスは、いくつかの原始的な関数を使って、アロー化された計算を一つの新しいアロー化された計算に結合する手段を提供してくれます ( first , arr そして *** というように >>>id をControl.Categoryから)。 また、モナドと同様、「矢印は何をするのか」という問いにも一概に答えることはできません。 それは矢印に依存します。

残念ながら、arrow のインスタンスを実際に使っている例をあまり知りません。 関数と FRP が最も一般的なアプリケーションのようです。 HXT は、思い浮かぶ唯一の重要な使用法です。

[1] 例外 count . のインスタンスに対して同じことをするカウント関数を書くことは可能です。 ArrowLoop .