26

したがって、質問を 4 つの部分に分けますが、最初にいくつかの背景を説明します。

Monads には比較的慣れていますが、Arrows にはあまり慣れていません。私が彼らに抱えている主な問題は、それらが何に役立つのか分からないことだと思います。正式に正しいかどうかは別として、モナドは計算から副作用を導入できるツールであると理解しています。プログラムのフラグメントを純粋な値から他のアクションでボックス化された値に一般化するため。矢について学ぶための散弾銃の「すべての論文を読む」アプローチから、私は2つの相反する視点に出くわしました。

A. アローはモナドよりも強力です/モナドの一般化です。haskell wiki は、「モナドができることはすべて、それ以上のことを行うことができます。静的コンポーネントを持つモナドにほぼ匹敵します。」で始まります。

B. アローはモナドのサブセットである ArrowApply でモナドを定義できる

  1. 視点Aに真実はありますか?
  2. 矢印にはどのような機能がありませんか。その違いは合成に関係していると読んだことがあります。
  3. アプリは正確に何をしますか? そのタイプには (->) さえありません
  4. モナドに適用可能な矢印を使いたいと思うのはなぜでしょうか?
4

3 に答える 3

42

複数の解釈が可能なステートメントは次のように警告します。

「A は B よりも強力である」 ... 「C は D の一般化である」 ... 「E は F ができることすべてを行うことができ、さらにそれ以上のことを行うことができる」 ... 「G は H のサブセットである」 ...

まず、パワフルなどの意味を把握する必要があります。GripHandleグリップ ハンドルを持つもの用のクラスScrewdriverと、ドライバー用の別のクラスがあるとします。どちらがより強力ですか?

  • 明らかに、グリップハンドルを持っているだけでは、ドライバーほど役に立ちません。グリップハンドル自体はあまり役に立たないので、グリップハンドルよりもドライバーの方が多くのことができるので、ドライバーの方が強力です.
  • 明らかに、ドライバーだけでなく、ドリル、ナイフ、フォークなど、あらゆる種類のものにグリップハンドルが付いているため、グリップハンドルはより強力で柔軟です.
  • 明らかに、ドライバーを持っていれば、握るだけでなく回すこともできます。また、握るだけでなく回すことができるため、ドライバーはグリップハンドルよりもはるかに強力で柔軟になります。

OK、それはばかげた議論ですが、「_____ でもっと多くのことができる」というフレーズがいかに曖昧であるかについての良い点を提起します.

インターフェースだけに固執する場合ドライバーはハンドルよりも便利ですが、インターフェースだけでなくより多くの機能を使用する場合グリップハンドルを備えたすべてのものは、すべてのドライバーよりも便利です.

階層のしくみ

AB
=Aのインターフェイスのみの機能は のサブセットです=インスタンス (単独) でよりB多くのことができます = すべての s のクラスはすべての s のクラスのサブセットです= s よりもsが多い=クラス でもっとできることB
BAABA

より一般的
= より多くの可能性がある
= より広く使用
できる = 舞台裏で余分なことができる=
インターフェースで指定される機能が少ない=インターフェースを介し
て できることが少ない

アローとモナドの間の階層は何ですか?

  • Arrowよりも一般的ですMonad
  • ArrowApplyとまったく同じくらい一般的Monadです。

これらの 2 つのステートメントは、Petr Pudlák のコメントにリンクされている論文で完全に証明されています: Idioms are oblivious, arrows are meticulous, monads are promiscuous

