【问题标题】:Haskell: Resolving dispatch to correct typeclassHaskell:解决派发以正确的类型类
【发布时间】:2011-12-28 07:32:36
【问题描述】:

我对 Haskell 如何分派到类型类中的正确函数有点困惑。这是隐式完成的,还是与 Haskell 如何推断某物的类型有关?例如,考虑以下内容:

instance Monad Parser where
     return a = Parser (\ cs -> [(a, cs)])

如果我这样做:

return something >>= \x -> -- Returning a parser!

haskell 是否使用绑定的右侧猜测哪个返回调用?它如何知道调用 Parser 返回而不是其他一些?

编辑:

好的,所以这实际上给我提出了更多问题。我知道 Haskell 如何通过解决类型声明来推断“返回”应该是什么。但是,如果是完全模棱两可的陈述呢?

例如,如果我打开解释器并输入return 1 会怎样?那么它怎么知道调用哪个返回呢?

【问题讨论】:

  • GHCi 语句对于某些 a 首先被假定为 IO a 类型,然后执行(如果失败,它会尝试 print it,其中 it 是你输入的表达式),所以由于return 1 适合(Num a) => IO a 类型,所以它被执行; Num 实例是使用 Haskell 的默认机制选择的(它将是整数)。
  • let x :: (Monad m, Num n) => m n; x = return 1 看看,x 是一个多态值!它可以在任何情况下使用,只要情况需要一个类型为m n的值并符合约束条件:mMonadnNumx 可能是 [1.0] :: [Double], Just 1 :: Maybe Int ... 各种各样的东西!

标签: haskell types typeclass


【解决方案1】:

没错——Haskell 可以根据返回类型进行调度。事实上,由于 Haskell 是一种函数式语言,因此没有严格的“返回类型”概念。比如Int中的(Bool -> Int) -> String是返回类型吗?

那么,这是怎么发生的呢?首先,Haskell 推断出表达式的类型。它的形式是

forall x1 x2 [...] . Ctx => t

其中Ctx 是上下文,格式为(Class1 vars1, Class2 vars2, ...)

现在,在大多数情况下(如您的情况),上下文中出现的所有类型变量也出现在类型t 中。因此,一旦将这些变量实例化为具体类型,就可以解析重载方法。

具体来说,在您的情况下,Haskell 知道绑定运算符 >>= 具有类型

m a -> (a -> m b) -> m b.

由于某些b 的右操作数的类型为Parser b,因此m 变量必须为Parser,因此return 的类型为a -> Parser a

在某些情况下,上下文中可能存在t 中未出现的变量。

例如

show (read "blah")

具有String 类型,但要对其进行评估,它需要选择ReadShow 类的一些实例。

在这种情况下,要么使用默认机制解决,要么报告错误。在后一种情况下,您可以通过提供显式签名来修复它。

【讨论】:

  • In fact, since Haskell is a functional language, there is no strict notion of a "return type". -- 什么?考虑一个函数类型t -> uu 像 T 一样适合“返回类型”。每个函数 f :: t -> u 的返回值(或结果,如果您更喜欢该术语)的类型为 u。至于你的例子:(Bool -> Int) -> String 中的Int 确实是一个返回类型,即作为高阶函数参数的函数的返回类型。 String 是上述高阶函数的返回类型。
  • @delnan:它是该内部函数的返回类型,但对于整个事物而言,它只是其参数类型的一部分。另一个例子:aFoo a -> b 中的返回类型吗?您需要知道如何实现 Foo 来回答这个问题。如果是抽象类型呢?
  • Foo a -> b的返回类型是b,句号。如果您对函数参数了解更多,则可以全部递归并添加参数是否为函数以及 its 返回类型是什么。但这只是额外的功劳,在考虑顶级函数的结果类型时无关紧要。不要让高阶函数混淆你:每个函数只有一个参数(类型)和一个结果(类型)。两者都可能是一个函数,但对于 Haskell 来说,函数只是常规值。
  • @delnan:无论如何,我的意思是在这种情况下没有理由对返回类型进行不同的处理。它只是整个类型的一部分。
【解决方案2】:

“返回 1”由 ghc 和 ghci 处理,见 Haskell 命令“默认”。

Haskell 98 Report here 正式记录了这种便利性。您可以覆盖 Prelude 中的“默认”声明是:

default (Integer,Double)

“默认”的目的是为了在最常见的情况下生成类型为“Num”实例的类型不需要显式类型注释。上述“默认”声明的含义是让编译器先尝试 Integer 类型,然后再尝试 Double 类型。选择允许正确编译的第一种类型。这可以通过编写完全关闭:

default ()

或者您可以使用自己的编译器提示创建一个列表,但标准限制您仅列出 Prelude 中定义的类型或编译器明确允许的类型。因此,不能保证您有权在“默认”声明中列出新数据类型。

使用 GHC,默认规则位于 section 2.4.5 of the user manual。 GHC 始终允许您在“默认”中使用新的“Num”类型。此外,通过打开语言扩展“ExtendedDefaultRules”,“default”声明可以为“Show”、“Eq”和“Ord”实例提供默认值。 Num 的一个简单示例:

module Main where

data K = K deriving (Show,Eq,Ord)

instance Num K where fromInteger _ = K

default (K)

main = print 0

在 ghci 中运行时会打印字母“K”

【讨论】:

    【解决方案3】:

    例如,如果我打开解释器并输入 return 1 会怎样?

    你试过了吗?

    它怎么知道调用哪个返回呢?

    其实不然。 在这种情况下,您需要指定类型以便它可以做正确的事情。例如:

    return 1 :: Maybe Int
    

    【讨论】:

    • 能不能告诉我投反对票的原因?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-08
    • 2017-08-13
    • 1970-01-01
    • 1970-01-01
    • 2023-02-03
    相关资源
    最近更新 更多