逸之

V1

2022/01/05阅读:41主题:红绯

haskell 101

Haskell 101: Installation, Expressions and Types

101: Installation,Expressions and Types

Welcome to part 1 of the Monday Morning Haskell Liftoff Series! If you've always wanted to try learning Haskell but were never able to find a good tutorial for it, you're in the right place! You might have zero knowledge about this awesome language right now. But after these three articles, you should know the basic concepts well enough that you can start programming on your own.

欢迎来到星期一早晨 Haskell 发射系列的第一部分!如果你一直想尝试学习 Haskell,但是从来没有找到一个好的教程,那么你来对地方了!你现在可能对这门了不起的语言一无所知。但是在这三篇文章之后,你应该对基本概念有足够的了解,这样你就可以自己开始编程了。

This article will cover a few different topics. First we'll download all the tools we need and install them. Then we'll start writing our first expressions and learning a bit about Haskell's type system. Next, we'll put the "function" in functional programming and learn how Haskell's functions are first class citizens. Finally, we'll wrap up by talking about some slightly more complicated types like lists and tuples.

这篇文章将涵盖一些不同的主题。首先,我们将下载所有需要的工具并安装它们。然后,我们将开始编写第一个表达式,并学习一些 Haskell 的类型系统。接下来,我们将在函数式编程中使用“函数”,并了解 Haskell 的函数是如何成为一等公民的。最后,我们将讨论一些稍微复杂一些的类型,如列表和元组。

If you've already read this article or are familiar with all these concepts, you should jump straight to part 2. There, we'll talk about writing our own code files, and writing more complicated functions with some advanced syntax. Make sure you also don't miss part 3, where we'll look at how easy it is to create our own data types!

如果你已经读过这篇文章或者熟悉所有这些概念,你应该直接跳到第二部分。在这里,我们将讨论编写我们自己的代码文件,以及使用一些高级语法编写更复杂的函数。请确保您也不要错过第3部分,在这里我们将看到创建我们自己的数据类型是多么容易!

This series also comes with a companion Github repository! This repository will allow you to work with some of the code samples from these articles. In this first part though, we'll mostly be working within GHCI, rather than source code files.

这个系列还附带了一个相关的 Github 库!这个存储库将允许您使用这些文章中的一些代码示例。不过,在第一部分中,我们将主要使用 GHCI,而不是源代码文件。

Finally, once you're done with these, make sure to check out our Beginner's Checklist! This will provide you with a review of the series and guide you to many more resources for improving your Haskell!

最后,一旦你完成了这些,一定要检查我们的初学者清单!这将为您提供一个系列的回顾,并指导您更多的资源,以提高您的 Haskell!

Installation

安装

If you have not touched any Haskell whatsoever, the first step is to download the Haskell Platform. Download the latest version for your operating system from and follow the on-screen prompts.

如果你还没有接触过任何 Haskell,那么第一步就是下载 Haskell 平台。从中下载您的操作系统的最新版本,并按照屏幕上的提示操作。

The platform currently includes 4 main items. First, it has GHC, the most commonly used Haskell compiler. The compiler is what takes your code and turns it into something the computer can run. Second, it has GHCI, an interpreter for the Haskell language. It allows you to enter expressions and test some calculations without having to put your code in a separate file.

该平台目前包括4个主要项目。首先,它使用了最常用的 Haskell 编译器 GHC。编译器就是把你的代码转换成计算机可以运行的东西。其次,它有 GHCI,一个 Haskell 语言的解释器。它允许您输入表达式和测试一些计算,而不必将代码放在单独的文件中。

Third, it includes "Cabal", a dependency manager for Haskell libraries. This will allow you to download code that other people have written and use it in your own projects. Finally, there is the "Stack" tool. This adds another layer on top of Cabal and makes it easier for you to download packages that won't cause conflicts. If you want more details about Stack, you can look at our Stack Mini-Course!

第三,它包括“ Cabal”,这是 Haskell 库的一个依赖管理器。这将允许您下载其他人编写的代码,并在您自己的项目中使用它。最后,还有“堆栈”工具。这在阴谋集团之上增加了另一层,使你更容易下载不会引起冲突的软件包。如果你想了解更多关于 Stack 的细节,你可以看看我们的 Stack Mini-Course!