A と B のアサーション

  • 「矢印は、モナドができることすべて、さらにはそれ以上のことを行うことができます。」
    これがマーケティングです。それは本当ですが、それを真実にするためには、意味論的に少し飛び回る必要があります。単独のArrowApplyインスタンスを使用すると、単独のインスタンスで実行できるすべてのことを実行できますMonadArrowApplyよりも を使用することはできませんMonad。あるもので、より多くのことができArrowsます。「モナドができることはすべて」という主張はおそらく を指し、ArrowApply「その他」という主張はおそらくArrow! Monad のマーケティング委員会は、インターフェイスの表現力が向上したため、「Monads を使用すると、Arrows でできることはすべて、さらに多くのことを実行できます」と言うことができます。これらのステートメントはあいまいであり、そのため正式な意味はほとんどありません。
  • 「それらは、静的コンポーネントを持つモナドにほぼ匹敵します。」
    厳密に言えば、いいえ、これArrowは で直接実行できないことでありMonad、Arrow インターフェースに関する数学的事実ではありません。これは、Monad が値 in を持つボックスであるというアナロジーと同様に、Arrow で何ができるかを把握するのに役立つ方法です。すべてのモナドが値 in を持つボックスとして容易に解釈できるわけではありません。しかし、初期段階では少し役立つかもしれません。
  • 「矢印はモナドのサブセットです」
    これはおそらく誤解を招くものです。Arrow のインターフェースのみの機能が Monads のインターフェースのみの機能のサブセットであること事実ですが、すべての Monads のクラスはすべての Arrow のクラスのサブセットであると言ったほうが公平Arrowです。
  • 「ArrowApply でモナドを定義できます」
    はい。後述しますが、Monad を使用すると、ArrowApply を定義できます。

