Haskell tutorial pdf




















This book teaches you how to make machine learning models more interpretable. Bruce Eckel and Svetlana Isakova. Alien Torres and Matt Dahse. Top Bundles 1. This bundle contains a detailed and challenging collection of workbook labs, plus an extensively detailed technical reference guide. You'll learn:The essence of software architecture. Why the software architecture role should include coding, coaching and collaboration.

The things that you really need to think about before The first book is primarily suitable for for programmers with some experience of programming in another language. If you don't have any experience with programming this book may be a bit daunting. You'll be learning not just a programming Updates are included. When I update one of the books, you immediately get the updated bundle. This training bundle for security engineers and researchers, malware and memory forensics analysts includes two accelerated training courses for Windows memory dump analysis using WinDbg.

It is also useful for technical support and escalation engineers who analyze memory dumps from complex software environments and need to check for possible Publish Early, Publish Often. Path There are many paths, but the one you're on right now on Leanpub is:. Star View license. Branches Tags. Could not load branches. Could not load tags. Latest commit. The one chosen in Haskell is the system of type classes.

It is, however, the one we have, so you should learn to love it. But this function may not be defined for every type; just for some. For instance, Int is an instance of Eq since equality is defined over integers. This was done so that when you type in a number like 5, the compiler is free to say 5 is an integer or floating point number as it sees fit. It defines the Num class to contain all of these numbers and certain minimal operations over them addition, for instance.

The basic numeric types Int, Double are defined to be instances of Num. We have only skimmed the surface of the power and complexity of type classes here. There will be much more discussion of them in Section 8. Before we do that, we need to talk a little more about functions. Types which are mem- bers of the Show class have functions which convert values of that type to a string.

This function is called show. Some types are note instances of Show; functions for example. The type given to functions mim- icks the lambda calculus representation of the functions.

Say we decide x is an Int. We know that when we multiply two Ints together, we get another Int, so the type of the results of square is also an Int. We can apply a similar analysis to the function f above. In general, any function which is used infix meaning in the middle of two arguments rather than before them must be put in parentheses when getting its type.

Similarly, : takes a value of type a and another value of type [a] list of as and produces another value of type [a]. The immediate question which arises is: okay, so how do I get rid of the IO. The only way to use things with an IO type is to combine them with other functions using for example , the do notation. Suppose you have a function f which takes a String and produces an Int.

We then, for example, print i to the screen. This is because we are in a do block. Type declarations are written separatly from the function definition. However, the type that you specify must match the inferred type of the function definition or be more specific. In this definition, you could apply square to anything which is an instance of Num: Int, Double, etc.

What is the type of square in this example? For instance, map took a function to apply to each element in a list, filter took a func- tion that told it which elements of a list to keep, and foldl took a function which told it how to combine list elements together. As with every other function in Haskell, these are well-typed. So map will take a value of type [a] and produce a value of type [b].

How does it do this? It uses the user-supplied function to convert. So it takes a function which turn an a and a b into an a, an initial value of type a and a list of bs. It produces an a. To see this, we can write a function count which counts how many members of a list satisfy a given constraint. It filters the list l according to the predicate p, then takes the length of the resulting list. On the other hand, count2 uses the intial value which is an integer to hold the current count.

For each element in the list l, it applies the lambda expression shown. It checks to see if p holds about x. However, it is often desirable to be able to define our own data structures and functions over them. After the equals sign, we specify the constructors of this data type. In the definition of pairFst we take an entire Pair and extract the first element; similarly for pairSnd.

Write functions tripleFst, tripleSnd and tripleThr to extract respectively the first, second and third elements. Exercise 4. However, the first two elements must be the same type and the last two elements must be the same type. Write a function firstTwo which returns a list containing the first two elements and a function lastTwo which returns a list containing the last two elements.

It is also possible and extremely useful to have multiple constructors. Let us consider a simple function which searches through a list for an element satisfying a given predicate and then returns the first element satisfying that predicate. What should we do if none of the elements in the list satisfy the predicate? Raising an error is certainly an option see Section Looping indefinitely is possible, but not terribly useful.

We could write a sister function which checks to see if the list contains an element satisfying a predicate and leave it up to the user to always use this function first. We could return the first element, but this is very ad-hoc and difficult to remember. The fact that there is no basic option to solve this problem simply means we have to think about it a little more. What are we trying to do? Furthermore, if it does succeed, it returns some sort of value. The second is to use the constructor Just, together with a value of type a.

