在单独的行上从类型列表输出每种类型

时间:2019-04-08 19:40:27

标签: haskell

type FirstName = String
type Surname = String
type Age = Int
type Id = Int
type Student = (FirstName, Surname, Age, Id)
testData :: [Student]
testData = [("Garry", "Queen", 10, 1),
    ("Jerry", "Bob", 11, 2),
    ("Amy", "Big", 9, 3)]

我正在尝试使用testData在新行上输出每个学生的信息。 我将如何去做?

我试过了,但是没用。

studentToString :: Student FirstName Surname Age Id -> String
studentToString (Student FirstName Surname Age Id) = FirstName ++ Surname ++ Age ++ Id

studentsToString :: [Student] -> String
studentsToString (x:xs) = putStrLn(studentToString x) ++ studentsToString xs

它给我一个错误

error: Not in scope: data constructor ‘Student’

此行

studentToString :: Student FirstName Surname Age Id -> String

2 个答案:

答案 0 :(得分:2)

在您的定义中,Con是类型别名,而不是数据构造函数,因此您无法按预期的方式使用它。就像您写了一样:

Student

那里没有太多意义。解决此问题的一种方法是将您的studentToString :: Student FirstName Surname Age Id -> String studentToString :: (FirstName, Surname, Age, Id) FirstName Surname Age Id -> String 定义转换为数据构造函数:

Student

数据构造函数的一个巧妙窍门是,它将让您对包装的值使用模式匹配,就好像它是一个元组一样:

data Student = Student FirstName Surname Age Id

studentToString :: Student -> String
...

答案 1 :(得分:1)

恐怕您的代码有很多错误。我将尝试一次遍历它们,并将其调整为可行的解决方案。

1)此行:

type Student = (FirstName, Surname, Age, Id)

声明所谓的“类型同义词”。它只是使Student对编译器的意义与四元组(FirstName, Surname, Age, Id)完全相同。相反,您可以这样做:

data Student = Student FirstName Surname Age Id

,它将使Student成为全新的类型,您可以使用函数-也称为Student,即{{1右侧的“数据构造函数” }}符号(您可以为其指定任何名称,但通常使用与类型本身相同的名称)-应用于类型=FirstNameSurname的值和Age

虽然我认为大多数经验丰富的Haskell程序员(我不是,只是对语言感兴趣并在有机会时尝试涉猎的开发人员)更喜欢Id声明,因为它更“类型安全”(不存在将data与某些相同类型的4元组混淆的意图,这本来是别的东西),我认为您拥有的类型同义词适合随意使用,我'请在下面继续。

无论如何,您的第一个问题是Student函数的类型签名。正如我所说的,studentToString本身就是一种类型,在这种情况下,它是4元组特定类型的同义词。尽管它具有您在签名中列出的4种类型的字段,但它本身就是一种类型,并且不需要-因此就不能具有-其他类型来构成有效类型。这是荒谬的。函数的输入类型为Student-即4元组。所以类型签名应该很简单:

Student

(顺便说一句,它与您给该函数提供的准确名称非常吻合。)

2)您对函数定义本身也有类似的困惑:

studentToString :: Student -> String

这将无法编译,因为GHC在错误消息中给出了您的原因。它只知道studentToString (Student FirstName Surname Age Id) = ... 作为类型的名称,为了使该定义有意义,它还必须是函数的名称,特别是构造函数。如上所述,您可以通过为Student使用data声明而不是类型同义词来实现此目的。但这不是您要做的。正如我已经说过的,您的Student类型只是一个四元组,因此您必须以一种接受四元组的方式定义函数。此外,像Student这样的大写标识符是指类型和类型构造函数,而像FirstName这样的小写标识符则是指函数和变量。因此,您应该这样做:

firstName

