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
答案 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右侧的“数据构造函数” }}符号(您可以为其指定任何名称,但通常使用与类型本身相同的名称)-应用于类型=
,FirstName
,Surname
的值和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
应用于每个元素,并将每个元素打印在新行上。