The Maybe type is useful in all sorts of circumstances. For instance, suppose we want to write a function like head which returns the first element of a given list. In the first line of code, we match against the empty list []. If this match succeeds i. If the first match fails, then we try to match against x:xs which must succeed. In this case, we return Just x.

For our findElement function, we represent failure by the value Nothing and success with value a by Just a. In this case, our first argument is the predicate and takes an element of type a and returns True if and only if the element satisfies the predicate ; the second argument is a list of as.

Our return value is maybe an a. That is, if the function succeeds, we will return Just a and if not, Nothing. That is, something of type Either a b is either a value of type a using the Left constructor or a value of type b using the Right constructor. Also provide functions tuple1 through tuple4 which take a tuple and return Just the value in that position, or Nothing if the number is invalid i.

You will need to use the Either type to represent this. These are datatypes whose definitions are based on themselves. This is almost identical to the actual definition of the list datatype in Haskell, except that uses special syntax where [] corresponds to Nil and : corresponds to Cons.

The first line says that the length of an empty list a Nil is 0. This much is obvious. The second line tells us how to calculate the length of a non- empty list. A non-empty list must be of the form Cons x xs for some values of x and xs.

Thus, we apply the listLength function to xs and add one to the result. This gives us the length of the entire list. Suppose we want to define a structure that looks like a binary tree. Each of these children is another node. It is simple to modify the listLength function so that instead of calculating the length of lists, it calculates the number of nodes in a BinaryTree. Can you figure out how? We can call this function treeSize.

The re- sult type should be a normal Haskell list. Suppose we were using this to write a drawing program, we could then write a function to convert between a Color and a RGB triple. This is essentially the same as a void type in a langauge like C or Java and will be useful when we talk about IO in Chapter 5. The idea behind CPS is to pass around as a function argument what to do next. I will handwave through an example which is too complex to write out at this point and then give a real example, though one with less motivation.

Consider the problem of parsing. The idea here is that we have a sequence of tokens words, letters, whatever and we want to ascribe structure to them.

So is the task of taking an English sentence and creating a parse tree though the latter is quite a bit harder. But for simplicity, assume they are not separated by commas. That is, a function call looks like myFunction x y z. The general approach to solving this would be to write a function which parses function calls like this one.

Similarly, parseIdentifier will parse one of the arguments. What the parseFunction function would do is to parse an identifier. If this fails, it fails itself.

Otherwise, it continues and tries to parse a open parenthesis. If that succeeds, it repeatedly calls parseIdentifier until that fails. It then tries to parse a close parenthesis. Otherwise, it fails. There is, however, another way to think about this problem. The advantage to this solution is that functions no longer need to return the remaining tokens which tends to get ugly.

This takes three arguments: a list of tokens and two continuations. The first continuation is what to do when you succeed. The second continuation is what to do if you fail. What parseIdentifier does, then, is try to read an identifier. If this succeeds, it calls the first continuation with that identifier and the remaining tokens as arguments.

If reading the identifier fails, it calls the second continuation with all the tokens. Recall that it wants to read an identifier, an open parenthesis, zero or more identifiers and a close parenthesis. Thus, the first thing it does is call parseIdentifier. The first argument it gives is the list of tokens. The first continuation which is what parseIdentifier should do if it succeeds is in turn a function which will look for an open parenthesis, zero or more arguments and a close parethesis.

The second argument the failure argument is just going to be the failure function given to parseFunction. Now, we simply need to define this function which looks for an open parenthesis, zero or more arguments and a close parethesis.

This is easy. I realize this discussion has been quite abstract. I would willingly give code for all this parsing, but it is perhaps too complex at the moment. Instead, consider the problem of folding across a list. The first is the current list element, x, the second is the accumulated element, z, and the third is the continuation: basicially, what to do next.

As it turns out, this slight difference changes the behavior for being like foldr to being like foldl. We will revisit the topic more thoroughly later in the book. If not, where is the difference? It could produce a unit , since there is essentially no return value from printing a string. We want both of these operations to be functions, but they are by definition not functions.

The item that reads a string from the keyboard cannot be a function, as it will not return the same String every time. But clearly this does not have the desired effect. Similarly, readAString takes a current state of the world and returns a new state of the world, paired with the String that was typed.

