36

ファンクターについては理解fmap . fmapしていると思いますが、機能に関しては、何ヶ月も頭を痛めています。

の定義をに適用できることは(.)わかり(.) . (.)ましたが、その方法を忘れてしまいました。
自分で試してみると、いつも間違っていることがわかります。

(.) f g = \x -> f (g x)
(.) (.) (.) = \x -> (.) ((.) x)
\x f -> (.) ((.) x) f
\x f y  -> (((.)(f y)) x)
\x f y g-> (((.)(f y) g) x)
\x f y g-> ((f (g y)) x)
\x f y g-> ((f (g y)) x):: t2 -> (t1 -> t2 -> t) -> t3 -> (t3 -> t1) -> t

「定義を適用するだけ」がそれを行う唯一の方法である場合、誰かがどのように思いついたの(.) . (.)ですか?
私が欠けているより深い理解や直感があるに違いありません。

4

5 に答える 5

38

思いつくこと(.) . (.)は実際にはかなり簡単です、それはそれが何をするかの背後にある直感であり、理解するのは非常に難しいです。

(.)式を「パイプ」スタイルの計算に書き直すときに、非常に遠くまで到達します(|シェルで考えてみてください)。ただし、1つだけを取る関数で複数の引数を取る関数を作成しようとすると、使用するのが面倒になります。concatMap例として、次の定義を考えてみましょう。

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)

取り除くことxsは単なる標準的な操作です:

concatMap f = concat . map f

ただし、を取り除く「良い」方法はありませんf。これは、2つの引数が必要であり、最終結果mapに適用したいという事実が原因です。concat

もちろん、いくつかのポイントフリーのトリックを適用して、次のことだけで逃げることができます(.)

concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map

しかし、残念ながら、このコードの可読性はほとんどなくなっています。代わりに、必要なことを正確に実行する新しいコンビネータを導入します。最初の関数の最終結果に2番目の関数を適用します。

-- .: is fairly standard name for this combinator
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x y = f (g x y)

concatMap = concat .: map

いいでしょう、それはやる気のためです。ポイントフリービジネスに取り掛かりましょう。

(.:) = \f g x y -> f (g x y)
     = \f g x y -> f ((g x) y)
     = \f g x y -> f . g x $ y
     = \f g x   -> f . g x

さて、ここに興味深い部分があります。これは、行き詰まったときに通常役立つポイントフリーのトリックのもう1つです.。プレフィックス形式に書き直して、そこから続行しようとします。

     = \f g x   -> (.) f (g x)
     = \f g x   -> (.) f . g $ x
     = \f g     -> (.) f . g
     = \f g     -> (.) ((.) f) g
     = \f       -> (.) ((.) f)
     = \f       -> (.) . (.) $ f
     =             (.) . (.)

直感に関しては、あなたが読むべきこのとても素晴らしい記事があります。についての部分を言い換えます(.)

私たちのコンビネータが何をすべきかをもう一度考えてみましょう:それはの結果結果fに適用されるべきです(私は意図的に前の部分で最終結果を使用していました、それはあなたが完全に適用したときに実際に得られるものです-モジュロ統一型変数を別のものと関数型-関数、ここでの結果は、いくつかのアプリケーションにすぎません)。ggg xx

結果に適用fすることはどういう意味ですか?さて、ある値に適用したら、結果を取得して適用します。おなじみのように聞こえます:それが何をするかです。ggf(.)

result :: (b -> c) -> ((a -> b) -> (a -> c))
result = (.)

さて、これらのコンビネータの合成(私たち言葉)は単なる関数合成であることがわかります。つまり、次のようになります。

(.:) = result . result -- the result of result
于 2013-02-22T18:12:02.663 に答える
20

の理解を活用することもできますfmap . fmap

とが2つFunctorある場合は、foobar

fmap . fmap :: (a -> b)  ->  foo (bar a)    ->   foo (bar b)

fmap . fmapFunctor関数を取り、2つのsの合成のための誘導関数を生成します。

さて、どのタイプの場合もt(->) tは、Functorであり、そのfmapためのFunctorはです(.)

2つのがとである場合もそう(.) . (.)です。fmap . fmapFunctor(->) s(->) t

(.) . (.) :: (a -> b) -> ((->) s) ((->) t a) -> ((->) s) ((->) t b)
          =  (a -> b) -> (s -> (t -> a))     -> (s -> (t -> b))
          =  (a -> b) -> (s ->  t -> a )     -> (s ->  t -> b )

