2016-05-27 25 views
1

我之前了解到:如何哈斯克尔“阅读”使用时,Haskell的<code>read</code>功能从<code>String</code>看一本数字时,你需要指定输出的类型,推断类型

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 +”在xs列表中的第一个元素将是“2”和累加器在时间[],因此它会漏网之鱼第3种模式和最后一个会做自己的工作,因此:

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

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

+0

查看我的答案,为什么'read 2'在ghci中给出了'Parse error'。 – ErikR

回答

2

所以你必须明白的是,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意味着我们必须具有该类型ba类型相同,并且由于head应用于foldl的输出,所以我们必须具有该类型c[a]类型相同。现在,由于第三个参数foldl[String]型的,我们知道这种类型dString,现在我们有足够的判断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应返回。

+0

我想知道在REPL提示符下键入'read“2”'时ghci正在尝试做什么。 – ErikR

+0

'read“2”::阅读a => a'。 'Read a => a'没有默认类型,产生一个分析错误。 – chepner

+0

通常情况下,如果GHC不知道您指的是哪个实例,就会说“有多个潜在实例...”,然后列出可用的实例。在这种情况下,它实际上是在考虑某种类型的'read'。但是哪种类型?例如,评估'读取未定义'会导致未定义的错误 - 所以某些类型的读取函数实际上是查看字符串。 – ErikR

0

您的solveRPN函数的上下文(Num a, Read a) =>已足以让read弄清楚。试试这在ghci:

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

If I tried to execute this bit in console it would give parse error becasue read wouldn't know what that string should be, right?

(感谢haskell-cafe mailing list关于此帮助。)

你得到在GHCI REPL解析错误,因为在不存在类型的上下文,GHCI评估就好像它是类型()(空元组类型中的表达。)

举例来说,这并没有给出一个错误:

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

这是-XExtendedDefaultRules选择使用GHCI时,这是隐含在作用的结果。请参阅Type defaulting in GHCiGHC User Guide了解更多关于为什么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不知道要返回哪种类型的消息。