あなたの4つの質問

  1. 視点Aに真実はありますか?
    いくつか。上記を参照。Bにもいくつかの真実があります。どちらもある意味で誤解を招くものです。
  2. 矢印にはどのような機能がありますか。その違いは合成に関係していると読んだことがあります。
    実際、 (より多くのインターフェース提供機能) 以上の>>=ことを行うことができます。>>>これにより、コンテキストを切り替えることができます。これはis 関数であるため、どのモナドを実行するかを決定する前Monad m => a -> m bに入力に対して任意の純粋なコードを実行できますが、is は関数ではなく、 input を調べる前に実行する矢印を決定しています。aArrow m => m a ba

    monadSwitch :: Monad m => m a -> m a -> (Bool -> m a)
    monadSwitch computation1 computation2 test 
          = if test then computation1 else computation2
    

    fromを使用ArrowせずにこれをシミュレートすることはできませんappArrowApply

  3. アプリは正確に何をしますか? タイプには (->) さえありません
    。矢印の出力を矢印として使用できます。タイプを見てみましょう。

    app :: ArrowApply m => m (m b c, b) c
    

    より計算のように感じ、値のように感じるので、 to を使用mすることを好みます。型演算子 (中置型コンストラクター) を使用するのが好きな人もいるので、ama

    app :: ArrowApply (~>) => (b ~> c, b) ~> c
    

    私たちはb ~> c矢と考えます。矢は、bs を取り、何かを行い、cs を与えるものと考えます。したがって、これはapp矢印と値を受け取る矢印であり、最初の矢印がその入力で生成したであろう値を生成できます。

    矢印を使用してプログラミングする場合、を使用->して任意の関数を矢印に変換arr :: Arrow (~>) => (b -> c) -> b ~> cできますが、すべての矢印を関数に変換することはできないため、型シグネチャにはありません。(b ~> c, b) ~> c(b ~> c, b) -> c(b -> c, b) ~> c

    produceArrow :: Arrow (~>) => (b ~> c) -> (any ~> (b ~> c))defined with を実行するだけで、ArrowApply がなくても、1 つの矢印または複数の矢印を生成する矢印を簡単に作成できますproduceArrow a = arr (const a)。難しいのは、その矢をどの矢でも機能させることです。作成した矢を次の矢にする方法は? 決定的に、矢印は関数ではないため、モナド関数>>>でできるように、次の計算としてそれをポップすることはできません(ただ実行してください!)。前の矢印で行ったでしょう。 Monad m => a -> m bid :: m a -> m aapp

    したがって、ArrowApply は、Monad から得られる実行時生成の計算実行可能性を提供します。

  4. モナドに適用可能な矢印を使いたいと思うのはなぜでしょうか?
    ええと、アローまたは Applicative Functor のことですか? Applicative Functor は素晴らしいです。これらは Monad や Arrow (論文を参照) よりも一般的であるため、インターフェース固有の機能は少なくなりますが、より広く適用できます (わかりますか? 適用可能/適用可能なチョートル チョートル 笑 rofl カテゴリ理論のユーモア ハハハハ)。

    Applicative Functor は、純粋な関数の適用に非常によく似た素敵な構文を持っています。次にf <$> ma <*> mb <*> mc実行され、3 つの結果に純関数が適用されます。例えば。ユーザーから 2 つの整数を読み取り、それらを加算します。mambmcf(+) <$> readLn <*> readLn

    Applicative を使用して一般性を得ることができ、Monads を使用してインターフェース機能を得ることができるので、理論的にはそれらは必要ないと主張することもできますが、do 表記に似ているため、矢印の表記法を好む人もいます。実際Arrow、静的コンポーネントを持つパーサーを実装するために使用できるため、コンパイル時の最適化を適用できます。Applicative でもできると思いますが、最初は Arrow でできました。

    Applicative が「あまり強力ではない」ことについての注意:この論文では、は よりも一般的であると
    指摘されていますが、生成された計算を実行できる関数、または生成された関数を昇格できる関数を提供することで、Applicative ファンクターに同じ能力を持たせることができます。計算から計算へ。モナドの 1 つの理論的基礎となる 2 つの関数を定義して取得すると、Haskell で使用されるもう 1 つの理論的基礎を定義して取得するとします。クラスがないのは、それができればモナドを作ることができ、型シグネチャはほとんど同じだからです。再利用しない唯一の理由ApplicativeMonadrun :: Applicative f => f (f b) -> f buse :: Applicative f => f (a -> f b) -> f a -> f bjoin = rununit = (<$>)(>>=) = flip (use.pure)return = unitApplicativeRunArrowApplyMonadタイプが同一ではないということです。~>ArrowApply のインターフェイスに抽象化 (一般化) されますが、関数適用->は Monad で直接使用されます。この違いにより、ArrowApply と Monad は同等であるにもかかわらず、Arrows を使用したプログラミングがモナドでのプログラミングと多くの点で異なっているように感じられます。

  5. < 咳 >なぜ Monad よりも Arrows/ArrowApply を使いたくなるのですか?
    OK、あなたの言いたいことはわかっていましたが、Applicative functor について話したかったので、夢中になって答えるのを忘れていました!

    機能上の理由: はい、モナドにできないものがある場合は、Monad 上の Arrow を使用したいと思うでしょう。そもそもアローをもたらした動機付けの例はパーサーでした。アローを使用して、コンビネーターで静的分析を行うパーサー ライブラリを記述し、より効率的なパーサーを作成できます。以前の Monadic パーサーはこれを行うことができません。パーサーを関数として表し、静的に記録せずに入力に対して任意のことを実行できるため、コンパイル時/結合時にそれらを分析できないためです。

    構文上の理由: いいえ、個人的にはアロー ベースのパーサーを使用したくありません。なぜなら、アローproc/do記法が嫌いだからです。モナド記法よりも悪いと思います。パーサーの私の好みの表記法は Applicative です。Arrow が行う効率的な静的解析を行う Applicative パーサー ライブラリを作成できるかもしれませんが、私がよく使用するパーサー ライブラリはそうではないことを率直に認めます。モナディックインターフェースを提供します。

    • モナド:

          parseTerm = do
               x <- parseSubterm
               o <- parseOperator
               y <- parseSubterm
               return $ Term x o y
      
    • 矢印:

          parseTerm = proc _ -> do
               x <- parseSubterm -< ()
               o <- parseOperator -< ()
               y <- parseSubterm -< ()
               returnA -< Term x o y
      
    • 適用可能:

          parseTerm = Term <$> parseSubterm <*> parseOperator <*> parseSubterm
      

      $これは、数回使用した関数アプリケーションのように見えます。うーん。きちんとした。クリア。低構文。命令型プログラミング言語よりも Haskell を好む理由を思い出しました。