This would be a possible way to do IO, though it is more than somewhat unweildy. It is completely unclear what this program should do. Clearly, it must read a string in order to have a value for name to be printed.

But that means that the RealWorld has been updated. There is clearly something wrong happening here. Suffice it to say that doing IO operations in a pure lazy functional language is not trivial. In fact, monads are able to express much more than just the simple operations described above; we can use them to express a variety of constructions like concurrence, exceptions, IO, non-determinism and much more.

Moreover, there is nothing special about them; they can be defined within Haskell with no special handling from the compiler though compilers often choose to optimize monadic operations. Therefore, we give them another name: actions. Not only do we give them a special name, we give them a special type.

One particularly useful action is putStrLn, which prints a string to the screen. What it returns is of type IO. This means that this function is actually an action that is what the IO means. This is something that is left up to the compiler. You cannot actually run an action yourself; instead, a program is, itself, a single action that is run when the compiled program is executed. Thus, the compiler requires that the main function have type IO , which means that it is an IO action that returns nothing.

The compiled code then executes this action. However, while you are not allowed to run actions yourself, you are allowed to combine actions.

In fact, we have already seen one way to do this using the do notation how to really do this will be revealed in Chapter 9. This is something that we are allowed to execute.

The getLine action has type IO String, so it is okay to execute it directly. The condition needs to have type Bool, and the two branches can have any type, provided that they have the same type. This clearly has the correct type. The code here is: do putStrLn "Too low! The first has type IO , which is fine. The second also has type IO , which is fine. The type result of the entire computation is precisely the type of the final computation.

This is somewhat overly verbose. Since we have only one action here, it is superfluous. It will certainly complain though the error may be somewhat dif- ficult to comprehend at this point. We can write the same doGuessing function using a case statement. To do this, we first introduce the Prelude function compare, which takes two values of the same type in the Ord class and returns one of GT, LT, EQ, depending on whether the first is greater than, less than or equal to the second.

This is not so in Haskell. In Haskell, return simply takes a normal value for instance, one of type IO Int and makes it into an action that returns the given value for instance, the value of type Int. On the other hand, if you guess incorrectly, it will try to evaluate the case statement and get either LT or GT as the result of the compare. Exercises Exercise 5. If the name is one of Simon, John or Phil, tell the user that you think Haskell is a great programming language.

Write two different versions of this program, one using if statements, the other using a case statement. That is, there is no difference between FilePath and String. So, for instance, the readFile function takes a String the file to read and returns an action that, when run, produces the contents of that file.

See Section 8. Most of these functions are self-explanatory. The openFile and hClose func- tions open and close a file, respectively, using the IOMode argument as the mode for opening the file. The getChar, getLine and getContents variants read from standard input. The variants without the h prefix work on standard output. The readFile and writeFile functions read an entire file without having to open it first. The bracket function is used to perform actions safely.

Consider a function that opens a file, writes a character to it, and then closes the file. When writing such a function, one needs to be careful to ensure that, if there were an error at some point, the file is still successfully closed. The bracket function makes this easy. The third is the action to perform in the middle, which might result in an error. However, if writing the character fails, hClose will still be executed, and the exception will be reraised afterwards.

The interface is admittedly poor, and it does not catch all errors try reading a non-existant file. Nevertheless, it should give a fairly complete example of how to use IO. First, it issues a short string of instructions and reads a command. Thus, the type of return is IO. Otherwise, it matches , the wildcard character, and loops to doLoop. The doRead function uses the bracket function to make sure there are no prob- lems reading the file.

It opens a file in ReadMode, reads its contents and prints the first characters the take function takes an integer n and a list and returns the first n elements of the list. The doWrite function asks for some text, reads it from the keyboard, and then writes it to the file specified.

NOTE Both doRead and doWrite could have been made simpler by using readFile and writeFile, but they were written in the ex- tended fashion to show how the more complex functions are used.

They only catch exceptions within the main body, not within the startup or shutdown functions openFile and hClose, in these cases.

We would need to catch exceptions raised by openFile, in order to make this complete. We will do this when we talk about exceptions in more detail in Section If the user responds quit, the program should exit.

For example, running this program might produce: Do you want to [read] a file, [write] a file or [quit]? Do you want to [read] a file, [write] a file or [quit]? Chapter 6 Modules In Haskell, program subcomponents are divided into modules. For instance, suppose I am writing a game of poker.

