1. ホーム
  2. haskell

[解決済み] <*>は何と呼ばれ、何をするのですか?[クローズド]

2022-09-22 16:26:15

質問

Applicative型クラスのこれらの関数はどのように動作するのでしょうか?

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(つまり、演算子でなかったら、何と呼ばれているのでしょうか)

余談ですが、もしあなたが pure を数学者でない人にも親しみやすい名前に変更できるとしたら、あなたはそれを何と呼びますか?

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

<ブロッククオート

申し訳ありませんが、私は本当に数学を知らないので、私はApplicative型クラスの関数をどのように発音するのか気になります。

数学がわかるかどうかは、ここではほとんど関係ない、と私は思います。おそらくご存知のように、Haskell は抽象数学のさまざまな分野からいくつかの用語を拝借しており、特に カテゴリ理論 そこからファンクターとモナドを得ました。Haskell でのこれらの用語の使用は、正式な数学的定義から多少離れていますが、いずれにせよ、良い説明用語となるには十分なほど通常近いものです。

Applicative 型クラスの間に位置する FunctorMonad というように、同じような数学的根拠を持っていることが予想されます。のドキュメントでは Control.Applicative モジュールのドキュメントは次のように始まっています。

このモジュールは、ファンクタとモナドの中間の構造を記述します: それは純粋な式とシーケンシングを提供しますが、バインディングはありません。(技術的には、strong lax monoidal functorです)。

ふむ。

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

のようにキャッチーではありませんが Monad と思います。

このすべてが基本的に集約されるのは Applicative は特にどのような概念にも対応せず 面白い は数学的に特に興味深い概念に対応していないので、Haskellで使われる方法を捕らえる既製の用語が転がっていないのです。ですから、数学はひとまず置いておいてください。


もし私たちが (<*>) をどう呼ぶか知りたいなら、それが基本的に何を意味するのかを知ることが助けになるかもしれません。

で、どうしたかというと Applicative はどうしたのでしょうか。 するのか と呼ぶのでしょうか?

Applicative は、実際には 任意 関数を Functor . の組み合わせを考えてみましょう。 Maybe の組み合わせを考えてみましょう (おそらく最も単純で自明でない Functor ) と Bool (同様に、最も単純な非自明なデータ型)。

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

この関数は fmapnot での作業から Bool への取り組みから Maybe Bool . しかし、もし (&&) ?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

さて、それは私たちが望むものではありません まったく ! 実際、それはほとんど役に立たないのです。私たちは巧妙に、こっそり別の Bool の中に Maybe を背中に通すと...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

...しかし、それはダメです。ひとつは、間違っていることです。もうひとつは、それは 醜い . 試行錯誤を続けることができましたが、結局は の上で動作するように複数の引数を持つ関数を持ち上げる方法はないことがわかりました。 Functor . うっとおしい!

一方、簡単にできるのは Maybe 's Monad のインスタンスです。

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

さて、単純な関数を翻訳するだけでも大変な作業です。 Control.Monad がそれを自動的に行う関数を提供している理由です。 liftM2 . 名前にある「2」は,引数がちょうど2つの関数で動作することを意味します。3,4,5引数の関数については,同様の関数が存在します。これらの関数は <項目 より良い しかし、完璧ではありませんし、引数の数を指定することは醜くて不器用です。

ということで、私たちは の論文で紹介したApplicative型クラス . その中で、著者らは本質的に 2 つの見解を示しています。

  • 複数の引数を持つ関数を Functor は非常に自然なことである
  • そうすることで、全能力を必要としない Monad

通常の機能アプリケーションは、単純な用語の並列によって記述されるため、quot;解除アプリケーションをできるだけシンプルかつ自然にするために、この論文では、以下のものを紹介しています。 の中に持ち上げられたアプリケーションの代用となる接尾辞演算子を導入しました。 Functor そして、そのために必要なものを提供するための型クラスです。

