将提升函数应用于Haskell

时间:2016-12-26 06:26:22

标签: haskell tuples functor lifting

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

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

另一方面,任何其他长度<2>的元组都会返回错误。 还

:t f <$>

返回错误。在对元组进行操作时是否可以看到f <$>的类型?

这种行为有什么解释吗?

The Data.Tuple documentation非常简短,没有提到如何将函数提升为元组。有没有消息来源解释它?

更新。关于2元组的问题的一部分与this answer有关,但是,关于多个长度元组的上述问题没有得到解决。

4 个答案:

答案 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。 (也就是说,WriterWriter是同构的。)

例如,给定一个在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扩展来专门化多态函数,方法是提供部分或全部类型变量的实例化(在本例中为fa和{ {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)