【问题标题】:Applying lifted functions to tuples (of arbitrary length) in Haskell在 Haskell 中将提升函数应用于元组(任意长度)
【发布时间】:2016-12-26 06:26:22
【问题描述】:

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

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

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

:t f <$>

返回错误。作用于元组时是否可以看到f &lt;$&gt;的类型?

对这种行为有什么解释吗?

The Data.Tuple documentation 非常简短,没有提到函数是如何提升到元组的。有什么资料可以解释吗?


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

【问题讨论】:

  • 关于您的附加问题:(1):t (&lt;$&gt;) f(前缀语法)和:t (f &lt;$&gt;)(部分语法)会起作用。 (2) 使用 GHC 8,您还可以打开 TypeApplications 扩展并将 (&lt;$&gt;) 专门用于与 :t (&lt;$&gt;) @((,) _) f 配对 (3) 转到 the definition of the class 并查看其中列出的实例可以帮助解决有关(可能现有)实例(在这种情况下,您会找到 Functor ((,) a) 但对于较大的元组则没有)。
  • @duplode 谢谢,这行得通,但我仍然不明白更大的元组有什么不同。你不能以完全相同的方式将函数应用于最后一个参数吗?

标签: haskell tuples functor lifting


【解决方案1】:

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

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

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

【讨论】:

  • @DmitriZaitsev 当你构建了一个像 GHC 这样大的系统并且没有忘记任何细节时,你可以在每个问题中加上第三个问号。 =)
  • @DmitriZaitsev 与 Daniel Wagner 一样,我也怀疑这是由于较大元组实例的用例相对稀有而引起的疏忽。这是今年早些时候图书馆邮件列表中的discussion about adding the instances。阅读它表明有些人不喜欢元组实例,因为它们只使用最后一个类型参数被认为是任意且令人困惑的。 (对于它的价值,我不同意:它是语言使之成为可能的唯一实例,所以它不是任意的。)
  • 在我看来,疏忽使 n 元组为每个 n 生成不同的类型 - 因此,GHC 在技术上从未完全符合 Haskell standard - 这明确表示“元组被写入 (e1, ..., ek),并且可以是任意长度 k ≥ 2”。我想这个设计选择是出于性能原因(一个 10 元组是一个构造函数而不是 9 个构造函数) - 但普遍的看法是,在你的程序中出现一个大元组本身就是一个设计错误。跨度>
  • @user2407038 IMO,n-元组表达式(a,b,c,...,ψ,ω) 应该只是(((...((a,b),c),...),ψ),ω) 的语法糖,那么我们就不必担心元组的数量>2 .当然,这会导致相当多的额外性能损失,除非默认情况下将元组严格拆箱(这也可能是一件好事)。
  • 不管怎样,我很矛盾,即使 (a,) 函子实例是否是一个聪明的主意 - 可以说最好只使用 Writer 来访问该函子,这样可以避免任何关于“我们映射哪个元素?”的困惑和“为什么是length (10,20) ≡ 1?”。
【解决方案2】:

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

因为元组是异构的,这意味着,一般来说,尝试将b -&gt; 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 &lt;$&gt;的类型?

f &lt;$&gt; 是一个 section(部分应用的中缀运算符)。要获取它的类型,您需要用括号括起来,如下所示:

:t (f <$>)

Data.Tuple 文档非常简短,没有提到函数是如何提升到元组的。有什么资料可以解释吗?

组合子(&lt;$&gt;)(和(&lt;*&gt;))比元组更通用,您可以在Control.Applicative 模块中找到它们。

【讨论】:

  • 我知道组合器更通用,它们传递给单独的提升实现。我在这里的问题是关于元组(任何长度)的具体实现,我在检查该链接时找不到任何有关它的信息。
【解决方案3】:

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

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

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

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)

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

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 的实例。

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

【讨论】:

  • 从其他一些讨论中,包括对邮件列表线程的引用,看起来这可能不是动机。我只是一直认为是的。
  • 感谢您试图找到解释,但事实是 2 元组与另一个 monad 同构,其中 n>2 的 n 元组不是,这是证明做出一般行为的充分理由对于不同于 2 元组的 n 元组?
【解决方案4】:

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

作用于元组时是否可以看到f &lt;$&gt;的类型?

(&lt;$&gt;) 是一个多态函数:

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

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

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。如果您愿意,可以更具体:

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

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

<interactive>:1:1: error:
    • Could not deduce (Functor ((,,) Bool String))
        arising from a use of ‘<$>’
      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

【讨论】:

  • “因为有一个 Functor ((,) a) 实例在 base) 中不会发生这样的事情” - 现在我失去了这条线,我们不是在寻找为什么只处理 base 2元组排在第一位?
  • @DmitriZaitsev 我的意思是,由于对确实有一个 Functor 实例,(&lt;$&gt;) @((,) _) 的类型不需要额外的约束来要求 ((,) a) 是一个函子。对于我上面使用的“推测性约束”的一个更不可信的例子,试试 GHCi 中的:t negate []
  • 感谢您的澄清,但我仍然觉得这表明“F(x) 的行为不同于 F(y),因为 x 的行为不同于 y”。也就是说,在您的示例中,对三元组的解释(不一致地)不同于对,这会导致(混乱?)差异进一步下降。相反,所有元组的一致行为不会消除这个问题吗?
  • @DmitriZaitsev 确实不一致。我的回答只是描述现状——我相信将n元组的实例增加到足够大的n我>。 (参见 user2407038 和 leftaroundabout 的 cmets 对 Daniel Wagner 的回答,以更激进地解释“所有元组的一致行为”。)
猜你喜欢
  • 2020-06-06
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多