【问题标题】:Is there a way to pass an operator with unknown type in haskell?有没有办法在haskell中传递未知类型的运算符?
【发布时间】:2020-12-23 11:14:25
【问题描述】:

我有一个函数f op = (op 1 2, op 1.0 2.0),它需要像这样工作:

f (+)
(3, 3.0)

但如果不声明 f 的类型,它的工作方式如下:

f (+)
(3.0, 3.0)

我正在努力声明f 的类型。它应该采用一个适用于所有Num 实例的运算符。在 Haskell 中甚至有可能吗?

【问题讨论】:

  • 您是否尝试过在 GHCi 中使用:type 来检查f 的推断类型?

标签: haskell types polymorphism parametric-polymorphism rank-n-types


【解决方案1】:

问题在于,您通过将1.0 和2.0 等小数应用于小数,从而强制运算符处理Fractional 类型。您的代码类型检查是因为Fractional 是Num 的子类型(意味着Fractional 的每个实例也是Num 的实例)。

下面的 GHCi 实验应该清楚了:

Prelude> :t 0
0 :: Num p => p
Prelude> :t 0.0
0.0 :: Fractional p => p
Prelude> :t 0 + 0.0  -- Fractional taking advantage!
0 + 0.0 :: Fractional a => a

所以,如果你想让它完全在Nums 上工作,你只需要摆脱那些“.0”:

Prelude> f op = (op 1 2, op 1 2)
Prelude> f (+)
(3, 3)

然而

如果您确实需要返回元组的第二个元素是 Fractional 并且第一个元素是更一般的 Num 的行为,事情会变得有点复杂。

您传递给f 的运算符(或实际上是函数)必须与同时出现两种不同的类型。这在普通的 Haskell 中通常是不可能的,因为每个类型变量都会为每个应用程序分配一个固定的赋值 - 这意味着 (+) 需要决定它是 Float 还是 Int 或者什么。

但是,这可以更改。您需要做的是通过在 GHCi 中写入 :set -XRank2Types 或在 .hs 文件的最顶部添加 {-# LANGUAGE Rank2Types #-} 来打开 Rank2Types 扩展。这将允许您以一种更动态的方式编写f:

f :: (Num t1, Fractional t2)
  => (forall a. Num a => a -> a -> a) -> (t1, t2)
f op = (op 1 2, op 1.0 2.0)

现在类型检查器不会为op 分配任何固定类型,而是将其保留为多态以供进一步的特化。因此,它可以在同一上下文中同时应用于1 :: Int 和1.0 :: Float。

确实,

Prelude> f (+)
(3,3.0)

来自 cmets 的提示:可以放宽类型约束以使函数更通用:

f :: (Num t1, Num t2)
  => (forall a. Num a => a -> a -> a) -> (t1, t2)
f op = (op 1 2, op 1 2)

它在所有情况下都可以正常工作,以前版本的f 可以加上更多返回对的snd 可能是Int:

Prelude> f (+)
(3,3)
Prelude> f (+) :: (Int, Float)
(3,3.0)

【讨论】:

  • 您可以将Fractional 约束放松为Num。它会被一个分数论点收紧。
  • 是的,我只是想复制示例中的行为。但是很好抓!将包括它
  • +1,虽然我要补充一点,原始发帖人可能一直在寻找更专业的 rank-2 类型,例如 f :: (forall a. Num a => a -> a -> a) -> (Int, Double) 或类似的,在元组中有具体类型。
  • 您可以将 Ord 添加到 rank-2 Num 约束,甚至将其提升到 Real,同时仍然使用 Integer、Int、@987654360 @等
猜你喜欢
  • 2020-09-02
  • 2015-06-05
  • 2021-03-23
  • 2019-09-21
  • 1970-01-01
  • 2019-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多