Function 您是否必须声明一个函数';s型?

Function 您是否必须声明一个函数';s型?,function,haskell,declaration,function-declaration,Function,Haskell,Declaration,Function Declaration,关于Haskell,有一件事我没有完全理解,那就是声明函数及其类型:为了清晰起见,这是您必须做的事情还是您应该做的事情?或者,在某些情况下,您需要这样做,而不是全部吗?您不需要声明任何只使用标准Haskell类型系统功能的函数的类型。Haskell 98是使用全局类型推断指定的,这意味着所有顶级绑定的类型都保证是可推断的 但是,最好为顶级定义包含类型注释,原因如下: 验证推断的类型是否符合您的期望 帮助编译器在类型不匹配时生成更好的诊断消息 最重要的是,记录您的意图并使代码更易于人类阅读

关于Haskell,有一件事我没有完全理解,那就是声明函数及其类型:为了清晰起见,这是您必须做的事情还是您应该做的事情?或者,在某些情况下,您需要这样做,而不是全部吗?

您不需要声明任何只使用标准Haskell类型系统功能的函数的类型。Haskell 98是使用全局类型推断指定的,这意味着所有顶级绑定的类型都保证是可推断的

但是,最好为顶级定义包含类型注释,原因如下:

  • 验证推断的类型是否符合您的期望

  • 帮助编译器在类型不匹配时生成更好的诊断消息

  • 最重要的是,记录您的意图并使代码更易于人类阅读

至于
where
子句中的定义,这是一个风格问题。传统的样式是省略它们,部分原因是在某些情况下,它们的类型不能在
ScopedTypeVariables
扩展之前显式编写。我认为在1998和2010标准中省略了作用域类型变量的bug,而GHC现在是事实上的标准编译器,但它仍然是一个非标准的扩展。不管怎样,在可能的情况下为非平凡代码添加注释是一种好的做法,对程序员很有帮助

在实践中,使用一些使类型推断复杂化或使其“不可判定”的语言扩展是很常见的,这意味着,至少对于任意程序来说,不可能总是推断一个类型,或者至少是唯一的“最佳”类型。但出于可用性的考虑,扩展的设计通常非常谨慎,只在实际使用时需要注释

例如,GHC(和标准Haskell)只会推断出具有顶级
forall
s的多态类型,这些多态类型通常是完全隐式的。(它们可以使用
ExplicitForAll
显式编写)如果您需要使用
RankNTypes
将多态函数作为参数传递给另一个函数,如
(forall t…)->…
,这需要一个注释来覆盖编译器的假设,即您的意思类似于
forall t。(…->…)
,或者您错误地将函数应用于不同的类型

如果扩展需要注释,则必须在何时何地包含注释的规则通常会记录在GHC用户指南等地方,并在指定功能的论文中正式指定。

简短回答:函数在“绑定”中定义,并在“类型签名”中声明其类型。绑定的类型签名在语法上总是可选的,因为该语言在任何特定情况下都不需要使用它们。(除了绑定之外,还有一些地方需要类型签名,比如在类定义或数据类型声明中,但我认为根据语言的语法,绑定不需要附带类型签名,尽管我可能忘记了一些奇怪的情况。)不需要它们的原因是,编译器通常可以(尽管不总是)在类型检查操作中计算出函数本身的类型

但是,有些程序可能无法编译,除非将类型签名添加到绑定中;有些程序可能无法编译,除非删除类型签名,所以有时需要使用它们,有时无法使用它们(至少在没有语言扩展和对附近的其他类型签名的语法进行一些更改以使用扩展的情况下是这样)

最好的做法是为每个顶级绑定包含类型签名,如果任何顶级绑定缺少关联的类型签名,GHC
-Wall
标志将警告您。其基本原理是顶级签名(1)为代码的“接口”提供文档,(2)它们的数量不会使程序员负担过重,并且(3)通常会为编译器提供足够的指导,与完全忽略类型签名相比,可以获得更好的错误消息

如果您查看几乎所有真实的Haskell源代码(例如,浏览任何关于Hackage的体面库的源代码),您将看到使用了这种约定——所有顶级绑定都有类型签名,并且类型签名在其他上下文中很少使用(在表达式或
where
let
子句中).我鼓励任何初学者在学习Haskell时在编写代码时使用相同的约定。这是一个好习惯,可以避免许多令人沮丧的错误消息

