web-dev-qa-db-ja.com

2-Tuple Functorインスタンスが関数を2番目の要素にのみ適用するのはなぜですか?

import Control.Applicative

main = print $ fmap (*2) (1,2)

(1,4)を生成します。 (2,4)を生成することを期待しますが、代わりに関数はタプルの2番目の要素にのみ適用されます。

更新私は基本的にこれをほぼすぐに理解しました。すぐに自分の答えを投稿します。

34
Peter Hall

Functorインスタンスは、実際にはControl.Applicativeによってインポートされた GHC.Base モジュールからのものです。

必要なインスタンスを作成しようとすると、タプルの定義を考えると、機能しないことがわかります。インスタンスに必要なタイプパラメータは1つだけですが、2タプルには2つあります。

有効なFunctorインスタンスは、少なくとも各要素に同じタイプのタプル(a,a)上にある必要がありますが、インスタンスを次のように定義するなど、卑劣なことはできません。

 type T2 a = (a,a)

インスタンスタイプを同義語にすることは許可されていないためです。

上記の制限された2タプルの同義語は、論理的には次のタイプと同じです。

data T2 a = T2 a a

どのcanがFunctorインスタンスを持つことができます:

instance Functor T2 where
    fmap f (T2 x y) = T2 (f x) (f y)

Gabrielがコメントで述べたように、これは構造の分岐や並行性に役立つ可能性があります。

15
Peter Hall

質問でこれに答えさせてください:あなたはどの出力を期待しますか:

main = print $ fmap (*2) ("funny",2)

あなたcanあなたが望むものを持っています(data Pair a = Pair a aなどを使用)が、(,)は最初と2番目の引数で異なるタイプを持っているかもしれないので、あなたは運が悪いです。

31
Landei

ペアは、基本的に次のように定義されます。

_data (,) a b = (,) a b
_

Functorクラスは次のようになります。

_class Functor f where
  fmap :: (a -> b) -> f a -> f b
_

関数の引数と結果の型は種類_*_である必要があるため(つまり、さらにエキゾチックなものに適用できる型関数ではなく値を表す)、_a :: *_、_b :: *_が必要です。 、そして、私たちの目的にとって最も重要なのは、_f :: * -> *_です。 _(,)_の種類は_* -> * -> *_であるため、Functorに適した型を取得するには、種類_*_の型に適用する必要があります。したがって、

_instance Functor ((,) x) where
  -- fmap :: (a -> b) -> (x,a) -> (x,b)
_

したがって、実際には、他のことを行うFunctorインスタンスを作成する方法はありません。


ペアを操作するためのより多くの方法を提供する1つの便利なクラスは、_Data.Bifunctor_のBifunctorです。

_class Bifunctor f where
  bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
  bimap f g = first f . second g

  first :: (a -> b) -> f a y -> f b y
  first f = bimap f id

  second :: (c -> d) -> f x c -> f x d
  second g = bimap id g
_

これにより、次のようなものを書くことができます(_Data.Bifunctor.Join_から):

_  newtype Join p a =
    Join { runJoin :: p a a }

  instance Bifunctor p => Functor (Join p) where
    fmap f = Join . bimap f f . runJoin
_

Join (,)は本質的にPairと同じです。ここで

_data Pair a = Pair a a
_

もちろん、Bifunctorインスタンスを使用してペアを直接操作することもできます。

21
dfeuer