Haskell初心者なので、見当違いのことを言っているかもしれませんが、質問させ...

Haskell初心者なので、見当違いのことを言っているかもしれませんが、質問させてください 🙇

**※全てGHCi v8.0.2で処理を行っています**

fmapap を用いることで、任意個の引数を持つ関数と任意個のFunctorに対して、以下のような計算が行えるかと思います:

(*) <$> Just 1 <*> Just 2 => Just 2


ここで、TupleもFunctorのインスタンスであることを知り、以下のような計算を考えてみました:

(*) <$> (1, 1) <*> (2, 2) => (1, 2) となることを期待


しかしながら、上記の式はShowのインスタンスとして解決できないという趣旨のエラーにより、期待する結果が得られませんでした。

そこで、ステップごとに型を確かめてみたところ:

:t ((*) <$>)
((*) <$>) :: (Num a, Functor f) => f a -> f (a -> a)

:t ((*) <$> (1,1))
((*) <$> (1,1)) :: (Num t, Num a) => (t, a -> a)

:t ((*) <$> (1,1) <*>)
((*) <$> (1,1) <*>) :: (Num t, Num b, Monoid t) => (t, b) -> (t, b)

:t ((*) <$> (1,1) <*> (2,2))
((*) <$> (1,1) <*> (2,2)) :: (Num t, Num b, Monoid t) => (t, b)


これより、 ap を適用した時点で第一要素にMonoidの型制約が入っており、これがエラーの原因かと考えています。

なぜ ap を適用した時点でMonoidの型制約が入るのでしょうか?これは、TupleがApplicativeのインスタンスとして何か特殊化されているためなのでしょうか?
またもし上記が正しい場合、特殊化を回避すれば期待する計算結果は得られるのでしょうか?

長文となりましたが、ご教授いただければ幸いです :haskell:

Replies

Haskell初心者なので、見当違いのことを言っているかもしれませんが、質問させてください 🙇

**※全てGHCi v8.0.2で処理を行っています**

fmapap を用いることで、任意個の引数を持つ関数と任意個のFunctorに対して、以下のような計算が行えるかと思います:

(*) <$> Just 1 <*> Just 2 => Just 2


ここで、TupleもFunctorのインスタンスであることを知り、以下のような計算を考えてみました:

(*) <$> (1, 1) <*> (2, 2) => (1, 2) となることを期待


しかしながら、上記の式はShowのインスタンスとして解決できないという趣旨のエラーにより、期待する結果が得られませんでした。

そこで、ステップごとに型を確かめてみたところ:

:t ((*) <$>)
((*) <$>) :: (Num a, Functor f) => f a -> f (a -> a)

:t ((*) <$> (1,1))
((*) <$> (1,1)) :: (Num t, Num a) => (t, a -> a)

:t ((*) <$> (1,1) <*>)
((*) <$> (1,1) <*>) :: (Num t, Num b, Monoid t) => (t, b) -> (t, b)

:t ((*) <$> (1,1) <*> (2,2))
((*) <$> (1,1) <*> (2,2)) :: (Num t, Num b, Monoid t) => (t, b)


これより、 ap を適用した時点で第一要素にMonoidの型制約が入っており、これがエラーの原因かと考えています。

なぜ ap を適用した時点でMonoidの型制約が入るのでしょうか?これは、TupleがApplicativeのインスタンスとして何か特殊化されているためなのでしょうか?
またもし上記が正しい場合、特殊化を回避すれば期待する計算結果は得られるのでしょうか?

長文となりましたが、ご教授いただければ幸いです :haskell:

tにMonoidが入るのは、そのApplicativeインスタンスの制約にMonoidが入るからですね!
Monoid t => Applicative ((,) t)
https://www.stackage.org/haddock/lts-12.12/base-4.11.1.0/Prelude.html#t:Applicative

ありがとうございます!

こちら、最初は変な挙動に思えていましたが、第一要素を無視するのであればそもそもで文脈としてのFunctor(Maybeの様な)を使えば良いのかなと
第一要素を活かしてApplicativeとしての連鎖を許すなら、第一要素はMonoidとして連結していく方が自然かつ有用ということなんですかね

このあたり、何か数学的・圏論的な背景もあったりしたら別途知ってみたいです :haskell:

端的に言うと、ソース読めやおらぁ?!(大汗) ってことになるわけですが、 (u, f) <*> (v, x) = (u <> v, f x) というApplicativeインスタンスの宣言(一部抜粋)がなされてたので、タプルの左側の型の <> の仕方わかんないんだけど(怒り) と言われてるのだと思います。

少し補足なのですが,
> 第一要素を活かしてApplicativeとしての連鎖を許すなら、第一要素はMonoidとして連結していく方が自然かつ有用ということなんですかね
というのは正しいのですが,

(*) <$> (1, 1) <*> (2, 2) -- => (1, 2)