长答覆:

在Haskell中,绑定为代码块分配一个名称,类似于函数
hypo
的以下绑定:

 hypo a b = sqrt (a*a + b*b)
编译绑定(或相关绑定的集合)时,编译器会对涉及的表达式和子表达式执行类型检查操作

正是这种类型检查操作允许编译器确定上述表达式中的变量
a
必须是具有
Num t
约束的某种类型
t
(以支持
*
操作),即
a*a
的结果将是相同类型的
t
,这意味着
b*b
b
也是相同类型的
t
(因为只有两个相同类型的值可以与
+
一起添加)因此,
a*a+b*b
是类型
t
,因此
sqrt
的结果也必须是相同类型的
t
,它必须附带具有
浮动t
约束以支持
sqrt
操作
hypo :: (Floating t) => t -> t -> t
double :: (Num a) => a -> a
half x = x / 2
double x = x + x
half :: (Fractional a) => a -> a
ex1, ex2, ex3 :: Tree Int
ex1 = Leaf 1
ex2 = Node (Leaf 2) (Leaf 3)
ex3 = Node (Node (Leaf 4) (Leaf 5)) (Leaf 5)
data Binary a = Leaf a | Pair (Binary (a,a)) deriving (Show)

-- following type signature is required...
toList :: Binary a -> [a]
toList (Leaf x) = [x]
toList (Pair b) = concatMap (\(x,y) -> [x,y]) (toList b)
myLookup :: Eq a => a -> [(a,b)] -> Maybe b
myLookup k = go
  where -- go :: [(a,b)] -> Maybe b
        go ((k',v):rest) | k == k'   = Just v
                         | otherwise = go rest
        go [] = Nothing
myLookup :: forall a b. Eq a => a -> [(a,b)] -> Maybe b
myLookup k = go
  where go :: [(a,b)] -> Maybe b
        go ((k',v):rest) | k == k'   = Just v
                         | otherwise = go rest
        go [] = Nothing
possibleSymbols :: Index -> Board -> [Symbol]
possibleBoards :: Index -> Board -> [Board]
setSymbol :: Index -> Board -> Symbol -> Board
data Tree a = Leaf a | Node (Tree a) (Tree a)

leaves (Leaf x) = x
leaves (Node l r) = leaves l ++ leaves r
hasLeaf x t = elem x (leaves t)

main = do
  -- some tests
  print $ hasLeaf 1 (Leaf 1)
  print $ hasLeaf 1 (Node (Leaf 2) (Leaf 3))
Leaves.hs:12:11-28: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘hasLeaf’
      prevents the constraint ‘(Eq a0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a0’ should be.
Leaves.hs:12:19: error:
    • Ambiguous type variable ‘a0’ arising from the literal ‘1’
      prevents the constraint ‘(Num a0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a0’ should be.
Leaves.hs:12:27: error:
    • No instance for (Num [a0]) arising from the literal ‘1’
Leaves.hs:13:11-44: error:
    • Ambiguous type variable ‘a1’ arising from a use of ‘hasLeaf’
      prevents the constraint ‘(Eq a1)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a1’ should be.
Leaves.hs:13:19: error:
    • Ambiguous type variable ‘a1’ arising from the literal ‘1’
      prevents the constraint ‘(Num a1)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a1’ should be.
Leaves.hs:13:33: error:
    • No instance for (Num [a1]) arising from the literal ‘2’
leaves :: Tree a -> [a]
leaves (Leaf x) = x
leaves (Node l r) = leaves l ++ leaves r

hasLeaf :: (Eq a) => a -> Tree a -> Bool
hasLeaf x t = elem x (leaves t)
leaves (Leaf x) = x
                  ^
Leaves.hs:4:19: error:
    • Occurs check: cannot construct the infinite type: a ~ [a]
leaves (Leaf x) = [x]
{-# LANGUAGE ScopedTypeVariables #-}
hypo :: forall t. (Floating t) => t -> t -> t
hypo (a :: t) (b :: t) = sqrt (((a :: t) * (a :: t) :: t) + ((b :: t) * (b :: t) :: t) :: t) :: t
qsort :: (Ord a) => [a] -> [a]
qsort (x:xs) = qsort l ++ [x] ++ qsort r
      where -- l, r :: [a]
            l = filter (<=x) xs
            r = filter (>x)  xs
qsort [] = []