Statistics
3
Views
0
Downloads
0
Donations
Support
Share
Uploader

高宏飞

Shared on 2026-03-13
Support Statistics
¥.00 · 0times
Text Preview (First 20 pages)
Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

(This page has no text content)
Haskell The Ultimate Beginner's Guide to Learn Haskell Programming Step by Step 1st edition 2020 By Claudia Alves
"Programming isn't about what you know; it's about what you can figure out.” - Chris Pine
memlnc INTRODUCTION WHAT MAKES HASKELL SPECIAL? HOW IS HASKELL USED? WHAT DO YOU NEED TO START STARTING READY, SET, GO! THE FIRST SMALL FUNCTIONS AN INTRODUCTION TO THE LISTS TEXAS RANGES I'M AN INTENSIONAL LIST TUPLES CHAPTER I TYPES AND TYPE CLASSES BELIEVE IN THE TYPE TYPE VARIABLES TYPE CLASSES STEP BY STEP (1ST PART) CHAPTER II THE SYNTAX OF FUNCTIONS PATTERN ADJUSTMENT
GUARDIANS, GUARDIANS! WHERE? LET IT BE CASE EXPRESSIONS CHAPTER III RECURSION HELLO RECURSION! THE IMPRESSIVE MAXIMUM A FEW MORE RECURSIVE FUNCTIONS QUICKSORT! THINKING RECURSIVELY CHAPTER IV HIGHER ORDER FUNCTIONS CURRIFIED FUNCTIONS HIGHER ORDER IN YOUR ORDER ASSOCIATIONS AND FILTERS LAMBDAS FOLDS AND ORIGAMI APPLICATION OF FUNCTIONS WITH $ COMPOSITION OF FUNCTIONS CHAPTER V
MODULES LOADING MODULES DATA.LIST DATA.CHAR DATA.MAP DATA.SET CREATING OUR OWN MODULES CHAPTER VI CREATING OUR OWN TYPES AND TYPE CLASSES INTRODUCTION TO ALGEBRAIC DATA TYPES REGISTRATION SYNTAX TYPE PARAMETERS DERIVED INSTANCES TYPE SYNONYMS RECURSIVE DATA STRUCTURES TYPE CLASSES STEP BY STEP (2ND PART) THE YES-NO TYPE CLASS THE FUNCTOR TYPE CLASS FAMILIES AND MARTIAL ARTS CHAPTER VII INPUT AND OUTPUT
HELLO WORLD! FILES AND DATA STREAMS COMMAND LINE PARAMETERS RANDOMNESS BYTE STRINGS EXCEPTIONS
Introduction A balance of flexible and inflexible qualities make Haskell a fascinating programming language to learn and use. First, the Haskell programming language is not named after Eddie Haskell, the sneaky double-dealing neighbor kid in the ancient TV sitcom, Leave It To Beaver. Haskell is named after Haskell Brooks Curry, an American mathematician and logician. If you don't know, logicians create models to describe and define human reasoning, for example, problems in mathematics, computer science, and philosophy. Haskell’s main work was in combinatory logic, a notation designed to eliminate the need for variables in mathematical logic. Combinatory logic captures many key features of computation and, as a result, is useful in computer science. Haskell has three programming languages named after him: Haskell, Brooks, and Curry. Haskell the language is built around functions, useful blocks of code that do specific tasks. They are called and used only when needed. Another interesting feature of functional languages like Haskell: functions are treated as values like integers (numbers) and strings. You can add a function to another function the way you can add an integer to an integer, 1 + 1 or 35 + 53. Perhaps the best way to describe this quality is a spreadsheet: in a cell in the spreadsheet, you can add numbers as well as a combination of functions to work on numbers. For example, you might specify each number in cells 1-10 be added up as a sum. In Excel, at least, you also can use SUMIF to look for a pattern in cells 1-10 and, if the pattern is found, perform an action on any cells with the pattern.
What Makes Haskell Special? Technically, Haskell is a general-purpose functional programming language with non-strict semantics and strong static typing. The primary control construct is the function. (Say that fast ten times!) Here's what it means: - Every language has a strategy to evaluate when to process the input arguments used in a call to a function. The simplest strategy is to evaluate the input arguments passed then run the function with the arguments. Non-strict semantics means the input arguments are not evaluated unless the arguments passed into the function are used to evaluate what is in the body of the function. - Programming languages have rules to assign properties — called a type — to the components of the language: variables, functions, expressions, and modules. A type is a general description of possible values the variable, function, expression, or module can store. Typing helps minimize bugs, for example, when a calculation uses a string ("house” or "cat”) instead of a number (2 or 3). Strong static typing evaluates the code before runtime, when the code is static and possibly as code is written. - The order in which statements, instructions and functions are evaluated and executed determines the results of any piece of code. Control constructs define the order of evaluation. Constructs use an initial keyword to flag the type of control structure used. Initial keywords might be "if” or "do” or "loop” while final keywords might be "end if” or "enddo” or "end loop”. Instead of a final keyword, Haskell uses indentation level (tabs) or curly brackets, or a mix, to indicate the end of a control structure. Perhaps what makes Haskell special is how coders have to think when they use the language. Functional programming languages work in very different ways than imperative languages where the coder manages many low-level details of what happens in their code and when. While it is true all languages have things in common, it’s also true languages are mostly functional or
mostly imperative, the way people are mostly right handed or left handed. Except functional programming languages require a different way of thinking about software as you code. Other features that make Haskell interesting: - Strong data typing (evaluating properties of all inputs into a function) is combined with polymorphism; a function to sort numbers also can be used to sort strings of text. In some languages, you would have to code two or more functions, one for each data type. - Lazy evaluation (one of my favorite coding terms!) allows the result of one function/task to be handed to another function/task on the same line of code. For example, the command can search a file for all instances of a string then pass the results to be printed to the computer screen. Functions that can take other functions as arguments or return them as results also are called higher order functions. - No side effects. In other languages, code can affect the state of the computer and application, for example, writing to a file. Haskell strictly limits these side effects which, in turn, makes Haskell applications less prone to errors. - Haskell uses monads, a structure that works like an assembly line where every stop on the line performs a different task. This allows Haskell to separate side effects as a distinct activity apart from any function, for example, logging any errors as a function performs tasks on its data inputs. Building from small bits of code, each bit tightly contained and testable. How is Haskell Used?
As a functional programming language, Haskell has benefits like shorter development time, cleaner code, and high reliability. The tight control of side effects also eliminates many unforeseen interactions within a code base. These features are especially of interest to companies who must build software with high fault tolerances, for example, defense industries, finance, telecommunications, and aerospace. However, Haskell also is used in web startups where functional programming might work better than imperative programming. Apparently Facebook, Google, NVIDIA, and other companies use Haskell to build internal tools used in their software development and IT environments. Even a lawn mower manufacturer in Kansas uses Haskell to build and distribute their mowers. And the New York Times recently used Haskell to build an image processing tool for the 2013 New York Fashion week. So what is Haskell? Haskell is a purely functional programming language . In imperative languages we obtain results by giving the computer a sequence of tasks that it will then execute. While running them, you can change state. For example, we set the variable to 5, perform some tasks, and then change the value of the previous variable. These languages have flow control structures to carry out certain actions several times ( for , while ...). With purely functional programming we do not tell the computer what it has to do, but rather, we say how things are. The factorial of a number is the product of all the numbers from 1 to that number, the sum of a list of numbers is the first number plus the sum of the rest of the list, etc. We express the form of the functions. Also we can't set a variable to something and then set it to something else. If we say that a is 5, then we cannot say that it is something else because we have just said that it is 5. Are we liars? Thus, in purely functional languages, a function has no side effects. The only thing a function can do is calculate and
return something as a result. At first this may seem like a limitation but in reality it has some good consequences: if a function is called twice with the same parameters, we will always get the same result. We call this referential transparency and it not only allows the compiler to reason about the behavior of a program, but it also allows us to easily deduce (and even demonstrate) that a function is correct and thus be able to build more complex functions by joining simple functions. Haskell is lazy . That is, unless we tell you otherwise, Haskell will not execute functions or calculate results until you are really forced to. This works very well in conjunction with referential transparency and allows us to view programs as a series of data transformations. It even allows us to do cool things like infinite data structures. Let's say we have a list of immutable numbers xs = [1,2,3,4,5,6,7,8] and a doubleMe function that multiplies each item by 2 and returns a new list. If we wanted to multiply our list by 8 in an imperative language if we did doubleMe (doubleMe (doubleMe (xs))) , the computer would probably loop through the list, make a copy and return the value. Then it would go through the list two more times and return the final value. In lazy language, calling doubleMe with an unforced list to display the value ends up with a program telling you "Sure, sure, then I'll do it!". But when you want to see the result, the first doubleMe tells the second one that he wants the result, now! The second says the same to the third and the latter reluctantly returns a duplicate 1, which is a 2. The second receives it and returns a 4 to the first. The first one sees the result and says that the first item in the list is an 8. In this way, the computer only makes a journey through the list and only when we need it. When we want to calculate something from initial data in lazy language, we just have to take this data and transform and mold it until it resembles the result we want. Haskell is a statically typed language . When we compile a program, the compiler knows which pieces of the code are integers, which are text strings, etc. Thanks to this a lot of possible errors are caught at compile time. If we try to add a number and a text string, the compiler will scold us. Haskell uses a fantastic type system that has type inference. This means that we don't have to explicitly tag each piece of code with a type because the type system can
intelligently deduce it . Type inference also allows our code to be more general, if we have created a function that takes two numbers and adds them together and we do not explicitly set their types, the function will accept any pair of parameters that act as numbers. Haskell is elegant and concise. It is because it uses high-level concepts. Haskell programs are typically shorter than the imperative equivalents. And short programs are easier to maintain than long ones, and they have fewer errors. Haskell was created by some very smart guys (all of them with their respective doctorates). The project to create Haskell began in 1987 when a committee of researchers agreed to design a revolutionary language. In 2003 the Haskell report was published, thus defining a stable version of the language. What do you need to start A Haskell text editor and compiler. You probably already have your favorite text editor installed so we're not going to waste your time on this. Right now, Haskell's two main compilers are GHC (Glasgow Haskell Compiler) and Hugs. For the purposes of this guide we will use GHC. I will not cover many details of the installation. In Windows it is a matter of downloading the installer, pressing "next" a few times and then restarting the computer. In Debian-based Linux distributions it can be installed with apt-get or by installing a deb package . In MacOS it is a matter of installing a dmg or using macports . Whatever your platform, here is more information. GHC takes a script from Haskell (they usually have the .hs extension ) and compiles it, but it also has an interactive mode which allows us to interact with these scripts. We can call the functions of the scripts that we have loaded and the results will be shown immediately. It is much easier and faster to learn instead of having to compile and run the programs over and over again. Interactive mode is executed by typing ghci from your terminal. If we have defined some functions in a file called, say, myFunctions.hs , we can load those functions by typing : l myFunctions , as long as myFunctions.hs is in the same directory where ghci was invoked . If we modify the .hs script and want to observe the changes we have to rerun : l myFunctions or run : r which is
equivalent since it reloads the current script. We will work defining some functions in a .hs file , we load them and spend time playing with them, then we will modify the .hs file by reloading it and so on. We will follow this process throughout the guide. Starting Ready, Set, Go! Alright, let's get started! If you are that kind of bad person who doesn't read the introductions and you have skipped it, you may want to read the last section of the introduction because it explains what you need to follow this guide and how we are going to work. The first thing we are going to do is run GHC in interactive mode and use some functions to get used to it a little. Open a terminal and type ghci . You will be greeted with a greeting like this: GHCi, version 7.2.1: http://www.haskell.org/ghc/:? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package ffi-1.0 ... linking ... done. Prelude> Congratulations, you came from GHCi! Here the pointer (or prompt ) is Prelude> but since it gets longer as we load modules during a session, we are going to use ghci> . If you want to have the same pointer execute : set prompt "ghci> " . Here we have some simple arithmetic. ghci> 2 + 15 17 ghci> 49 * 100 4900 ghci> 1892 - 1472 420 ghci> 5/2 2.5 ghci> It is self explanatory. We can also use several operations on the same line so that all the rules of precedence that we all know are followed. We can use parentheses to use explicit precedence.
ghci> (50 * 100) - 4999 one ghci> 50 * 100 - 4999 one ghci> 50 * (100 - 4999) -244950 Very interesting, huh? Yes, I know not, but be patient. A small difficulty to keep in mind occurs when we deny numbers, it will always be better to surround negative numbers with parentheses. Doing something like 5 * -3 will make GHCi angry, however 5 * (-3) will work. Boolean algebra is also quite simple. As you probably know, && represents the logical AND while || represents the logical OR . not denies True to False and vice versa. ghci> True && False False ghci> True && True True ghci> False || True True ghci> not False True ghci> not (True && True) False The equality check is done like this: ghci> 5 == 5 True ghci> 1 == 0 False ghci> 5 / = 5 False ghci> 5 / = 4 True ghci> "hello" == "hello" True What if we do something like 5 + "text" or 5 == True ? Well, if we try the first one we get this friendly error message: No instance for (Num [Char]) arising from a use of `+ 'at <interactive>: 1: 0-9 Possible fix: add an instance declaration for (Num [Char]) In the expression: 5 + "text" In the definition of `it ': it = 5 +" text " GHCi is telling us that "text" is not a number and therefore does not know how to add it to 5. Even if instead of "text" it were "four" , "four" , or "4" ,
Haskell would not consider it as a number. + expects its left and right sides to be numbers. If we try to perform True == 5 , GHCi would tell us that the types do not match. While + works only with things that are considered numbers, == works with anything that can be compared. The trick is that both must be comparable to each other. We can't compare speed with bacon. We'll take a more detailed look at the types later. Note: we can do 5 + 4.0 because 5 does not have a specific type and can act as an integer or as a floating point number. 4.0 cannot act as an integer, so 5 is the only one that can be adapted. You may not know it, but we have been using functions all this time. For example, * is a function that takes two numbers and multiplies them. As you have already seen, we call him making a sandwich on him. We call this infix functions. Many functions that are not used with numbers are prefixes. Let's see some of them. Functions are normally prefixes so from now on we are not going to say that a function is in prefix form, we will just assume it. In many imperative languages functions are called by writing their names and then writing their parameters in parentheses, usually separated by commas. In Haskell, functions are called by typing their name, a space, and their parameters, separated by spaces. For starters, let's try calling one of Haskell's most boring functions. ghci> succ 8 9 The succ function takes anything that has a successor defined and returns that successor. As you can see, we have simply separated the function name and its parameter by a space. Calling a function with multiple parameters is just as easy. The min and max functions take two things that can be put in order (like numbers!) And return one of them. ghci> min 9 10 9 ghci> min 3.4 3.2 3.2 ghci> max 100 101 101 The application of functions (calling a function by putting a space after it and then writing its parameters) has the highest priority. Said with an example,
these two sentences are equivalent: ghci> succ 9 + max 5 4 + 1 16 ghci> (succ 9) + (max 5 4) + 1 16 However, if we had wanted to obtain the successor of the product of the numbers 9 and 10, we could not have written succ 9 * 10 because we would have obtained the successor of 9, which would have been multiplied by 10, obtaining 100. We have to write succ ( 9 * 10) to get 91. If a function takes two parameters we can also call it as an infix function by surrounding it with open accents. For example, the div function takes two integers and performs an integer division between them. Doing div 92 10 would get 9. But when we call it that, there may be some confusion as to what number is doing the division and which is being divided. So we call it as an infix function making 92 `div` 10 , thus making it clearer. People who already know some imperative language tend to cling to the idea that parentheses indicate an application of functions. For example, in C, you use parentheses to call functions like foo () , bar (1) , or baz (3, "haha") . As we have said, spaces are used to apply functions in Haskell. So these functions in Haskell would be foo , bar 1 and baz 3 "haha" . If you see something like bar (bar 3) it does not mean that bar is called with bar and 3 as parameters. It means that we first call the function bar with 3 as a parameter to get a number, and then call bar again with that number. In C, this would be something like bar (bar (3)) . The first small functions In the previous section we got a basic idea of how to call the functions. Now let's try to make ours! Open your favorite text editor and paste this function that takes a number and multiplies it by two. doubleMe x = x + x Functions are defined similarly to what they are called. The function name is followed by the parameters separated by spaces. But, when we are defining functions, there is an = and then we define what the function does. Save this as baby.hs or as you like. Now navigate to where you saved it and run ghci
from there. Once inside GHCi, write : l baby . Now that our code is loaded, we can play with the function that we have defined. ghci>: l baby [1 of 1] Compiling Main (baby.hs, interpreted) Ok, modules loaded: Main. ghci> doubleMe 9 18 ghci> doubleMe 8.3 16.6 Since + works with integers just as well as with floating-point numbers (actually anything that can be considered a number), our function also works with any number. We are going to make a function that takes two numbers, multiplies each of them by two and then adds both. doubleUs x y = x * 2 + y * 2 Simple. We could also have defined it as doubleUs x y = x + x + y + y . Both forms produce very predictable results (remember to add this function in the baby.hs file , save it and then execute : l baby inside GHCi). ghci> doubleUs 4 9 26 ghci> doubleUs 2.3 34.2 73.0 ghci> doubleUs 28 88 + doubleMe 123 478 As you can deduce, you can call your own functions within the functions you do. With this in mind, we could redefine doubleUs as: doubleUs x y = doubleMe x + doubleMe y This is a simple example of a normal pattern that you will see throughout Haskell. Create small functions that are obviously correct and then combine them into more complex functions. In this way you will also avoid repeating yourself. What if some mathematicians discover that 2 is actually 3 and you have to change your program? You can simply redefine doubleMe to be x + x + x and how doubleUs calls doubleMe will automatically work in this strange world where 2 is 3. Functions in Haskell don't have to be in any particular order, so it doesn't matter if you define doubleMe first and then doubleUs or do it the other way around. Now we are going to create a function that multiplies a number by 2 but only if that number is less than or equal to 100, because the numbers greater than
100 are already large enough on their own. doubleSmallNumber x = if x > 100 then x else x * 2 We have just introduced the Haskell if statement . You are probably already familiar with the if statement from other languages. The difference between Haskell's if statement and that of imperative languages is that the else part is mandatory. In imperative languages we can skip a few steps if a condition is not satisfied, but in Haskell each expression or function must return a value. We could also have defined the if statement on a single line but it seems a bit more readable that way. Another thing about the if statement in Haskell is that it is an expression. Basically an expression is a piece of code that returns a value. 5 is an expression because it returns 5, 4 + 8 is an expression, x + y is an expression because it returns the sum of x and y . Since the else part is mandatory, an if statement will always return something and is therefore an expression. If we want to add one to each number that is produced by the previous function, we can write its body like this. doubleSmallNumber ' x = ( if x > 100 then x else x * 2 ) + 1 If we had omitted the parentheses, I would have only added one if x was not greater than 100. Look at the ' at the end of the function name. That apostrophe has no special meaning in Haskell's syntax. It is a valid character to be used in the name of a function. We usually use ' to denote the strict version of a function (one that is not lazy) or a small modified version of a function or variable. Since ' is a valid character for functions, we can do things like this. conanO'Brien = "It's me, Conan O'Brien!" There are two things that remain to be highlighted. The first is that the name of this function does not begin with capital letters. This is because functions cannot start with an uppercase letter. We will see why a little later. The second is that this function does not take any parameters, we usually call it a definition (or a name). Since we can't change definitions (and functions) after we've defined them, conanO'Brien and the string "It's a-me, Conan O'Brien!" they can be used interchangeably.
An introduction to the lists Like real-life shopping lists, lists in Haskell are very helpful. It is the most widely used data structure and can be used in different ways to model and solve a lot of problems. The lists are VERY important. In this section we will take a look at the basics about lists, text strings (which are lists) and intensional lists. In Haskell, lists are a homogeneous data structure . Stores multiple items of the same type. This means that we can create an integer list or a character list, but we cannot create a list that has a few integers and a few other characters. And now, a list! Note We can use the let keyword to define a name in GHCi. Doing let a = 1 inside GHCi is equivalent to writing a = 1 to a file and then loading it. ghci> let lostNumbers = [4,8,15,16,23,42] ghci> lostNumbers [4,8,15,16,23,42] As you can see, the lists are defined by square brackets and their values are separated by commas. If we tried to create a list like this [1,2, 'a', 3, 'b', 'c', 4] , Haskell would warn us that the characters (which by the way are declared as a character in single quotes) are not numbers. Speaking of characters, strings are simply lists of characters. "hello" is just a syntactic alternative to ['h', 'e', 'l', 'l', 'o'] . Since the strings are lists, we can use the functions that operate with lists on them, which is really useful. A common task is to concatenate two lists. Which we did with the ++ operator . ghci> [1,2,3,4] ++ [9,10,11,12] [1,2,3,4,9,10,11,12] ghci> "hello" ++ "" ++ "world" hello world ghci> ['w', 'o'] ++ ['o', 't'] woot Be careful when using the ++ operator repeatedly on long strings. When we