ArrowApplyのアプリがモナドを作るのはなぜですか?

Control.Arrowモジュールの ArrowApply セクションに Monad インスタンスがあります。考えを明確にするために(~>)代わりに編集します。(とにかく Functor なしで Monad を定義するのはばかげているので、a私は残しました。定義する必要があります。):Functorfmap f xs = xs >>= return . f

newtype ArrowMonad (~>) b = ArrowMonad (() ~> b)

instance Arrow (~>) => Functor (ArrowMonad (~>)) where
    fmap f (ArrowMonad m) = ArrowMonad $ m >>> arr f

instance ArrowApply (~>) => Monad (ArrowMonad (~>)) where
    return x = ArrowMonad (arr (\_ -> x))
    ArrowMonad m >>= f = ArrowMonad $
        m >>> arr (\x -> let ArrowMonad h = f x in (h, ())) >>> app

それは何をしますか?まず、型シノニムArrowMonadnewtype代わりに、あらゆる種類の厄介な型システムの問題なしでインスタンスを作成できるようにしますが、コンパイル可能性よりも概念を明確にするために、それを無視して、type ArrowMonad (~>) b = () ~> b

instance Arrow (~>) => Functor (() ~>) where
    fmap f m = m >>> arr f

(コンパイル不能な型演算子セクション(()~>)を型コンストラクタとして使用)

instance ArrowApply (~>) => Monad (() ~>) where
 -- return :: b -> (() ~> b)
    return x = arr (\_ -> x)
 -- (>>=) ::   ()~>a   ->    (a  ->  ()~>b )   ->   ()~>b 
    m >>= f = 
        m >>> arr (\x ->  (f x, ()) ) >>> app