3)您在函数定义的右侧也有类型不匹配。 studentToString (firstName, surname, age, id) = ... 运算符用于将(相同类型的)列表放在一起形成更大的列表。用字符串执行此操作是正常的,因为Haskell字符串只是字符列表。 (出于性能关键的应用程序,或者如果您的字符串很大,出于性能原因,请不要这样做。但是对于像这样的简单学习练习,请不要担心。)

但是问题是++age的类型为id,而不是Int。您根本无法将String与它们一起使用-++不是列表,当然也不是Int的列表。在“字符串上下文”中使用时,Haskell不太可能为您将数字转换为字符串,这并不是很多种语言。它有一个非常严格的静态类型系统,根本不允许您在任何时候使用错误类型的值。 (在其他方面,由于类型类和多态函数,类型系统可以非常灵活,当您进一步了解该语言时,您会发现。但是将数字类型转换为字符串表示形式并不会为您带来好处。 )

因此,您必须显式地进行转换-Haskell为此具有一个简单的函数,称为Char。不用太技术性,它基本上将任何内容转换为可以合理转换的字符串。因此,加上我之前的所有评论,show的工作版本将是:

studentToString

4)要点-上面的代码可以很好地编译,但是出于实际目的,您可能希望将输出字符串的不同部分隔开:

studentToString :: Student -> String
studentToString (firstName, surname, age, id) = firstName ++ surname ++ show age ++ show id

5)现在转到第二个功能studentToString :: Student -> String studentToString (firstName, surname, age, id) = firstName ++ " " ++ surname ++ " " ++ show age ++ " " ++ show id ,您尝试的实现中存在根本的类型不匹配:

studentsToString

您的类型签名声明输出将为studentsToString :: [Student] -> String studentsToString (x:xs) = putStrLn(studentToString x) ++ studentsToString xs (名称也是如此!)。但是String不会 输出putStrLn!它输出类型为String的值-无需太深入地研究Haskell的类型系统以及它如何以纯方式处理I / O,我们可以说这是不一样的,正是因为它有一个“方面”。效果”(将一些输出打印到终端)。 Haskell中没有任何副作用-所有值都是“纯”的-除了类型以IO ()开头的值之外。基本上,类型IO的值是一个“动作”,执行后将在“外部世界”中执行某些操作(在这种情况下,将执行打印操作),并且自身不返回任何有用的值。 (请注意,仅在代码中执行这样的“操作”并不会执行其效果-即使在您运行最终程序或在GHCi中输出这样的值时,也会发生这种情况。)

坦率地说,您的功能有些混乱。您可以将学生列表转换为字符串(纯操作),然后尝试输出结果。或者,您可以简单地使函数使用返回值IO ()输出结果。

快点,这就是我要做的这两种方法。首先,纯函数可能是(请注意与您的错误版本有多相似):

IO ()

我真正要改变的是删除studentsToString :: [Student] -> String studentsToString [] = "" studentsToString (x:xs) = studentToString x ++ ", " ++ studentsToString xs ,以便获得“纯” putStrLn结果。我还为空列表添加了“基本情况”-如果没有此功能,您的函数将崩溃,因为每个递归步骤都作用于较短的列表,最终它会到达空列表并因String模式而失败与空列表不匹配。

最后,也是最不重要的一点,我在每个输出值之间添加了一些“填充”,因此它们不会一起运行。我选择了逗号和空格,但这是任意的。您可能更喜欢换行符-甚至其他任何东西。

完成此操作后,您只需在GHCi中运行(x:xs)即可输出结果。 (尽管putStrLn (studentsToString testData)不是必需的,但GHCi始终会打印您提供的任何表达式。)

最后,这是一种非常简单-稍微高级的方法-您可以输出测试数据,每行一位学生:

putStrLn

要完全理解这一点,您必须对Monad有所了解-相信我,它并不像听起来那样吓人-但这基本上是使“动作”(如小程序)在{ {1}}列表,将mapM_ (putStrLn . studentToString) testData 应用于每个元素,并将每个元素打印在新行上。