To test if you've installed everything correctly, see if the command ghci works in your terminal and brings up the interpreter. We'll spend the rest of this lesson in GHCI trying out some of Haskell's basic language features.

要测试是否正确安装了所有程序,请查看命令 ghci 是否在终端中工作并启动解释器。在接下来的课程中,我们将尝试 Haskell 的一些基本语言特性。

Expressions

表达方式

Now that you've installed everything, let's get down to the basics! The most fundamental thing about Haskell is that everything you write is an expression. All your program actually consists of is evaluating these expressions. Let's start by examining some of the most basic expressions we can make. Try entering the following 6 expressions in the interpreter. When you press enter each time, the interpreter should simply echo back what you typed.

既然您已经安装了所有的程序,那么让我们开始基础工作吧!关于 Haskell 最基本的事情是你写的每一个东西都是一个表达式。你的程序实际上包含的就是计算这些表达式。让我们先来看看我们能做出的一些最基本的表达式。尝试在解释器中输入以下6个表达式。当您每次按回车键时,解释器应该简单地回显您输入的内容。

>> True
True
>> False
False
>> 5
5
>> 5.5
5.5
>> 'a'
'a'
>> "Hello"
"Hello

With this series of expressions, we cover most of the basic "types" of the language. If you've done any programming in the past, these basic types should be pretty familiar to you. The first two are "boolean" expressions. True and False are actually the only values of this type. We can also make expressions out of numbers, both whole and decimal. Finally, we can make expressions representing individual characters as well as whole words, which we call strings.

通过这一系列表达式,我们涵盖了语言的大多数基本“类型”。如果您以前做过任何编程,那么这些基本类型对您来说应该非常熟悉。前两个是“布尔”表达式。True 和 False 实际上是此类型的唯一值。我们也可以用整数和十进制的数字表达式。最后,我们可以制作表示单个字符以及整个单词的表达式,我们称之为字符串。

In the interpreter, we can assign expressions to names by using "let" and an equals sign. This saves the expression under that name so we can refer to it later.

在解释器中,我们可以通过使用“ let”和一个等号将表达式赋给名称。这将表达式保存在该名称下,以便以后引用。

>> let firstString = "Hello"
>> firstString
"Hello"

Types

类型

Now, one of the cool things about Haskell is that every expression has a type. Let's examine the types of the basic expressions we entered above. We'll see that the ideas we were talking about are formalized in the language itself. You can examine the type of any expression by using the :t command:

现在,Haskell 最酷的地方之一就是每个表达式都有一个类型。让我们检查一下上面输入的基本表达式的类型。我们将看到,我们刚才谈论的想法是正式的语言本身。您可以使用: t 命令检查任何表达式的类型:

>> :t True
True :: Bool
>> :t False
False :: Bool
>> :t 5
5 :: Num t => t
>> :t 5.5
5.5 :: Fractional t => t
>> :t 'a'
'a' :: Char
>> :t "Hello"
"Hello" :: [Char]

A couple of these are straightforward, but a couple are kind've weird. Isn't that last one just a String? Well yes. You can use the term String in your code. Under the hood though, Haskell thinks of Strings as a list of characters, which is what [Char] means. We'll get to that later. True and False correspond to the Bool type, just like we'd expect. The 'a' character is a single Char. Our numbers are a little more complicated. Ignore the Num and Fractional words for now. This is how we can refer to a whole range of types. We'll think of whole numbers as having type Int, and floating point numbers as having type Double. We can explicitly assign the type we like like so:

其中一些是直截了当的,但有一些则有点奇怪。最后一个不就是一根绳子吗?是的。您可以在代码中使用术语 String。不过,Haskell 认为字符串是一个字符列表,这就是[ Char ]的意思。我们稍后再谈这个。正如我们所料,True 和 False 符合 Bool 类型。字符是一个字符。我们的数据有点复杂。现在忽略 Num 和小数字。这就是我们如何引用整个范围的类型。我们认为整数是 Int 类型,浮点数是 Double 类型。我们可以像下面这样显式地分配我们喜欢的类型:

>> let a = 5 :: Int
>> :t a
a :: Int
>> let b = 5.5 :: Double
>> :t b
b :: Double

We can already see something pretty cool about Haskell. It can infer some information about the types of our expressions just from their form. We generally don't need to explicitly give a type to each of our expressions like we do in a language like Java or C++ (think int a = ...).

