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

Leo*_*ang 1 haskell

我有一个函数readConfigFromEnv从环境变量中读取一些env var.

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

即使Haskell有很棒的类型推理系统,我仍然想要明确指定我期望的类型,让编译器告诉我是否有任何意义:

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

但是这段代码没有编译.但是,编译器提示我应该添加语言扩展名ScopedTypeVariables.我尝试过它.

所以我的问题是:

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

  2. 明确指定类型是一种好的做法吗?

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

谢谢!

Ben*_*Ben 5

如果没有,ScopedTypeVariables可以为声明块中引入的变量添加类型签名,例如letdo-blocks,let ... in ...expression和where子句中的语句.你在例子中猜到的语法不太正确,你需要在变量的定义之外添加一个单独的类型声明(与你readConfigFromEnv :: IO (Int, String)在其定义的单独行上写的方式完全相同,而不是在曾经as 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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

在这里,类型表达式x, y :: a表示,xy类型a是出现在类型签名readTwoThings.

比较当您删除会发生什么forall a.,和/或换出ScopedTypeVariablesExplicitForAll.在这种情况下,你会得到一个错误,因为a这是类型xy是不同的变量对a中的类型readTwoThings.

在实践中我发现,ScopedTypeVariables行为通常是你真正希望发生的事情,而是因为你可以找到这样的例子,其中相同的代码意味着这取决于是两个不同的东西ScopedTypeVariables启用(你甚至能够构建例子,其中的代码编译无论哪种方式,做不同的事情!),你不能盲目地打开它而不理解它如何影响类型签名.


现在,在建立了所有背景的情况下,这是我对您的问题列表的明确答案:

  1. 是的,如果你想声明声明它的每个局部变量的类型,你需要ScopedTypeVariables.

  2. 明确指定每个顶级定义的类型是一个非常常见的建议,我认为它几乎是Haskell社区的共识.我还发现如果它们变得非常复杂,那么将类型声明添加到letwhere绑定变量会很有帮助,但这种做法不太普遍.在类似的东西中添加类型portStr <- getEnv "PORT"根本不常见,但这确实是一个品味问题.我很确定我会发现额外的努力来编写代码和障碍,改变它比利益更烦人.

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

    所以我的投票不是不好的做法,但这是一个主观意见,而不是客观事实.不要让它阻止你尝试它,如果它似乎对你有帮助.

  3. 请参阅上面的所有讨论.ScopedTypeVaribles是一个伟大的延伸; 很多人都会非常乐于在默认情况下将其打开,许多人甚至希望它有一天成为基础语言的一部分.打开它绝对没有任何缺点,除了你不知道它实际上做了什么之外的混乱.