わかりました、何が起こっているのかが少し明確になりました。アローとモナドの間の対応は と の間にありますが、モナド クラスは宣言に を含まないことに注意しMonad m => b -> m cてくださいArrow (~>) => b ~> c。そのため、ゼロ入力で物事を開始し、 type の何かをレプリケートするためにb、ダミーの値を提供する必要があり() ます。() ~> bm b

  • 関数を出力に適用する場所と同等なのはfmap、出力を生成してから、関数を矢印形式で実行することです。fmap f m = m >>> arr f
  • return に相当するもの (指定された値を生成するだけですx) は、関数const xをアロー形式で実行することreturn x = arr (\_ -> x)です。
  • >>=計算を実行し、次に実行する計算を計算できる関数への入力として出力を使用するbind と同等のものfは次のとおりです。最初m >>>に最初の計算を実行しm、次にarr (\x -> (f x, ....出力で関数を適用し、fその矢印を次のように使用します与えられた入力に通常通りapp作用する出力された矢印であるかのように振る舞う入力。()きちんとした!
于 2013-07-16T10:06:39.133 に答える
10

視点 A は少し奇妙です。一般的に言えば、抽象化は他の抽象化よりも強力で一般的ではありません。二人は対立している。「より多くの力」を持つということは、あなたが扱っているものの構造についてより多くのことを知ることを意味し、それはより多くの制限を意味します。極端な場合、作業しているタイプが正確にわかります。これは非常に強力です。任意の有効な関数を適用できます。一方で、これは一般的でもありません。この仮定で書かれたコードは、その型にのみ適用されます! 反対に、自分の型について何も知ることができません(たとえば、型変数を持っているa)。これは非常に一般的で、すべてに適用されます。タイプしますが、何もするのに十分な情報がないため、まったく強力ではありません!

Functor実際のコードに根ざした例は、との違いApplicativeです。ここでは、Functorはより一般的です。厳密には、すべての型も aですが、その逆はないため、 Functors よりも多くの型が s です。ただし、より多くの構造を運ぶため、厳密にはより強力です。では、引数が 1 つの関数のみを型にマップできます。を使用すると、任意の数の引数の関数をマップできます。繰り返しますが、一方はより一般的で、もう一方はより強力です。ApplicativeApplicativeFunctorApplicativeFunctorApplicative

それで、それはどれですか?アローはモナドよりも強力ですか、それともより一般的ですか? これは、ファンクタ、アプリケーション ファンクタ、モナドを比較するよりも難しい問題です。なぜなら、矢印は非常に異なる獣だからです。それらには別の種類さえあります: モナドなどには種類* -> *があり、矢印には種類があり* -> * -> *ます。幸いなことに、アローをアプリケーション ファンクター/モナドと識別できることが判明したため、実際にこの質問に有意義に答えることができます。アローはモナドよりも一般的であり、その結果、強力ではありません。モナドが与えられた場合、それから矢印を作成できますが、すべての矢印に対してモナドを作成することはできません。

基本的な考え方は次のとおりです。

instance Monad m => Category (a -> m b) where
  id = return
  (f . g) x = g x >>= f

instance Monad m => Arrow (a -> m b) where
  arr f = return . f
  first f (x, y) = f x >>= \ x' -> return (x', y)

ただし、 の矢印インスタンスがあるため、実際のコードa -> bにラップa -> m bする必要があります。newtypeこれnewtypeKlesli( Klesli カテゴリのため) と呼ばれます。

ただし、その逆はできません。任意の から a を取得するための構造はありMonadませ Arrow。これはArrow、モナドができるのに対し、計算はそれを流れる値に基づいてその構造を変更できないために発生します。これを回避する唯一の方法は、追加のプリミティブ関数を使用して矢印の抽象化に力を加えることです。これはまさに何をするかArrowApplyです。

矢印の>>>演算子は.関数の一般化であるため、同じ一般的な制限があります。>>=一方、 は関数適用の一般化に似ています。タイプに注意してください。 の場合>>>、両側が矢印です。の>>=場合、最初の引数は値 ( m a) で、2 番目の引数は関数です。さらに、 の結果は、 の結果が値>>>である別の矢印>>=です。アローには>>>に相当する概念しかないため>>=、一般にアローを引数に「適用」することはできません。アロー パイプラインを構築することしかできません。実際の適用/実行機能は、特定の矢印に固有である必要があります。一方、モナドは次の観点から定義されます。>>=したがって、デフォルトでいくつかのアプリケーションの概念が付属しています。

ArrowApplyappアプリケーションの一般的な概念である で矢印を拡張するだけです。通常の関数の適用を想像してみましょう:

apply :: (b -> c) -> b -> c
apply f x = f x

これをアンカリー化して取得できます:

apply :: ((b -> c), b) -> c

矢印が関数を一般化する方法は、基本的に->は変数 ( a) に置き換えることです。の両方のオカレンスをinfixapplyに置き換えて、これを行いましょう:->a

apply :: (b `a` c, b) `a` c

applyの最初のバージョンと同じ構造をまだ見ることができ`a`ます->。ここで、バッククォートを削除してaプレフィックスを作成すると、次の署名が得られますapp

app :: a (a b c, b) c

ArrowApplyこれで、矢印にアプリケーションの概念を追加する方法がわかります。これは、>>=モナド (または、特に形状の関数a -> m b) への適用の概念である への応用です。これは、アローからモナドを構築するのに十分な追加構造であるため、ArrowApplyに同型Monadです。

なぜこれらを使用したいのでしょうか? 正直なところ、そうは思わないでしょう。アローはかなり過大評価されているので、モナドとアプリカティブ ファンクターに固執してください。

于 2013-07-16T10:21:10.850 に答える
1

モナドは、命令型のスタイル (ステップバイステップ) で書くことを可能にする手段です。

Arrowは、ブロックダイアグラム スタイルで記述できるツールです。

したがって、矢印のモナドは線形ブロック図のように見えます。

http://www.soi.city.ac.uk/~ross/talks/fop.pdf

http://www.haskell.org/arrows/syntax.html

于 2013-08-01T22:37:09.193 に答える