これらすべてから、次のようなことがわかります。 (<*>) は単に関数アプリケーションを表します。では、なぜ空白の "並置演算子" と同じように発音するのでしょうか?

しかし、もしそれがあまり満足のいくものでないなら、私たちは Control.Monad モジュールもまた、モナドに対して同じことを行う関数を提供していることを観察することができます。

ap :: (Monad m) => m (a -> b) -> m a -> m b

ここで ap はもちろん、"apply" の略です。ということは、どんな MonadApplicative であり、かつ ap は後者に存在する特徴のサブセットのみを必要とするため、おそらく次のように言うことができます。 であれば (<*>) が演算子でなかったなら、それは ap .


また、別の方向から物事をアプローチすることもできます。それは Functor というリフティング操作を行います。 fmap を一般化したものだからです。 map 演算の一般化だからです。リストに対する関数はどのようなものかというと (<*>) ? そこにあるのは ap がありますが、これはそれ自体では特に有用ではありません。

実際、リストにはおそらくもっと自然な解釈があります。次の型シグネチャを見たときに何が思い浮かぶでしょうか。

listApply :: [a -> b] -> [a] -> [b]

リストを並列に並べ、1つ目の関数を2つ目の対応する要素に適用するというアイデアは、何かとても魅力的です。残念ながら、私たちの古い友人である Monad にとっては残念なことですが、この単純な操作 はモナドの法則に反しています。 に違反します。しかし、これによって Applicative を作ることができ、その場合 (<*>) を一般化したものを連ねることになります。 zipWith と呼ぶことができるかもしれません。 fzipWith ?


このzippingのアイデアは、実は私たちを丸ごと連れてきてくれます。モノイダルファンクターについて、先ほどの数学の話を思い出してください。名前が示すように、これはモノイドとファンクタの構造を組み合わせる方法で、どちらも Haskell でおなじみの型クラスです。

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

これらを一緒に箱に入れて、ちょっと揺らすとどんな感じになるか?から Functor というアイデアを残しておきます。 構造体を、その型パラメータから独立した から、そして Monoid は関数の全体的な形式を維持することにします。

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

本当に "empty" を作成する方法があるとは思いたくないのです。 Functor を作成する方法があるとは思いたくありませんし、任意の型の値を呼び出すこともできないので、型を固定することにします。 mfEmpty として f () .

また、強制的に mfAppend に一貫した型パラメータを必要とすることを強制したくないので、今はこのようになっています。

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

の結果型は何ですか? mfAppend ? 私たちは何も知らない2つの任意の型を持っているので、多くの選択肢はありません。最も賢明なことは、両方を保持することです。

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

どの時点で mfAppend は明らかに一般化された zip をリスト上で再構成することができます。 Applicative を簡単に再構築できます。

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

また、これによって pure の identity 要素と関係があることがわかります。 Monoid の ID 要素に関連しているので、他の良い名前は、単位値、NULL 演算、またはそのようなものを示唆するものであるかもしれません。


長くなりましたので、まとめますと。

  • (<*>) は単なる修正された関数アプリケーションなので、 "ap" または "apply" として読むか、通常の関数アプリケーションと同じように完全に消去することができます。
  • (<*>) もおおよそ一般化して zipWith をリスト上で一般化したもので、quot;zip functors with"として読むことができ、同じように fmap を"map a functor with"として読むのと同じです。

の意図に近いものです。 Applicative 型クラスの意図に近く、その名前が示すように、私が推奨するのはこれです。

実際、私は を自由に使うこと、そして発音しないこと、すべての持ち上げられたアプリケーション演算子 :

  • (<$>) これは、単一引数関数を Functor
  • (<*>) これは、複数引数の関数を Applicative
  • (=<<) に入る関数を束縛するものです。 Monad に入る関数を既存の計算機上に結合します。

この3つはすべて、本質的には、通常の関数アプリケーションに少しスパイスを加えたものです。