我们已经可以看到 Haskell 非常酷的一面。它可以从表达式的形式中推断出一些关于表达式类型的信息。我们通常不需要像在 Java 或 c + + 这样的语言中那样为每个表达式显式地赋予一个类型(想象一下 int a = ...)。

Functions

功能

Let's start doing some computations with our new expressions and see what we come up with. We can start with some basic mathematical calculations:

让我们开始用新的表达式做一些计算,看看我们能得到什么。我们可以从一些基本的数学计算开始:

>> 4 + 5
9
>> 10 - 6
4
>> 3 * 5
15
>> 3.3 * 4
13.2
>> (3.3 :: Double) * (4 :: Int)

Once again, we've been thrown a curveball by this last example! By the time we're done with this section, we'll understand what's going on here and how we can fix it. Now, an important note here is that we said everything in Haskell is an expression, and every expression has a type. So logically, we should be able to ask and determine the types of these different expressions. And we certainly can! We just have to put parentheses around them to make sure the type command knows to include the whole expression.

再一次,我们被这最后一个例子抛出了一个曲线球!当我们完成这一部分的时候,我们就会明白这里发生了什么,以及我们如何修复它。现在,一个重要的注意是,我们说在哈斯克尔的一切都是一个表达式,每个表达式都有一个类型。所以逻辑上,我们应该能够询问并确定这些不同表达式的类型。我们当然可以!我们只需在它们周围加上括号,以确保 type 命令知道要包含整个表达式。

>> let a = 4 :: Int
>> let b = 5 :: Int
>> a + b
9
>> :t (a + b)
(a + b) :: Int

This makes perfect sense, since the result, 9, seems like an Int as well. But this is where it gets cool. The + operator, even on its own without the numbers, is still an expression! It is our first example of a function, or an expression that takes arguments. When we represent it by itself, we put parentheses around it.

这很有道理,因为结果9看起来也像一个整型数。但这就是最酷的地方。即使 + 操作符本身没有数字,它仍然是一个表达式!这是函数的第一个例子,或者说是接受参数的表达式。当我们用它自己表示时,我们在它周围加上括号。

>> :t (+)
(+) :: Num a => a -> a -> a

This is our first example of representing the type of a function. The important part is a -> a -> a. This tells us that (+) is a function taking two arguments, which must have the same type. It will then give us a result that has the same type as the inputs. The Num a portion specifies we have to use numeric types, like ints and floats. We can't for instance do:

这是我们表示函数类型的第一个例子。重要的部分是 a-> a-> a。这告诉我们(+)是一个带有两个参数的函数,它们必须具有相同的类型。然后它会给我们一个与输入相同类型的结果。Numa 部分指定我们必须使用数值类型,比如 int 和 floats。例如,我们不能这样做:

>> "Hello " + "World"

But this explains why we were not able to add an int and a double together. The function demands we use the same type for both arguments. To fix this, we'd have to use a different function to change the type of one of them to match the other. Or we could let type inference figure it out, like we had in the example right above it. But we're getting ahead of ourselves. Let's focus on the semantics of how we "apply" this function.

但是这解释了为什么我们不能把 int 和 double 加在一起。该函数要求我们对两个参数使用相同的类型。为了解决这个问题,我们必须使用一个不同的函数来更改其中一个函数的类型以匹配另一个函数。或者我们可以让类型推断来解决这个问题,就像我们在上面的例子中所做的那样。但我们有点操之过急了。让我们关注如何“应用”这个函数的语义。

In general, we "apply" functions by placing the arguments after the functions. The + function is special in that we can use it as an operator in between its arguments. If we want though, we can use parentheses around it and treat it like a normal function. In this case, both arguments need to come after it:

一般来说,我们通过在函数后面放置参数来“应用”函数。函数的特殊之处在于,我们可以在它的参数之间使用它作为运算符。但是,如果我们愿意,我们可以在它周围使用括号,并把它当作一个正常的函数来对待。在这种情况下,两个论点都需要紧随其后:

>> (+) 4 5
9

What's really cool about functions is that we don't have to apply all the arguments at once!. We can take the same addition operator and apply it to only one of the numbers. This is called partial application.