という動作で実装されていないのはそもそもこういう動作をApplicativeにすることが困難だからです.
理由は単純でApplicativeクラスのpureを実装できないからです.つまり,以下のような式において?の要素を埋められないからです.
pure 2 :: (Int, Int) -- => (?, 2)

デフォルト値として0を入れればいいのではないかという見方もできますが,デフォルト値というのは行いたい演算によって変わってくるので,Int型(や他の全ての型)でデフォルト値を一意に決めることは通常できません.

Monoidのインスタンスであればデフォルト値が決まりますし,apのための演算も提供できるというのがMonoidインスタンスで実装されている主な背景です(これは圏論でのApplicative functorの捉え方とも関連性はあるのですが,あまり安易な解説をしてる文献は知らないですね… 興味があれば, https://bartoszmilewski.com/2017/02/06/applicative-functors/ とかを読んでみるといいかもしれません)

ところで,全ての型に強制的にデフォルト値を足して,演算として最初の要素をただ単に返すMonoidインスタンスを持つデータ型はFirst(https://www.stackage.org/haddock/lts-12.12/base-4.11.1.0/Data-Monoid.html#t:First)という名前で提供されていて,これを使えばおそらく最初に期待した動作は得られます.
(*) <$> (First (Just 1), 1) <*> (First (Just 2), 2) == (First (Just 1), 1 * 2)

@U57D08Z9U pure が実装できないというのはすごく納得です!ありがとうございます 🙂

>数学的・圏論的な背景
Applicativeは自己函手の圏でDay convolutionを積とした場合のモノイド対象で、MonoidはSet圏のモノイド対象である事がちょっと関係あったりするかもしれないなあと思いました(小並感)

その説はオーソドックスけど、個人的に、monoidal functor として解釈する方がわかりやすいと思う、Coendなどのことを要らなくなる

@UACQ9J5D3 そのあたりを調べるとあたりがつくんだろうなと考えて、 http://www.staff.city.ac.uk/~ross/papers/Applicative.pdf を読んでいるのですが、2-Tuple Applicativeの実装として自然に上述のものが得られる根拠は *4. Monoids are phontom Applicative functors* に記述されているのかなと理解しました
( newtype Accy o a = Acc { acc :: o } に対して、 a を無視しない適切な構成を考えると2-Tuple Applicativeの実装になるかなと)

@UDC1LV887 さんが上げているPDFにある通り,Applicativeは(Strong) monoidal functorと一致します.

monoidal functor同士の積はmonoidal functorになります.(m, -) ~ Const m * Identityで,
instance Monoidal Identity where
unit = Identity ()
Identity x * Identity y = Identity (x, y)
と書けますから,後はConst mからmonoidal functorが作れれば自明な(m, -)のmonoidal functorが導出できます.Const m上ではfmapは意味を為さないので,ここから算出されるmonoidal functorはHaskでは唯一で,それが 4. Monoids are phontom Applicative functors に記述されている Accy ですね.

この(m, -)の実装は一般にはWriter Monadと呼ばれています.

@U57D08Z9U
Const m * Identity が 2-Tuple Applicativeになるよねというふわっとした理解がより補強されました、ありがとうございます!(唯一であることも気になっていたので、完全にすっきりしました :haskell: )

自分のやつも少し詳しく考えてみました。

関手(a,)から(b,)への自然変換は forall x. (a, x) -> (b, x) ですが、これは関数 a->b と同型です。
したがってこの自然変換を射とする圏、すなわちHask^Haskの関手圏の部分圏 (*,) と、関数を射とするHask圏は圏同値になります。
a->bを上記の自然変換にマップする関数を以下の通り定義しておきます。

iso1 :: (a->b) -> (a, x) -> (b, x)
iso1 f = \(a, x) -> (f a, x)

さらに、Data.Functor.Day (http://hackage.haskell.org/package/kan-extensions-5.2/docs/Data-Functor-Day.html) で定義されるDayについて、

Day (a,) (b,) x = forall p q. Day (a, p) (b, q) (p -> q -> x)
<-> ((a, b) (forall p q. (p, q, p -> q -> x))
<-> ((a, b), x)


という同型を考えられますが、これは前述の圏同値でHaskの (a, b) に対応する対象になります。つまり、圏同値によって(,)はDay convolution(の同型)に対応します。
Day (a,) (b,) x((a,b), x) にマップする関数を以下のように定義しておきます。

iso2 :: Day (a,) (b,) x -> ((a, b), x)
iso2 (Day (a, p) (b, q) f) = ((a, b), f p q)

以上より圏同値によって、(,)をモノイド積としたHaskのモノイド対象Monoidは、Day convolutionを積とした(*,)のモノイド対象Applicativeと一対一関係になります。

実際、(*,) の <*>\x y -> iso1 (uncurry (<>)) . iso2 $ day x y によって作られます。

@UACQ9J5D3 さんが仰るように直観性に欠けるとは思います。