【问题标题】:Learning Haskell, understanding data structures学习 Haskell,了解数据结构
【发布时间】:2018-07-17 05:48:51
【问题描述】:

我实际上正在学习 Haskell,我正在尝试制作一个简单的 FizzBu​​zz Kata。

这个想法是将一个数字列表作为条目,并生成一个数字列表 |遵循以下规则的字符串:

  • fizzBu​​zz:: [StringInt] -> [StringInt]
  • 每 3 的倍数,列表中的项目应为“Fizz”
  • 每 5 的倍数,列表中的项目应为“Buzz”
  • 否则应该是数字

这里是我生成的代码:

module FizzBuzz where 

data StringInt a = Int a | String a

handleRule:: StringInt a -> a
handleRule x
    | x % 3 == 0 = StringInt "Fizz"
    | x % 5 == 0 = StringInt "Buzz"
    | otherwise = x

run:: [StringInt a] -> [a]
run = map handleRule

在尝试运行以下测试时:

module FizzBuzzSpec (spec) where

    import Test.Hspec
    import FizzBuzz

    spec :: Spec
    spec = describe "FizzBuzz#run" $ do
        it "should have displayed Fizz for number 3" $
            run [1..10]:2 `shouldBe` "Fizz"
        it "should have displayed Buzz for number 5" $
            run [1..10]:4 `shouldBe` "Buzz"

我有以下输出:

• Occurs check: cannot construct the infinite type: a ~ StringInt a
• In the expression: x
  In an equation for ‘handleRule’:
      handleRule x
        | (%) x 3 == 0 = StringInt "Fizz"
        | (%) x 5 == 0 = StringInt "Buzz"
        | otherwise = x
• Relevant bindings include
    x :: StringInt a (bound at src/FizzBuzz.hs:6:12)
    handleRule :: StringInt a -> a (bound at src/FizzBuzz.hs:6:1)

| 9 | |否则 = x

重要提示:我真的是一个 Haskell 新手,如果我在那里做出可怕的事情,请见谅。

你知道我做错了什么吗?


编辑:

我尝试过使用:

module FizzBuzz where 

type StringInt = Either Int String

handleRule:: Int -> StringInt
handleRule x
    | x `mod` 3 == 0 = Right "Fizz"
    | x `mod` 5 == 0 = Right "Buzz"
    | otherwise      = Left x

run:: [Int] -> [StringInt]
run = map handleRule

还有

module FizzBuzzSpec (spec) where

    import Test.Hspec
    import FizzBuzz

    spec::Spec
    spec = describe "FizzBuzz#run" $ do
        it "should have displayed Fizz for number 3" $
            run [1..10]:2 `shouldBe` Right "Fizz"
        it "should have displayed Buzz for number 5" $
            run [1..10]:4 `shouldBe` Right "Buzz"

但这总是抛出

    • Couldn't match expected type ‘[[StringInt]]’
                  with actual type ‘Either a0 [Char]’
    • In the second argument of ‘shouldBe’, namely ‘Right "Fizz"’
      In the second argument of ‘($)’, namely
        ‘run [1 .. 10] : 2 `shouldBe` Right "Fizz"’
      In a stmt of a 'do' block:
        it "should have displayed Fizz for number 3"
          $ run [1 .. 10] : 2 `shouldBe` Right "Fizz"
  |
9 |                         run [1..10]:2 `shouldBe` Right "Fizz"
  |                                                  ^^^^^^^^^^^^

/Users/pc/Soft/haskell/hello-stack/test/FizzBuzzSpec.hs:11:50: error:
    • Couldn't match expected type ‘[[StringInt]]’
                  with actual type ‘Either a1 [Char]’
    • In the second argument of ‘shouldBe’, namely ‘Right "Buzz"’
      In the second argument of ‘($)’, namely
        ‘run [1 .. 10] : 4 `shouldBe` Right "Buzz"’
      In a stmt of a 'do' block:
        it "should have displayed Buzz for number 5"
          $ run [1 .. 10] : 4 `shouldBe` Right "Buzz"
   |
11 |                         run [1..10]:4 `shouldBe` Right "Buzz"
   |                              

                ^^^^^^^^^^^^

无法从这里得到重点......

感谢你们的帮助

【问题讨论】:

  • 如果你想访问一个列表的元素,不要使用 :,使用 !!例如:([1..10] !! 2) == 3
  • 不要忘记有些数字是 3 和 5 的倍数。在 FizzBu​​zz 的某些变体中,处理这种情况是练习的重点。
  • 是的,谢谢:)。似乎守卫是解决这个问题的好方法。怀俄明?

标签: haskell


【解决方案1】:

规则应该是这样的:

data StringInt = SI_Str String
               | SI_Int Int

handleRule:: Int -> StringInt
handleRule x
    | x `mod` 3 == 0 = SI_Str "Fizz"
    | x `mod` 5 == 0 = SI_Str "Buzz"
    | otherwise      = SI_Int x

当您描述“数据”时,实际上是在描述数据类型。等式的左边是类型的名称,在这种情况下,类型的名称是 StringInt。在等式的右侧,您描述了构造函数(或构造函数,如本例所示)如何创建数据类型。在这种情况下,我们有 2 个构造函数可以创建 StringInt 类型的“数据”——SI_Str 构造函数和 SI_Int 构造函数。如何区分它们并访问它们的“数据内容”称为模式匹配。不过为了不破坏学习的乐趣,我建议从那里开始。

【讨论】:

    【解决方案2】:

    我假设,通过以下行:

    data StringInt a = Int a | String a
    

    您将定义一个 sum 类型。但实际上,它需要提供数据构造函数:

    data StringInt = Left Int | Right String
    

    或者,已经有 Either 类型,它有助于定义 sum 类型,因此您可以创建一个类型别名。因此,您的程序将如下所示

    type StringInt = Either Int String
    
    handleRule:: Int -> StringInt
    handleRule x
        | mod x 3 == 0 = Right "Fizz"
        | mod x 5 == 0 = Right "Buzz"
        | otherwise = Left x
    
    run:: [Int] -> [StringInt]
    run = map handleRule
    

    而且测试也要修改,因为返回值不仅仅是一个字符串,而是一个包裹在Right中的字符串:

    module FizzBuzzSpec (spec) where
    
    import Test.Hspec
    import FizzBuzz
    
    spec::Spec
    spec = describe "FizzBuzz#run" $ do
        it "should have displayed Fizz for number 3" $
            run [1..10] !! 2 `shouldBe` (Right "Fizz")
        it "should have displayed Buzz for number 5" $
            run [1..10] !! 4 `shouldBe` (Right "Buzz")
    
    main :: IO()
    main = hspec spec
    

    运行测试:

    runhaskell Tests.hs
    

    顺便说一句,你现在可以跳过 sum 类型,只要返回一个数字的字符串表示,如果它不能被35 整除:

    handleRule:: Int -> String
    handleRule x
        | mod x 3 == 0 = "Fizz"
        | mod x 5 == 0 = "Buzz"
        | otherwise = show x
    
    run:: [Int] -> [String]
    run = map handleRule
    

    【讨论】:

    • 这个想法是使用类型并尝试理解它们,我更喜欢第一个命题并尝试使其工作:)。感谢您的帮助
    • 实际上(Right "Fuzz") 中的括号很关键。如果你想从列表中获取第 n 个元素 !! 应该使用函数,而不是 :。我更新了我的答案