是否有任何解释为什么提升函数在应用于2元组时仅适用于第2个条目:
f x = x + 1
f <$> (2,2)
// -> (2,3)
另一方面,任何其他长度<2>的元组都会返回错误。 还
:t f <$>
返回错误。在对元组进行操作时是否可以看到f <$>
的类型?
这种行为有什么解释吗?
The Data.Tuple documentation非常简短,没有提到如何将函数提升为元组。有没有消息来源解释它?
更新。关于2元组的问题的一部分与this answer有关,但是,关于多个长度元组的上述问题没有得到解决。
答案 0 :(得分:3)
可以(并且可以说,GHC应该)为三元组和更大的元组定义一个Functor实例。即:
instance Functor ((,,) a b) where
fmap f (a, b, c) = (a, b, f c)
如果这个实例确实不存在于base
的任何地方,我怀疑这主要是疏忽,尽管我不太清楚地知道这段历史。您可以将其包含在任何看起来有用的代码中,但需要注意的是,您应该绝对对base
文件中的*.cabal
版本设置一个相当严格的上限,因为此实例可能合理地包含在base
的未来版本中。 The PVP只允许在这种情况下更改版本的第三个组件,因此在上限中至少包含那么多组件!
答案 1 :(得分:3)
是否有任何解释为什么提升函数在应用于2元组时仅适用于第2个条目
因为元组是异构的,这意味着,通常,尝试将类型b -> c
的函数应用于类型为{{1的元组的每个组件 - 没有意义}}
如果你想要对相同类型的值,你可以声明自己的类型(a, b)
,然后让functor实例将函数应用于每个组件。
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 <$>
Data.Tuple文档非常简短,没有提及如何将函数提升为元组。有没有消息来源解释它?
组合子:t (f <$>)
(和(<$>)
)比一般的元组更通用,你可以在Control.Applicative模块中找到它们。
答案 2 :(得分:3)
这里的所有其他答案看起来都不错,但我认为没有人能够正确回答你的问题。
我相信原因 2元组(并且没有其他元组)默认情况下这样处理是因为这允许它们以与Writer
中的((,) a)
相同的方式使用monadic context。 (也就是说,Writer
和Writer
是同构的。)
例如,给定一个在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中运行的函数:
Monad
您可以使用((,) a)
的{{1}}实例重写它:
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'
的定义,因为由于某种原因,((,) a)
尚未成为MonadWriter
的实例。
(已编辑添加:)虽然可以将定义扩展为更大的元组,但这并不能真正为该对的定义提供任何额外的通用性:该对的一个组件是你可以写一个monoid,另一个组件是底层&#34;值&#34;在monad上下文中 - 如果你需要更多的组件,你可以让组件本身成为一个元组。
答案 3 :(得分:2)
在这个答案中,我将稍微阐述一下我在评论中提出的建议之一。
在对元组进行操作时是否可以看到
f <$>
的类型?
(<$>)
是一个多态函数:
GHCi> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
使用GHC 8,您可以使用TypeApplications
扩展来专门化多态函数,方法是提供部分或全部类型变量的实例化(在本例中为f
,a
和{ {1}},按此顺序):
b
要将它用于配对,请使用配对类型构造函数的前缀语法:
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)
中的_
使其未指定该对的第一个元素的类型(这是((,) _)
类型构造函数的第一个参数)应该是什么。它的每一个选择都会产生不同的(,)
。如果您愿意,可以更具体:
Functor
最后,值得一看的是如果您尝试使用3元组会发生什么:
GHCi> :t (<$>) @((,) String) f
(<$>) @((,) String) f :: Num b => (String, b) -> (String, b)
正如Daniel Wagner在his answer中讨论的那样, base 并没有为3元组定义GHCi> :t (<$>) @((,,) _ _) f
(<$>) @((,,) _ _) f
:: (Num b, Functor ((,,) t t1)) => (t, t1, b) -> (t, t1, b)
个实例。尽管如此,类型检查器不能排除某个地方某人可能已经定义了特定于前两个类型参数的某些选择的实例的可能性,但这是无意义的。出于这个原因,推测约束Functor
显示在类型中(因为在 base 中有Functor ((,,) t t1)
个实例,因此配对不会发生这种情况)。正如预期的那样,只要我们尝试实例化前两个类型参数,就会爆炸:
Functor ((,) a)