【问题标题】:How does Haskell 'read' infer typesHaskell 如何“读取”推断类型
【发布时间】:2016-05-27 13:27:14
【问题描述】:

我之前了解到,在使用 Haskell 的 read 函数从 Strings 读取数字时,需要指定输出类型,如:

read "2" :: Int

除非你这样做:

read "2" + 2

Haskell 然后知道你正在尝试做加法,因此它必须是一个数字。

但是,一个特定的函数引起了我的注意,因为通过查看函数,我认为它不会编译,但确实如此,我不知道为什么。

这个实现加法、减法和乘法的逆波兰符号计算器:

solveRPN :: (Num a, Read a) => String -> a  
solveRPN xs = head . foldl foldingFunction [] . words $ xs
    where   foldingFunction (x:y:ys) "*" = (x * y):ys  
            foldingFunction (x:y:ys) "+" = (x + y):ys  
            foldingFunction (x:y:ys) "-" = (y - x):ys  
            foldingFunction xs numberString = read numberString:xs 

如果你给它一个像“2 5 +”这样的字符串,它会返回7。

这段代码的最后一行是我无法理解的。当你给这个函数“2 5 +”时,xs 列表中的第一个元素将是“2”并且累加器在时间是 [],因此它会滑过前 3 个模式,最后一个将完成它的工作,因此:

 foldingFunction [] "2" = read "2":[]

所以,我的问题是:read "2":[] 怎么没有崩溃?如果我试图在控制台中执行这个位,它会给出解析错误,因为read 不知道那个字符串应该是什么,对吧?怎么不是(read "2" :: Int):[]什么的?

【问题讨论】:

  • 查看我的回答,了解为什么 read 2 在 ghci 中给出 Parse error

标签: haskell types type-inference


【解决方案1】:

所以你要明白的是,Haskell 在编译时分配所有类型的函数,而不是运行时。此外,该函数的所有模式只有一种类型。

这意味着它将决定函数的类型是整体的,并在每种情况下都使用该决定。此外,Haskell 进行了相当繁重的类型推断(与大多数其他语言不同),因此有时会根据可能看起来与原始函数调用有点远的事物的类型来确定函数的类型。

让我们看看你的例子:

solveRPN :: (Num a, Read a) => String -> a  
solveRPN xs = head . foldl foldingFunction [] . words $ xs
    where   foldingFunction (x:y:ys) "*" = (x * y):ys  
            foldingFunction (x:y:ys) "+" = (x + y):ys  
            foldingFunction (x:y:ys) "-" = (y - x):ys  
            foldingFunction xs numberString = read numberString:xs 

首先,solveRPN 的类型被声明为String -> a

现在,看看solveRPN 的定义,第一行说:

solveRPN xs = head . foldl foldingFunction [] . words $ xs

现在,使用的名称类型有:

xs :: String    (from the type of solveRPN)
head :: [b] -> b    (I'm using different variable names for each different type)
foldl :: (c -> d -> c) -> c -> [d] -> c
words :: String -> [String]

所以solveRPN 的类型意味着我们必须有b 类型与a 类型相同,并且由于head 应用于foldl 的输出,所以我们必须有那个类型@ 987654332@ 与类型[a] 相同。现在由于foldl 的第三个参数是[String] 类型,我们知道d 类型是String,现在我们已经足够确定foldingFunction 的类型了:

foldingFunction :: [a] -> String -> [a]

【讨论】:

    【解决方案2】:

    让我们向后工作。

    1. head . foldl foldingFunction [] . words :: Num a => String -> a
    2. foldl foldingFunction [] .words :: Num a => String -> [a]
    3. foldl foldingFunction [] :: Num a => [String] -> [a]:

    由于foldl ::Foldable t => (b -> a -> b) -> b -> t a -> b,我们可以看到t a ~ [String],所以我们也可以看到foldingFunction :: Num a => [a] -> String -> [a]

    因此,read "2" : [] :: Num a => [a],与foldingFunction [] "2" 的类型相同。

    换句话说,solveRPN 提供了必要的上下文来推断 read 应该返回什么。

    【讨论】:

    • 我想知道当您在 REPL 提示符下键入 read "2" 时 ghci 正在尝试做什么。
    • read "2" :: Read a => aRead a => a 没有默认类型,产生解析错误。
    • 通常,如果 GHC 不知道您指的是哪个实例,则会说“有几个潜在的实例……”,然后列出可用的实例。在这种情况下,它实际上是在考虑某种类型的情况下处理read。但哪种类型?例如,评估read undefined 会导致未定义的错误——因此某些类型的读取函数实际上是在查看字符串。
    • haskell 既令人难以置信又令人敬畏。谢谢!顺便说一句,我什至可以省略函数类型,haskell 无论如何都可以计算出来。太棒了
    • read 是。但是在没有进一步上下文的提示下,read 不知道实际返回什么,因此 it 引发了错误。 read alwaysString 作为其参数,因此调度不是问题。一旦read 开始 运行,并且无法弄清楚如何处理它的参数,就会引发异常。
    【解决方案3】:

    如果我尝试在控制台中执行此位,它会给出解析错误,因为 read 不知道该字符串应该是什么,对吧?

    (感谢 haskell-cafe mailing list 对此的帮助。)

    您在 GHCI REPL 中遇到解析错误,因为在没有类型上下文的情况下,GHCi 将表达式计算为() 类型(空元组类型)。

    例如,这不会给出错误:

    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    Prelude> read "()"
    ()
    

    这是-XExtendedDefaultRules 选项的结果,该选项在使用 GHCi 时隐式生效。请参阅GHC User Guide 中的Type defaulting in GHCi,了解有关 GHCi 为何具有这些扩展默认规则的更多详细信息。

    要查看此选项如何影响评估,您可以在禁用该选项的情况下执行相同的实验:

    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    Prelude> :set -XNoExtendedDefaultRules
    Prelude> read "2"
    
    <interactive>:3:1:
        No instance for (Read a0) arising from a use of ‘it’
        The type variable ‘a0’ is ambiguous
        ...
    

    现在我们收到No instance for ... 错误消息。这是告诉您 GHC 不知道要返回哪种类型的消息。

    【讨论】:

      【解决方案4】:

      solveRPN 函数上的上下文 (Num a, Read a) =&gt; 足以让 read 弄清楚。在 ghci 中试试这个:

      let n = read "2" :: (Num a, Read a) => a
      :t n
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多