函数真正酷的地方在于,我们不必一次应用所有的参数!.我们可以使用相同的加法运算符,并且只对其中一个数应用它。这就是所谓的部分应用程序。

>> let a = 4 :: Int
>> :t (a +)
(a +) :: Int -> Int

On its own, (+) was an operator that could take two arguments. Now we've applied one argument (an Int) to it. The resulting expression is now a function that takes one remaining argument. Furthermore, since the first argument we passed was an Int, the second one must also be an Int. We can assign our partial function to an expression using let and then apply it to a second argument!

(+)本身就是一个可以接受两个参数的运算符。现在我们对它应用了一个参数(Int)。结果表达式现在是一个接受一个剩余参数的函数。此外,因为我们传递的第一个参数是一个 Int,所以第二个参数也必须是一个 Int。我们可以使用 let 将部分函数赋给一个表达式,然后将它应用到第二个参数!

>> let f = (4 +)
>> f 5
9

Let's experiment a bit with some more operators, this time on boolean values. These are important because they'll let you build more advanced conditions when you start writing functions. There are three main operators, which work the way you would expect for other languages: And, Or, and Not. The first two take two boolean parameters and return a boolean, and the last takes a single boolean and returns it.

让我们试验一下更多的操作符,这次是布尔值。这些都很重要,因为当你开始编写函数时,它们可以让你构建更高级的条件。有三个主要的操作符,它们的工作方式与您期望的其他语言相同: And、 Or 和 Not。前两个接受两个布尔参数并返回一个布尔值,最后一个接受一个布尔值并返回它。

>> :t (&&)
(&&) :: Bool -> Bool -> Bool
>> :t (||)
(||) :: Bool -> Bool -> Bool
>> :t not
not :: Bool -> Bool

And we can see some simple examples of their behavior:

我们可以看到一些他们行为的简单例子:

>> True && False
False
>> True && True
True
>> False || True
True
>> not True
False

One final function we'll want to know about is the equality function. This can take two arguments of almost any type and determine if they are equal.

我们想知道的最后一个函数是相等函数。这可以接受几乎任何类型的两个参数,并确定它们是否相等。

>> 5 == 5
True
>> 4.3 == 4.8
False
>> True == False
False
>> "Hello" == "Hello"
True

Lists

名单

Now we're going to broaden our horizons a bit and discuss some more advanced types. The first concept we'll look at is lists. These are a series of values that have the same type. We denote lists by using square brackets. A list can have zero elements, and we call this the empty list.

现在我们要开阔一下视野,讨论一些更高级的类型。我们首先看到的概念是列表。这些是具有相同类型的一系列值。我们用方括号表示列表。一个列表可以有零个元素,我们称之为空列表。

>> :t [1,2,3,4,7]
[1,2,3,4,7] :: Num t -> [t]
>> :t [True, False, True]
[True, False, True] :: [Bool]
>> :t ["Hello", True]
Error! (these aren't the same type!)
>> :t []
[] :: [t]

Notice the error in the third example! Lists can't have different types of elements! Remember we said earlier that a string is really just a list of characters. We can test how this looks:

注意第三个例子中的错误!列表不能有不同类型的元素!记得我们之前说过,字符串实际上只是一个字符列表。我们可以测试一下这看起来怎么样:

>> "Hello" == ['H', 'e', 'l', 'l', 'o']
True

Lists can be combined using the (++) operator. Because strings are lists, this allows you to combine strings like you can in other languages.

可以使用(+ +)运算符组合列表。因为字符串是列表,这允许您像在其他语言中那样组合字符串。

>> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]
>> "Hello " ++ "World"
"Hello World"

Lists also have two functions that are specially designed to get certain elements out. We can use the head function to get the first element of the list. Similarly, we can use the tail function to get all the elements of a list EXCEPT the head element.

列表还有两个特别设计的函数,用于去除某些元素。我们可以使用 head 函数来获得列表的第一个元素。类似地,我们可以使用 tail 函数来获取除了 head 元素之外的列表的所有元素。

>> head [1,2,3]
1
>> tail [True, False, False]
[False, False]
>> tail [3]
[]

Beware though! Calling either of these functions on an empty list will result in an error!

但是要注意,在一个空列表上调用这些函数中的任何一个都会导致错误!

>> head []
Error!
>> tail []
Error!

Tuples