f :: a -> b2つの引数の関数を持つ関数を「構成」しますg :: s -> t -> a

((.) . (.)) f g = \x y -> f (g x y)

その見方はまた、パターンがより多くの引数を取る関数にどのように拡張されるかを明らかにします。

(.)             :: (a -> b) -> (s ->           a) -> (s ->           b)
(.) . (.)       :: (a -> b) -> (s -> t ->      a) -> (s -> t ->      b)
(.) . (.) . (.) :: (a -> b) -> (s -> t -> u -> a) -> (s -> t -> u -> b)

于 2013-02-22T18:38:32.313 に答える
5

を導入すると、ソリューションは分岐しますy。そのはず

\x f y -> ((.) ((.) x) f) y     :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) ((.) x) f) y z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) x (f y)) z     :: (c -> d) -> (a -> b -> c) -> a -> b -> d
-- Or alternately:
\x f y z -> (x . f y) z         :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> (x (f y z))         :: (c -> d) -> (a -> b -> c) -> a -> b -> d

元の型署名と一致するもの:(.) . (.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d

(ghciで拡張を行うのが最も簡単で、各ステップをで確認できます:t expression

編集:

より深い直感はこれです:

(.)単に次のように定義されます

\f g -> \x -> f (g x)

これを単純化できます

\f g x -> f (g x)

したがって、2つの引数を指定すると、カレーが発生し、解決するには別の引数が必要になります。2つの引数を使用するたび(.)に、もう1つの引数の「必要性」を作成します。

(.) . (.)もちろんです(.) (.) (.)ので、展開してみましょう。

(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))

ベータリダクションを行うことができf0ますg0(ただし、!はありませんx0):

\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0) 

2番目の式をf1...に置き換えます

\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1) 

今では「裏返し」です!(beta-reduction on f2):
これは興味深いステップです-x0代わりにf2-これはx、データであった可能性のある、が代わりに関数であることを意味します。
これ(.) . (.)提供するものです-追加の引数の「必要性」。

\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1) 

これは正常に見え始めています...最後にベータリデュースしましょう(上g2):

\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))

だから私たちは単純に残されています

\x0 g1 x1 x2 -> x0 ((g1 x1) x2)

、ここで、引数はまだ適切に整理されています。

于 2013-02-22T17:59:29.383 に答える
3

だから、これは私がもう少し増分拡張を行うときに私が得るものです

(.) f g   = \x -> f (g x)
(.) . g   = \x -> (.) (g x)
          = \x -> \y -> (.) (g x) y
          = \x -> \y -> \z -> (g x) (y z)
          = \x y z -> (g x) (y z)
(.) . (.) = \x y z -> ((.) x) (y z)
          = \x y z -> \k -> x (y z k)
          = \x y z k -> x (y z k)

ghciによると、これは正しいタイプです

Prelude> :t (.) . (.)
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
Prelude> :t \x y z k -> x (y z k)
\x y z k -> x (y z k)
  :: (t1 -> t) -> (t2 -> t3 -> t1) -> t2 -> t3 -> t
Prelude> 

このコンビネータの起源はわかりませんが、コンビネータを厳密に操作するコンビネータ論理で使用するために開発された可能性が高いため、より便利なラムダ式を使用して定義することはできません。これらのことを理解することに伴う直感があるかもしれませんが、私はそれを見つけていません。ほとんどの場合、それを十分に行う必要がある場合は、ある程度の直感を身に付けることができます。

于 2013-02-22T17:58:09.077 に答える
3

ラムダ式の代わりに、コンビネータスタイルの方程式を書くのが最も簡単です。は、と同等であり、その逆も同様です。それで、a b c = (\x -> ... body ...)a b c x = ... body ...x{a,b,c}

-- _B = (.)  

_B f g x = f (g x)
_B _B _B f g x y = _B (_B f) g x y
                 = (_B f) (g x) y
                 = _B f (g x) y
                 = f ((g x) y)
                 = f (g x y)

与えられた場合、それを組み合わせ形式f (g x y)に変換したい場合にこれを発見します(すべての括弧と変数の繰り返しを取り除きます)。次に、コンビネータの定義に対応するパターンを適用し、できればこの派生を逆方向にトレースします。ただし、これは機械的/自動的ではありません。

于 2013-02-22T18:02:18.210 に答える