Haskell类型错误 - 使用"打印"引起的模糊类型变量

Has*_*ase 1 debugging haskell

所以我试着写一些Haskell,我遇到了这个让我想把头撞在墙上的问题.

printGrade points = case points of            
    points | 0 <= points && points < 50 -> 5.0
    points | 50 <= points && points < 54 -> 4.0
    points | 54 <= points && points < 58 -> 3.7      
    points | 58 <= points && points < 62 -> 3.3
    points | 62 <= points && points < 66 -> 3.0
    points | 66 <= points && points < 70 -> 2.7
    points | 70 <= points && points < 74 -> 2.3
    points | 74 <= points && points < 78 -> 2.0
    points | 78 <= points && points < 82 -> 1.7
    points | 82 <= points && points < 86 -> 1.3
    points | 86 <= points && points < 100 -> 1.0

note a b c d = 
            if d > 100 || c > 20
            then return "Wrong input"
            else if a == False || b == False 
            then printGrade d
            else printGrade (c + d) 
Run Code Online (Sandbox Code Playgroud)

当我尝试运行代码时,它编译没有问题,但实际上调用该函数会带来此错误

<interactive>:91:1: error:
• Ambiguous type variable ‘m0’ arising from a use of ‘print’
  prevents the constraint ‘(Show (m0 [Char]))’ from being solved.
  Probable fix: use a type annotation to specify what ‘m0’ should be.
  These potential instances exist:
    instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
    instance (Show a, Show b) => Show (a, b) -- Defined in ‘GHC.Show’
    instance (Show a, Show b, Show c) => Show (a, b, c)
      -- Defined in ‘GHC.Show’
    ...plus 13 others
    ...plus two instances involving out-of-scope types
    (use -fprint-potential-instances to see them all)
• In a stmt of an interactive GHCi command: print it
Run Code Online (Sandbox Code Playgroud)

我知道它与"返回错误的输入"有关,但我不知道解决这个问题的方法,因为我必须在某个时候打印出一个字符串.(我试过show/print/putStrLn导致另一个错误)

Wil*_*sem 6

通常,以Haskell开头的人使用该return :: Monad m => a -> m a函数,因为在命令式世界中,几乎所有[命令式]编程语言都为return 关键字分配了几乎相同的语义.然而,在Haskell中,return是一个函数(不是关键字),它在monad的上下文中使用.虽然monad非常强大,但我建议首先看看这个结构使用之前是如何工作的return.一个提示:它并不像在命令式世界中那样有效.

我们可以删除该return功能,例如:

note a b c d = 
    if d > 100 || c > 20
        then "Wrong input"
        else if a == False || b == False 
            then printGrade d
            else printGrade (c + d)
Run Code Online (Sandbox Code Playgroud)

但后来我们得到一个新的错误:

<interactive>:20:18: error:
    • Could not deduce (Fractional [Char])
        arising from a use of ‘printGrade’
      from the context: (Num a, Ord a)
        bound by the inferred type of
                 note :: (Num a, Ord a) => Bool -> Bool -> a -> a -> [Char]
        at <interactive>:(16,1)-(21,35)
    • In the expression: printGrade d
      In the expression:
        if a == False || b == False then
            printGrade d
        else
            printGrade (c + d)
      In the expression:
        if d > 100 || c > 20 then
            "Wrong input"
        else
            if a == False || b == False then
                printGrade d
            else
                printGrade (c + d)
Run Code Online (Sandbox Code Playgroud)

现在Haskell遇到了返回类型的问题.确实printGrade返回了类似的值1.0,这是一种Fractional类型(后面可以指定的类型,但文字表明它应该是一种Fractional类型).并且它表示你也返回一个字符串("Wrong input"),并且因为a String不是一个Fractional类型,所以存在不匹配.我们可以通过调用解决这个问题show上的结果printGrade(我建议你重命名功能,因为该功能并没有打印任何东西),这样我们转换Fractional型成String,所以现在我们得到:

note a b c d = 
    if d > 100 || c > 20
        then "Wrong input"
        else if a == False || b == False 
            then show (printGrade d)
            else show (printGrade (c + d))
Run Code Online (Sandbox Code Playgroud)

现在该程序将编译,但它相当不优雅.例如,您caseprintGrade函数中使用了a ,但实际上并没有执行任何模式匹配.我们可以使用警卫,例如:

grade :: (Num a, Ord a, Fractional b) => a -> b
grade points | points < 0 = error "too small"
             | points < 50 = 5.0
             | points < 54 = 4.0
             | points < 58 = 3.7      
             | points < 62 = 3.3
             | points < 66 = 3.0
             | points < 70 = 2.7
             | points < 74 = 2.3
             | points < 78 = 2.0
             | points < 82 = 1.7
             | points < 86 = 1.3
             | points < 100 = 1.0
             | otherwise = error "too large"
Run Code Online (Sandbox Code Playgroud)

因此,我们在每种情况下使用一个防护装置,而且不需要检查该值是否例如小于0,因为在那种情况下前一个防护装置将被解雇.

我们可以对note函数使用相同的技术:使用模式和保护来匹配值:

note :: (Ord a, Num a) => Bool -> Bool -> a -> a -> String
note _ _ c d | d > 100 || c > 20 = "Wrong input"
note False False _ d = show (printGrade d)
note _ _ c d = show (printGrade (c + d))
Run Code Online (Sandbox Code Playgroud)