如何在 haskell 中正确键入可选参数

How to properly type optional arguments in haskell

我正在尝试编写一个函数来递归地确定解决 collat​​z 猜想所需的步数。我希望函数输入只是该系列的起始输入编号,并在第一次迭代中添加一个额外的计数器变量。等效的 JS 看起来像:

const collatz = (v, count) => {
  if (!count) return collatz(v,0)
  if (v === 1) return count
  if (v % 2 === 0) return collatz(v/2, count + 1)
  return collatz(v*3+1, count + 1)
}

我的Haskell代码:

module CollatzConjecture (collatz) where
collatz :: Integer -> Maybe Integer -> Maybe Integer
collatz v () = collatz v 0
collatz 1 count = Just count
collatz v count 
  | even v = collatz (div v 2) next
  | otherwise = collatz (v * 3 + 1) next
  where next = count + 1

但是编译器抱怨

    • Couldn't match type ‘Maybe Integer’ with ‘Integer’
      Expected type: Maybe Integer
        Actual type: Maybe (Maybe Integer)
    • In the expression: Just count
      In an equation for ‘collatz’: collatz 1 count = Just count
  |
4 | collatz 1 count = Just count
  |                   ^^^^^^^^^^

我的思路哪里出了问题?

不要使用仅用于实现细节的额外参数污染函数的 public API。相反,让您的 public 1 参数函数委托给私有 2 参数函数。如果你想不出更好的名字,像这样的内部函数通常被命名为 go

collatz :: Integer -> Integer
collatz = go 0
  where go count 1 = count
        go count v | even v = next $ div v 2
                   | otherwise = next $ v * 3 + 1
          where next = go $ count + 1

我还做了一些其他的改进:

  1. 没有理由 return 一个 Maybe Integer 当你从不 return Nothing.
  2. 我交换了参数顺序(count 在前),以便更方便地部分应用 gonext
  3. 我没有将您的辅助变量 next 定义为整数,而是将其定义为 go 的部分应用,这样您的两个案例只需重复 next , 而不是 go (...) next.

如果您根本不自己计数,整个功能也会简单得多。我不会立即建议这样做,因为与您最初的尝试相比,它是相当难以辨认的,但有经验的 Haskeller 会更像这样写:

collatz :: Integer -> Int
collatz = length . takeWhile (/= 1) . iterate step
  where step n | even n = n `div` 2
               | otherwise = 3 * n + 1

你的想法错误是 Maybe 不是关键字,也不是对编译器的某种提示。它是一个类型构造函数。 Maybe Integer 是具体的特定数据类型。它用于表示可选性的一般概念,但具体值(这种类型) .

Haskell 中的每个值都有 一个 类型。它不能是 Int()。 Haskell的求和类型是tagged unions.

一个类型就是一个类型——一个类型。根据数据类型,Maybe Int 类型的值被写入 Just i(其中 i :: Int,即 i 具有类型 Int)或 Nothing Maybe

的定义
data Maybe a  =  Nothing
              |  Just a 

从概念上讲,Maybe IntEither () Int 相同——它确实表示 可能是 Int 或什么都没有():

data Either a b  =  Left a
                 |  Right b

所以 Just i 就像 Right i,而 Nothing 就像 Left ()

但不能单独使用 i() -- 它们必须使用适当的数据构造器 标记,此处为 RightLeft.