通常の関数合成は次のタイプです
_(.) :: (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つの引数の代わりにタプルを使用することですが、これは同じではありません。
私がやりたいことをすることは可能ですか?そうでない場合、それが可能であるべきだと私に思わせる誤解は何ですか?
注:これは事実上すでに尋ねられています ここ 、しかし答え(私が存在しなければならないと思う)は与えられませんでした。
誤解は、型_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)
に適用してみてください。これは、y
をc
および_b -> c
_と統合する必要があることを意味します。これはあまり意味がありません。 y
がc
とc
を返す関数の両方になるにはどうすればよいですか?それは無限のタイプでなければなりません。したがって、これは機能しません。
標準の合成演算子を使用できないからといって、独自の演算子を定義することを妨げることはありません。
_ 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
_
これに対する私の好ましい実装は
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))
等.
一般に、それ自体で構成されたfmap
n回は、fmap
に使用できます。 nレベルの深さ!
また、関数はFunctor
を形成するため、これはn引数の配管を提供します。
詳細については、Conal Elliottの Semantic Editor Combinators を参照してください。
これを行う標準ライブラリ関数はわかりませんが、それを実現するポイントフリーパターンは、合成関数を作成することです。
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
これをコメントで書くつもりでしたが、少し長く、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
はい、modminus
とdiffsqmod
は非常にランダムで価値のない関数ですが、迅速で要点を示しています。別の作成関数で作成することにより、次のレベルを定義するのがいかに簡単であるかに注目してください(Edwardが言及した連鎖fmap
sと同様)。
(.***) = (.) . (.**)
実際には、compose12
以降は、演算子ではなく関数名を記述する方が短くなります。
f .*********** g
f `compose12` g
アスタリスクを数えるのは面倒ですが、コンベンションを4または5で止めたいと思うかもしれません。
[編集]別のランダムなアイデアとして、compose2には.:
、compose3には.:.
、compose4には.::
、compose5には.::.
、compose6には.:::
を使用できます。ドットの数(最初のドットの後)に、ドリルダウンする引数の数を視覚的にマークさせます。でも星の方が好きだと思います。
_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
_
これがあなたが望むものを達成するためのエレガントな方法だと私が思うものです。 Functor
型クラスは、関数をコンテナに「プッシュ」する方法を提供するため、fmap
を使用して各要素に関数を適用できます。関数a -> b
はb
sのコンテナと考えることができ、各要素はa
の要素によってインデックス付けされます。したがって、このインスタンスを作成するのは自然なことです。
instance Functor ((->) a) where
fmap f g = f . g
(適切なライブラリをimport
することでそれを取得できると思いますが、どちらかは思い出せません。)
これで、f
とg
の通常の構成は簡単に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
を使用できることがわかります。