4

是否有任何解释为什么提升函数在应用于 2 元组时仅适用于第二个条目:

f x = x + 1
f <$> (2,2)
    // -> (2,3)

另一方面,任何长度不是 2的元组都会返回错误。还

:t f <$>

返回错误。作用于元组时是否可以看到类型f <$>

这种行为有什么解释吗?

Data.Tuple 文档非常简短,没有提到函数是如何提升到元组的。有没有解释它的来源?


更新。关于 2-tuples 的问题的一部分与这个答案有关,但是,上面关于多长度元组的问题没有得到解决。

4

4 回答 4

3

可以(并且可以说,GHC 应该)为三元组和更大的元组定义一个 Functor 实例。以机智:

instance Functor ((,,) a b) where
    fmap f (a, b, c) = (a, b, f c)

如果这个实例真的不存在于 中的任何地方base,我怀疑这主要是疏忽,尽管我对历史的了解不足以肯定地说。您可以将其包含在任何看起来有用的代码中,但需要注意的是,您应该绝对base在文件中的版本上设置一个相当严格的上限*.cabal,因为此实例可能会合理地包含在base. 在这种情况下, PVP只允许版本的第三个组件更改,因此在您的上限中至少包含那么多组件!

于 2016-12-26T09:41:17.893 回答
3

是否有任何解释为什么提升函数在应用于 2 元组时仅适用于第二个条目

因为元组是异构的,这意味着,一般来说,尝试将类型函数应用于类型b -> c元组的每个组件是没有意义的(a, b)

如果你想要相同类型的值对,你可以声明你自己的类型Pair然后让仿函数实例将函数应用于每个组件。

data Pair a = Pair { fst :: a
                   , snd :: a }

instance Functor Pair where
  fmap f (Pair fst snd) = Pair (f fst) (f snd)

作用于元组时是否可以看到类型f <$>

f <$>是一个部分(部分应用的中缀运算符)。要获取它的类型,您需要用括号将其包装起来,如下所示:

:t (f <$>)

Data.Tuple 文档非常简短,没有提到函数是如何提升到元组的。有没有解释它的来源?

组合子(<$>)(and (<*>)) 比元组更通用,您可以在Control.Applicative模块中找到它们。

于 2016-12-26T09:49:07.233 回答
3

这里的所有其他答案似乎都不错,但我认为还没有人准确回答你的问题。

我相信默认情况下以这种方式处理 2 元组(并且没有其他元组)的原因是因为这允许它们以与 aWriter单子上下文中相同的方式使用。(即((,) a)Writer是同构的。)

例如,给定一个在Writermonad 中运行的函数:

import Control.Monad.Writer

foo :: Int -> Writer [String] Int
foo n = do tell ["Running foo " ++ show n]
           if (n <= 0) then do
             tell ["We are done!"]
             return 1
           else do
             rest <- foo (n-1)
             return (n * rest)

您可以使用以下Monad实例重写它((,) a)

bar :: Int -> ([String], Int)
bar n = do tell' ["Running bar " ++ show n]
           if (n <= 0) then do
             tell' ["We are done!"]
             return 1
           else do
             rest <- bar (n-1)
             return (n * rest)
  where tell' str = (str, ())

你会发现这些做同样的事情:

runWriter (foo 5)
bar 5

直到对的排序。

tell'仅需要定义 of ,因为由于某种原因((,) a)尚未成为 of 的实例。MonadWriter

(编辑添加:)虽然您可以将定义扩展到更大的元组,但这并没有真正为该对的定义提供任何额外的通用性:该对的一个组件是您可以写入的幺半群,而另一个组件是 monad 上下文中的底层“值”——如果你需要更多的组件,你可以让组件本身成为一个元组。

于 2016-12-26T21:34:06.290 回答
2

在这个答案中,我将稍微扩展一下我在评论中提出的建议之一。

作用于元组时是否可以看到类型f <$>

(<$>)是一个多态函数:

GHCi> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b

使用 GHC 8,您可以使用TypeApplications扩展来专门化多态函数,方法是提供它们的部分或全部类型变量的实例化(在本例中f,按顺序排列):ab

GHCi> :set -XTypeApplications 
GHCi> :t (<$>) @Maybe
(<$>) @Maybe :: (a -> b) -> Maybe a -> Maybe b
GHCi> :t (<$>) @Maybe @Int
(<$>) @Maybe @Int :: (Int -> b) -> Maybe Int -> Maybe b
GHCi> :t (<$>) @Maybe @_ @Bool
(<$>) @Maybe @_ @Bool :: (t -> Bool) -> Maybe t -> Maybe Bool
GHCi> :t (<$>) @_ @Int @Bool
(<$>) @_ @Int @Bool
  :: Functor t => (Int -> Bool) -> t Int -> t Bool
GHCi> :t (<$>) @Maybe @Int @Bool
(<$>) @Maybe @Int @Bool :: (Int -> Bool) -> Maybe Int -> Maybe Bool

要将其与对一起使用,请使用对类型构造函数的前缀语法:

GHCi> :t (<$>) @((,) _)
(<$>) @((,) _) :: (a -> b) -> (t, a) -> (t, b)
GHCi> -- You can use the specialised function normally.
GHCi> -- That includes passing arguments to it.
GHCi> f x = x + 1
GHCi> :t (<$>) @((,) _) f
(<$>) @((,) _) f :: Num b => (t, b) -> (t, b)

_in未指定该((,) _)对的第一个元素的类型(它是(,)类型构造函数的第一个参数)应该是什么。它的每一个选择都会产生不同的Functor. 如果您愿意,可以更具体:

GHCi> :t (<$>) @((,) String) f
(<$>) @((,) String) f :: Num b => (String, b) -> (String, b)

最后,值得看看如果您尝试使用 3 元组会发生什么:

GHCi> :t (<$>) @((,,) _ _) f
(<$>) @((,,) _ _) f
  :: (Num b, Functor ((,,) t t1)) => (t, t1, b) -> (t, t1, b)

正如 Daniel Wagner 在他的回答中所讨论的,base没有Functor为 3 元组定义一个实例。尽管如此,类型检查器不能排除某个地方的某个人可能已经为前两个类型参数的某些选择定义了一个特定的实例的可能性,但那将毫无意义。出于这个原因,推测性约束Functor ((,,) t t1)出现在类型中(对不会发生这种情况,因为在baseFunctor ((,) a)中有一个实例)。正如预期的那样,一旦我们尝试实例化前两个类型参数,它就会爆炸:

GHCi> :t (<$>) @((,,) Bool String) f

<interactive>:1:1: error:
    • Could not deduce (Functor ((,,) Bool String))
        arising from a use of ‘&lt;$>’
      from the context: Num b
        bound by the inferred type of
                 it :: Num b => (Bool, String, b) -> (Bool, String, b)
        at <interactive>:1:1
    • In the expression: (<$>) @((,,) Bool String) f
于 2016-12-26T18:06:16.920 回答