评估,让我们在Haskell中

Leo*_*Leo 6 haskell

我目前正在学习Haskell,并试图了解如何评估类型类,以及如何letwhere工作.这段代码运行正常:

{-# LANGUAGE FlexibleInstances #-}
class Expr a where
    literal :: Integer -> a

instance Expr Integer where
    literal = id

instance Expr [Integer] where
    literal i = [i]

coerceInteger :: Integer -> Integer
coerceInteger = id

main = print $ coerceInteger (literal 100) : literal 100 -- Prints [100,100]
Run Code Online (Sandbox Code Playgroud)

但改变主要功能

main = print $ coerceInteger expr : expr
    where expr = literal 200
Run Code Online (Sandbox Code Playgroud)

导致编译器错误:

Couldn't match expected type `[Integer]' with actual type `Integer'
In the second argument of `(:)', namely `expr'
In the second argument of `($)', namely `coerceInteger expr : expr'
In the expression: print $ coerceInteger expr : expr
Run Code Online (Sandbox Code Playgroud)

我猜这是因为在第一个main方法中,它literal 100被评估两次,而在第二个例子literal 200中只评估一次,因此编译器被迫选择一个类型.

如何分解代码以避免重复自己,而不会导致此错误?我尝试使用let expr = literal 300 in ...但遇到了同样的问题.

bhe*_*ilr 9

问题是literal 200在第一个例子的两个不同的上下文中对它的解释是不同的.把它想象成

((:) :: a -> [a] -> [a])
    ((coerceInteger :: Integer -> Integer) (literal 100 :: Expr a => a))
    (literal 100 :: Expr a => a)
Run Code Online (Sandbox Code Playgroud)

基于类型,编译器确定第一个literal 100必须具有类型,Integer因为它被传递给coerceInteger,因为它必须采用类型的值Integer.这也设置了(:)现在的类型Integer -> [Integer] -> [Integer],暗示最后一个literal 100必须有类型[Integer].

在第二个示例中,您说它们都具有相同的值,因此具有相同的类型,这是不可能的,因为第二个必须是(:)要键入check 的列表.

这实际上是因为可怕的单态限制.您可以通过两种方式解决此问题:一,关闭单同态限制{-# LANGUAGE NoMonomorphismRestriction #-},或者您可以提供一个显式类型,expr使其保持一般化:

main :: IO ()
main = print $ coerceInteger expr : expr
    where
        expr :: Expr a => a
        expr = literal 100
Run Code Online (Sandbox Code Playgroud)

这些方法中的任何一种都可以工作,无论您决定做什么,我都建议始终提供类型签名以帮助避免这些问题.


事实上,一旦你添加了类型签名,你甚至可以做类似的事情

main :: IO ()
main = print $ coerceInteger expr : expr : expr : expr : expr : expr
    where
        expr :: Expr a => a
        expr = literal 100
Run Code Online (Sandbox Code Playgroud)

没有任何问题,这将打印出来[100, 100, 100, 100, 100, 100].但是coerceInteger需要初始化,因为否则编译器将不知道将其实例化为什么,因此不会有Show实例print.