web-dev-qa-db-ja.com

タイプ(c→d)→(a→b→c)→(a→b→d)のHaskell関数合成演算子

通常の関数合成は次のタイプです

_(.) :: (b -> c) -> (a -> b) -> a -> c
_

これは次のようなタイプに一般化する必要があると思います。

_(.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
_

具体的な例:差の二乗の計算。 diffsq a b = (a - b) ^ 2と書くこともできますが、のように感じますdiffsq = (^2) . (-)のようなものを書くために_(-)_と_(^2)_を作成できるはずです。

もちろんできません。私がcanすることの1つは、uncurryで変換することにより、_(-)_への2つの引数の代わりにタプルを使用することですが、これは同じではありません。

私がやりたいことをすることは可能ですか?そうでない場合、それが可能であるべきだと私に思わせる誤解は何ですか?


注:これは事実上すでに尋ねられています ここ 、しかし答え(私が存在しなければならないと思う)は与えられませんでした。

42
jameshfisher

誤解は、型_a -> b -> c_の関数を戻り値の型cの2つの引数の関数と考えるのに対し、実際には戻り値の型_b -> c_の1つの引数の関数であるということです。関数の型が右側に関連付けられているため(つまり、a -> (b -> c)と同じです。これにより、標準の関数合成演算子を使用できなくなります。

理由を確認するには、タイプ_(.)_演算子である_(y -> z) -> (x -> y) -> (x -> z)_演算子を2つの関数_g :: c -> d_とf :: a -> (b -> c)に適用してみてください。これは、ycおよび_b -> c_と統合する必要があることを意味します。これはあまり意味がありません。 yccを返す関数の両方になるにはどうすればよいですか?それは無限のタイプでなければなりません。したがって、これは機能しません。

標準の合成演算子を使用できないからといって、独自の演算子を定義することを妨げることはありません。

_ compose2 :: (c -> d) -> (a -> b -> c) -> a -> b -> d
 compose2 g f x y = g (f x y)

 diffsq = (^2) `compose2` (-)
_

通常、この場合はポイントフリースタイルの使用を避けて、

_ diffsq a b = (a-b)^2
_
33
hammar

これに対する私の好ましい実装は

fmap . fmap :: (Functor f, Functor f1) => (a -> b) -> f (f1 a) -> f (f1 b)

覚えやすいからといって。

Fとf1をそれぞれ(->) c(->) dにインスタンス化すると、次のタイプが得られます。

(a -> b) -> (c -> d -> a) -> c -> d -> b

のタイプです

(.) . (.) ::  (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c

しかし、fmap . fmapバージョンをガタガタ鳴らすのは少し簡単で、他のファンクターに一般化されます。

これはfmap fmap fmapと書かれることもありますが、fmap . fmapと書かれると、より簡単に展開してより多くの引数を許可できます。

fmap . fmap . fmap 
:: (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))

fmap . fmap . fmap . fmap 
:: (Functor f, Functor g, Functor h, Functor i) => (a -> b) -> f (g (h (i a))) -> f (g (h (i b))

等.

一般に、それ自体で構成されたfmapn回は、fmapに使用できます。 nレベルの深さ!

また、関数はFunctorを形成するため、これはn引数の配管を提供します。

詳細については、Conal Elliottの Semantic Editor Combinators を参照してください。

46
Edward KMETT

これを行う標準ライブラリ関数はわかりませんが、それを実現するポイントフリーパターンは、合成関数を作成することです。

(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
20
mightybyte

これをコメントで書くつもりでしたが、少し長く、mightybyteとhammarの両方から引用しています。

.*の場合はcompose2.**の場合はcompose3などの演算子を標準化することをお勧めします。 mightybyteの定義の使用:

(.*) :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
(.*) = (.) . (.)

(.**) :: (d -> e) -> (a -> b -> c -> d) -> (a -> b -> c -> e)
(.**) = (.) . (.*)

diffsq :: (Num a) => a -> a -> a
diffsq = (^2) .* (-)

modminus :: (Integral a) => a -> a -> a -> a
modminus n = (`mod` n) .* (-)

diffsqmod :: (Integral a) => a -> a -> a -> a
diffsqmod = (^2) .** modminus

はい、modminusdiffsqmodは非常にランダムで価値のない関数ですが、迅速で要点を示しています。別の作成関数で作成することにより、次のレベルを定義するのがいかに簡単であるかに注目してください(Edwardが言及した連鎖fmapsと同様)。

(.***) = (.) . (.**)

実際には、compose12以降は、演算子ではなく関数名を記述する方が短くなります。

f .*********** g
f `compose12` g

アスタリスクを数えるのは面倒ですが、コンベンションを4または5で止めたいと思うかもしれません。


[編集]別のランダムなアイデアとして、compose2には.:、compose3には.:.、compose4には.::、compose5には.::.、compose6には.:::を使用できます。ドットの数(最初のドットの後)に、ドリルダウンする引数の数を視覚的にマークさせます。でも星の方が好きだと思います。

8
Dan Burton

Maxcomment で指摘したように:

_diffsq = ((^ 2) .) . (-)
_

_f . g_は、1つの引数をgに適用し、その結果をfに渡すと考えることができます。 _(f .) . g_は2つの引数をgに適用し、結果をfに渡します。 _((f .) .) . g_は、gなどに3つの引数を適用します。

_\f g -> (f .) . g :: (c -> d) -> (a -> b -> c) -> a -> b -> d
_

関数_f :: c -> d_(左側にfがある部分適用)を使用して合成演算子を左セクション化すると、次のようになります。

_(f .) :: (b -> c) -> b -> d
_

したがって、_b -> c_からの関数を期待するこの新しい関数がありますが、gは_a -> b -> c_、または同等にa -> (b -> c)です。必要なものを取得する前に、aを適用する必要があります。さて、もう一度繰り返しましょう:

_((f .) .) :: (a -> b -> c) -> a -> b -> d
_
4
gdejohn

これがあなたが望むものを達成するためのエレガントな方法だと私が思うものです。 Functor型クラスは、関数をコンテナに「プッシュ」する方法を提供するため、fmapを使用して各要素に関数を適用できます。関数a -> bbsのコンテナと考えることができ、各要素はaの要素によってインデックス付けされます。したがって、このインスタンスを作成するのは自然なことです。

instance Functor ((->) a) where
  fmap f g = f . g

(適切なライブラリをimportすることでそれを取得できると思いますが、どちらかは思い出せません。)

これで、fgの通常の構成は簡単にfmapになります。

o1 :: (c -> d) -> (b -> c) -> (b -> d)
f `o1` g = fmap f g

タイプa -> b -> cの関数は、タイプcの要素のコンテナーのコンテナーです。したがって、関数fを2回プッシュする必要があります。どうぞ:

o2 :: (c -> d) -> (a -> (b -> c)) -> a -> (b -> d)
f `o2` g = fmap (fmap f) g

実際には、o1またはo2は必要なく、fmapだけが必要な場合があります。また、場所を忘れたライブラリが見つかった場合は、追加のコードを記述せずにfmapを使用できることがわかります。

3
sigfpe