In this code, the function deal calls a helper function dealHelper. In order to do this, we create an export list, which we insert just after the module name declaration: module Cards Card , Deck , newDeck, shuffle, deal where The after Card and Deck specify that we are exporting the type but none of the constructors.

And this will automatically export all the constructors. It may be the case that two module export functions or types of the same name. In these cases, you can import one of the modules qualified which means that you would no longer be able to simply use the newDeck format but must use the longer Cards.

This was designed to get rid of clutter in the directories in which modules are stored. Hierarchical imports allow you to specify to a certain degree where in the directory structure a module exists. The full path of the Cards. Cards as Cards When we think about programming, we think about the code being the default mode of entry and comments being secondary.

Literate programming swaps these preconceptions. Each will be discussed individually. No matter which you use, literate scripts must have the extension lhs instead of hs to tell the compiler that the program is written in a literate style.

Everything else remains the same. For example, our Hello World program would be written in Bird-style as: This is a simple literate! Hello World program. When compiled or loaded in an interpreter, this program will have the exact same properties as the non- literate version from Section 3. Chapter 7 Advanced Features Discussion 7. In general, these are called sections. Where clauses can contain any number of subexpressions, but they must be aligned for layout. Some- times it is more convenient to put the local definitions before the actual expression of the function.

We have already seen let clauses; where clauses are virtually identical to their let clause cousins except for their placement. This is strongly advised against, as it tends to make code difficult to read. However, if you choose to do it, values in the let clause shadow those in the where clause. Of course, I plead with you to never ever write code that looks like this. No one should have to remember this rule and by shadowing where-defined values in a let clause only makes your code difficult to understand.

In general, whether you should use let clauses or where clauses is largely a matter of personal preference. Usually, the names you give to the subexpressions should be sufficiently expressive that without reading their definitions any reader of your code should be able to figure out what they do. In this case, where clauses are probably more desirable because they allow the reader to see immediately what a function does. However, in real life, values are often given cryptic names.

In which case let clauses may be better. Either is probably okay, though I think where clauses are more common. When discussing sections in Section 7. For instance, suppose we are writting a function lcaseString which converts a whole string into lower case.

However, we notice that the application of s occurs at the end of both lcaseString and of map toLower. Now, consider the task of converting a string to lowercase and remove all non letter characters. In fact it has a name: point-free programming not to be confused with pointless program- point-free programming ming. It is call point free because in the original definition of lcaseLetters, we can think of the value s as a point on which the function is operating.

By removing the point from the function definition, we have a point-free function. A function similar to. Consider now the task of extracting from a list of tuples all the ones whose first component is greater than zero. What are we checking? The fst element. While converting to point free style often results in clearer code, this is of course not always the case. Of course, not all functions can be written in point free style. Exercises Exercise 7.

It is most commonly used in conjunction with case expres- sions, which we have already seen in Section 3. Specifically, if we see a Color which is Red, we want to return ,0,0 , since this is the RGB value for red. We then apply this to colorToRGB. This match fails because according to the definition of Eq Color, Red is not equal to Yellow. This fails, too.

You express that in the form of functions. You also can't set a variable to something and then set it to something else later. If you say that a is 5, you can't say it's something else later because you just said it was 5. What are you, some kind of liar?

So in purely functional languages, a function has no side-effects. The only thing a function can do is calculate something and return it as a result. At first, this seems kind of limiting but it actually has some very nice consequences: if a function is called twice with the same parameters, it's guaranteed to return the same result. That's called referential transparency and not only does it allow the compiler to reason about the program's behavior, but it also allows you to easily deduce and even prove that a function is correct and then build more complex functions by gluing simple functions together.

Haskell is lazy. That means that unless specifically told otherwise, Haskell won't execute functions and calculate things until it's really forced to show you a result.

That goes well with referential transparency and it allows you to think of programs as a series of transformations on data. It also allows cool things such as infinite data structures.

If we wanted to multiply our list by 8 in an imperative language and did doubleMe doubleMe doubleMe xs , it would probably pass through the list once and make a copy and then return it. Then it would pass through the list another two times and return the result. In a lazy language, calling doubleMe on a list without forcing it to show you the result ends up in the program sort of telling you "Yeah yeah, I'll do it later!

But once you want to see the result, the first doubleMe tells the second one it wants the result, now! The second one says that to the third one and the third one reluctantly gives back a doubled 1, which is a 2.



0コメント

  • 1000 / 1000