如何明确指定中间变量的类型?

时间:2018-09-09 02:33:55

标签: haskell

我有一个函数readConfigFromEnv,它从环境变量中读取一些环境变量。

readConfigFromEnv :: IO (Int, String)
readConfigFromEnv = do
  portStr <- getEnv "PORT"
  let port = read portStr
  secret <- getEnv "SECRET"
  return (port, secret)

即使Haskell具有出色的类型推断系统,我仍然想显式指定所需的类型,并让编译器告诉我是否有任何不合理的地方:

readConfigFromEnv :: IO (Int, String)
readConfigFromEnv = do
  portStr :: String <- getEnv "PORT"
  let port :: Int = read portStr
  secret :: String <- getEnv "SECRET"
  return (port, secret)

但是此代码无法编译。但是,编译器提示我应该添加语言扩展名ScopedTypeVariables。我试过了。

所以我的问题是:

  1. 是否添加了ScopedTypeVariables来正确地指定中间值类型的正确方法?

  2. 明确指定类型是一种好习惯吗?

  3. 使用ScopedTypeVariables有副作用吗,我应该将其添加到默认语言扩展名列表中吗?

谢谢!

1 个答案:

答案 0 :(得分:5)

如果没有ScopedTypeVariables,您可以 为声明块中引入的变量添加类型签名,例如do块中的let语句,let ... in ...表达式和{ {1}}子句。不过,您在示例中猜测的语法不太正确,您需要添加单独的类型声明,而不是在变量的定义之外(与在变量的定义的单独一行上写where的方式完全相同,而不是同时写成readConfigFromEnv :: IO (Int, String))。因此,您可以例如编写:

readConfigFromEnv :: IO (Int, String) = do

readConfigFromEnv :: IO (Int, String) readConfigFromEnv = do portStr <- getEnv "PORT" let port :: Int port = read portStr secret <- getEnv "SECRET" return (port, secret) 允许您在其他情况下无法添加的类型中添加类型注释,这有助于您声明以“声明块”以外的其他方式引入的变量的类型,例如ScopedTypeVariables绑定在do块中,lambda绑定变量和以<-模式引入的变量。

然而,case的主要目的是允许类型变量具有多个类型注释的作用域;通常,即使两个不同类型注释中的变量具有相同的名称,也假定它们是不同的变量,因此,如果没有它,则有很多类型是无法编写的。基本上,在显式ScopedTypeVariables引入的任何变量上使用ScopedTypeVariables都会成为变量的定义,因此,在其范围内使用该类型变量名实际上是对该外部作用域变量的引用,而不是新变量。例如,考虑以下代码:

forall

在这里,类型表达式{-# LANGUAGE ScopedTypeVariables #-} readTwoThings :: forall a. Read a => IO (a, a) readTwoThings = do sx <- readLn sy <- readLn let x, y :: a x = read sx y = read sy pure (x, y) 表示x, y :: ax 类型y的类型,出现在{ {1}}。

比较删除a和/或将readTwoThings换成forall a.时发生的情况。在这种情况下,您会收到错误消息,因为ScopedTypeVariablesExplicitForAll类型的ax类型的y是不同的变量

在实践中,我发现a行为通常是您真正想要发生的事情,但是因为您可以找到类似的示例,其中相同的代码取决于是否启用了readTwoThings,因此意味着两种不同的情况(您甚至可以构造示例,其中代码可以以任何一种方式进行编译并执行不同的操作!),您不能只在不了解代码如何影响类型签名的情况下盲目地将其打开。


现在,在具备所有背景知识之后,以下是我对您的问题列表的明确回答:

  1. 是的,如果要在声明的地方声明每个局部变量的类型,则需要ScopedTypeVariables

  2. 明确指定每个顶级定义的类型是一个非常普遍的建议,我想这在Haskell社区中几乎是共识。我还发现,如果将类型声明添加到ScopedTypeVariablesScopedTypeVariables绑定变量中非常复杂,则会有所帮助,但是这种做法不太普遍。在let之类的类型中添加类型根本不常见,但这确实是一个品味问题。我敢肯定,我会付出更多的努力来编写代码,而将代码修改变得比收益更令人讨厌。

    我可以看到您为什么要在非常大的函数定义(特别是很长的do块)中执行此操作,但是我通常会尝试将它们分成较小的部分(顶级或使用where子句),并且这些片段将具有类型声明。

    所以我的投票是不,这不是好习惯,但这是主观意见,不是客观事实。如果对您有帮助,请不要停止尝试。

  3. 请参阅上面的所有讨论。 portStr <- getEnv "PORT"是一个很好的扩展;默认情况下,很多人会很舒服地打开它,甚至很多人甚至希望有一天它成为基础语言的一部分。

  4. 如果您不知道它的实际作用,那么打开它绝对没有缺点。