元组

So now that we know about lists, you might be wondering if there's any way we can combine elements that do not have the same type. In fact there is! These are called tuples! You can make tuples that have any number of elements, each with different types. Tuples are denoted using parentheses:

既然我们已经知道了列表,您可能想知道是否有什么方法可以组合不具有相同类型的元素。事实上是有的!这些被称为元组!您可以使元组具有任意数量的元素,每个元素具有不同的类型。元组用括号表示:

>> :t (1 :: Int, "Hello", True)
(1 :: Int, "Hello", True) :: (Int, [Char], Bool)
>> :t (1 :: Int, 2 :: Int)
(1 :: Int, 2 :: Int) :: (Int, Int)

Each tuple we make has its own type based on the constituent types within the tuple. This means the following are all different types, even though some of them share the same types of elements, or have the same length:

我们创建的每个元组基于元组中的组成类型都有自己的类型。这意味着以下是所有不同类型的元素,尽管它们中的一些共享相同类型的元素,或者具有相同的长度:

>> :t (1 :: Int, 2 :: Int)
(1 :: Int, 2 :: Int) :: (Int, Int)
>> :t (2 :: Int, 3 :: Int, 4 :: Int)
(2 :: Int, 3 :: Int, 4 :: Int) :: (Int, Int, Int)
>> :t ("Hi", "Bye", "Good")
([Char], [Char], [Char])

Since tuples are expressions like any other, we can make lists out of them! However, we cannot combine tuples of different types in the same list.

因为元组和其他表达式一样,所以我们可以用它们制作列表!但是,我们不能在同一个列表中组合不同类型的元组。

>> :t [(1 :: Int, 2 :: Int), (3 :: Int, 4 :: Int)]
[(1 :: Int, 2 :: Int), (3 :: Int, 4 :: Int)] :: [(Int, Int)]
>> :t [(True, False, True), (False, False, False)]
[(Bool, Bool, Bool)]
>> :t [(1,2), (1,2,3)]
Error

Conclusion

总结

This concludes the first section of our liftoff series. Look at how much we've gone through in one article! We installed the Haskell platform and started experimenting with the GHCI, a code interpreter. We also learned about expressions, types, and functions, which are the building blocks of Haskell.

这就是我们发射系列的第一部分。看看我们在一篇文章中经历了多少!我们安装了 Haskell 平台,并开始测试代码解释器 GHCI。我们还学习了表达式、类型和函数,它们是 Haskell 的构建块。

In part 2 of this series, we'll start writing our own code in Haskell source files and learn more about the language syntax. We'll examine how we can print output to a user of our program, as well as get their input. We'll also start writing our own functions and look at the various ways we have to specify function behavior.

在本系列的第2部分中,我们将开始在 Haskell 源文件中编写自己的代码,并学习更多关于语言语法的知识。我们将研究如何将输出打印给程序的用户,以及如何获得他们的输入。我们还将开始编写自己的函数,并研究指定函数行为的各种方法。

Then in part 3, we'll start building our own data types. We'll see how simple Haskell's algebraic data types are, and how type synonyms and newtypes can give us additional control over the style of our code.

然后在第3部分,我们将开始构建自己的数据类型。我们将看到 Haskell 的代数数据类型有多么简单,类型 synonyms 和 newtypes 如何为我们提供对代码样式的额外控制。

If you want some more resources for learning introductory Haskell, check out our Beginner's Checklist! It'll tell you about some cool tools and other great beginner resources! You'll also get a great summary of the key points of this Liftoff series!

如果你想要更多的资源来学习 Haskell,请参考我们的初学者清单!它会告诉你一些很酷的工具和其他很棒的初学者资源!你也会得到一个很好的概括,这个关键点的升空系列!

And if you're itching to do some more advanced Haskell work, be sure to check out our Stack Mini Course! This will walk you through creating a basic Haskell program with the Stack utility! You'll be able to seamlessly incorporate code libraries from online and build your own applications piece by piece!

如果你渴望做一些更高级的 Haskell 工作,一定要看看我们的 Stack Mini Course!这将指导您使用堆栈实用程序创建基本的 Haskell 程序!您将能够从在线无缝地合并代码库,并逐片构建您自己的应用程序!

分类:

后端

标签:

后端

作者介绍

逸之
V1