Haskell Quest Tutorial - Ahead

  • Tutorial
West of House
You are standing in an open field west of a white house, with a boarded front door.
There is a small mailbox here.

> open mailbox
Opening the small mailbox reveals a leaflet.

> read leaflet

ZORK is a game of adventure, danger, and low cunning. In it you will explore some of the most amazing territory ever by mortals. No computer should be without one! ”

Part 1 - Anticipation
Part 2 - Forest
Part 3 - Glade
Part 4 - View of the canyon
Part 5 - Hall

Part 1,
in which we will not get to know all the basics of the Haskell language and write one useful function for the quest.

So, you are standing at the very beginning, in front of the closed door and you see the mailbox.

Haskell doesn't need much to program. First of all, desire. The interpreter and compiler may also be useful. We will use the GHC compiler (Glasgow Haskell Compiller) as part of the Haskell Platform for Windows, simply because it is convenient. (If you have Linux, you don’t need any advice - you know better than me how to make everything work.) Installation is trivial: downloadand install. After installation, run the ghci command. If it is not found, add the folder "... \ Haskell Platform \ xyz0 \ bin \" in PATH. Haskell code is stored in "* .hs" files. It can be written in HugIDE or in Notepad ++ (as I do). After installing the Haskell Platform, the * .hs files will be associated with the GHCi interpreter - and this is very convenient, as you will see later. You can also download a huge repository of all the goodness of Hackage, and there you will find a simple game advgame. She was a type and reference point for me in writing her own.

Create a folder for your quest. Create an empty QuestMain.hs file in it and run it. You will see, um, the console of the GHCi interpreter, which will help us in debugging. You will see that GHCi loaded some libraries, successfully compiled 1 file out of 1 and said: “Ok, modules loaded: Main.” You can play around: at the command prompt “* Main>” enter any mathematical expression. No, no, we will not write a program in it. Now we will study this tool a little, so that later it would be easier to debug the program.

* Main> 4
* Main> 2 + 4-7
* Main> 9 / (2 * 5.0)
* Main> (-1) +4
* Main> 7 == 7
* Main> -5> 0

The math functions are also available to you: sin x, cos x, tan x, atan x, abs x.

* Main> sin 10 * sin 10 + cos 10 * cos 10
* Main> sin (2 * cos 10) + tan 5

Haskell is a case-sensitive language (like C ++, Java). I know that you always wanted the cOS function, but it isn’t, reconcile! There is only "cos".

* Main> cos (cos (cos 0.5))
* Main> cos pi

This is all expected and understandable. pi is a function that returns the number pi; it has no arguments. Trigonometric functions receive one argument. What about functions with multiple arguments? The syntax is simple: first the name of the function, then the arguments. Without any brackets, commas or semicolons.

* Main> logBase 2 2
* Main> logBase (sin 2) 2

In the second example, it is important to wrap sin 2 in brackets so that this is the # 1 argument to the logBase function. If this is not done, the interpreter will think that we passed three arguments (sin, 2, 2) to the logBase function instead of two, and swears:

* Main> logBase sin 2 2
: 1: 1:
   No instance for ( Floating  (a0 -> a0))
     arising from a use of 'logBase'
   Possible fix: add an instance declaration for ( Floating  (a0 -> a0))
   In the expression: logBase sin 2 2
   In an equation for 'it': it = logBase sin 2 2

What he tells us, we will not think about it yet. This is not a royal matter, we have more important matters.

Other functions, including mathematical ones, can be found in the accompanying documentation, which is in the Haskell Platform ("GHC Library Documentation"). By default, all functions that are in the Prelude module are available. You did not load this module, it loaded itself. sin, cos and others are defined in it. Prelude contains a whole bunch of useful functions, they are used most often, therefore they are gathered together. Check out the Prelude documentation and you'll see something unusual, some weird constructions like this:

words ::  String  -> [ String ]

or even this one:

Eq  a =>  Eq  ( Maybe  a)

Unclear? Nothing, we'll figure it out.

Although there is something, let's play a bit, only now with lines. Haskell strings look the same as in C: characters inside double quotes. The special character \ n ("New Line") also works.

* Main>  "Hello, world!"
"Hello world!"
* Main>  "Hello, \ nworld!"
"Hello, \ nworld!"

What didn’t work ?? A. When we write a line in ghci, it simply repeats it. Similarly, it will repeat a single number or True:

* Main> 1,000,000
* Main> True

This is the ghci debug output, let's call it that. Neither a line nor a number have yet been sent to the real console. Let's send a line to print in a real console:

* Main> putStrLn  "Hello, \ nworld!"

Well, the putStrLn function accepted the string and printed it on the real console. ghci reproduced the result for us. Write: putStrLn, accepts the string, displays it on the screen. Two lines can be output by connecting them with the "++" operation:

* Main> putStrLn ( "Hello, world!"  ++  "\ nHere we go!" )
Hello, world!
Here we go!

Brackets are needed so ghci gets us right. Without parentheses, he would literally think the following:
putStrLn "Hello, world!" ++ "\ nHere we go!" <=> (putStrLn "Hello, world!") ++ ("\ nHere we go!")
And this is an ambush, because we are trying to add string1 to the console output1. How can we add a string to the output ?? Here is what the abuse will be:

* Main> putStrLn  "Hello, world!"  ++  "\ nHere we go!"
: 1: 1:
   Couldn't match expected type [a0] with actual type ' IO  ()'
   In the return type of a call of 'putStrLn'
   In the first argument '(++)', namely
     'putStrLn  "Hello, world! ” '
   In the expression: putStrLn  “ Hello, world! ”  ++  " \ nHere we go! "

Two outputs to the console also do not add up. A few more erroneous options:

* Main> putStrLn  "Hello, world!"   ++ putStrLn  "\ nHere we go!"
* Main> putStrLn  "Hello, world!"       PutStrLn  "\ nHere we go!"
* Main>  "Hello, world!"   ++ putStrLn  "\ nHere we go!"

The option when there is no putStrLn function at all will work, but we will get not "real", but debug string output. We wanted the part "\ nHere we go!" was printed from a new line, and not like this:

* Main>  "Hello, world!"   ++   "\ nHere we go!"
"Hello world! \ NHere we go!"

By trying to complete each of the erroneous options, you will find out what ghci thinks of you. We all know the pettiness of these interpreters and compilers! .. Give them only the correct and accurate, as if they do not live in our world, but in some kind of their own, ideal.

Well, actually it is. Haskell has its own world and its own laws. Laws require expression types to be correct. Haskell is a very strongly typed language. Functions, parameters, expressions - everything has a certain type. You cannot pass a parameter to a function if it wants a parameter of a different type. Try typing a number in a real console:

* Main> putStrLn 5
: 1: 10:
   No instance for ( Num  String )
     arising from the literal '5'
  Possible fix: add an instance declaration for ( Num  String )
  In the first argument of 'putStrLn', namely '5'
  In the expression: putStrLn 5
  In an equation for 'it': it = putStrLn 5

You see the familiar swearing of the interpreter about something. The putStrLn function wants a string (String type), and only it, but receives a number. Types do not match, => there is a conflict. You can do this:

* Main> putStrLn (show 5)

The show function, if possible, converts the argument to a string, which is then printed using putStrLn. To make sure that when you execute the function (show 5), you get the string “5”, enter something like this:

* Main> putStrLn ( "String and"  ++ (show 5) ++  "\ n - a string again." )
String  and 5
 - a string again.

The show function can translate many types into a string. She will be very useful in the quest.

* Main> putStrLn ( "sin ^ 2 5 + cos ^ 2 5 ="  ++ show (sin 5 * sin 5 + cos 5 * cos 5))
sin ^ 2 5 + cos ^ 2 5 = 0.999999999999999

Of course, putStrLn will come in handy no less. In theory, it should not return anything, because the Pascal procedure writeLn does not return anything either. But in Haskell, functions always return something, because otherwise what would they be “functions”. Make sure of this? You can enter some utility commands in ghci, and the ": t" (": type") command shows the type of any expression:

* Main>: t 3 == 3
3 == 3 ::  Bool
* Main>: t 3 / = 3
3 / = 3 ::  Bool
* Main>: type 'f'
'f' ::  Char
* Main>: type  "I am a string"
"I am a string"  :: [ Char ]

Here, any expression can be considered a special kind of function, possibly not taking parameters, but necessarily returning a result. The putStrLn function type looks like this:

* Main>: t putStrLn
putStrLn ::  String  ->  IO  ()

If you have not come across this form of writing (in mathematics, in some other functional languages), then perhaps you will be a little unusual, as it once was to me. But you quickly get used to the good, and the types in Haskell are just as good. So good that in the vast majority of cases we do not need to specify them - Haskell will remove them himself and even threaten us with a finger if something does not match somewhere. We are still looking at all kinds of designs from the basic types, and you will also feel how convenient it is. Not like in some C ++, where every variable is needed, each element is described, painted, registered ...

In this case, putStrLn takes a String and returns IO (). The colon separates the name of the function and its type. It’s correct to read like this: “putStrLn has a type from String to IO ()”. String is the type of our input string. Type "IO ()" - an integral part of the input-output (Input / Output, you guessed it). The “IO ()” type indicates that the putStrLn function indulges in the “unclean” area of ​​the program. Anything can happen in this area, even disaster, and we must think carefully about it. But for now, we have nothing to think about, and let IO () not bother us. We will try to write functions in which there can be no catastrophe, functions in which there is no place for side effects.

They are called pure, deterministic: such functions must return the same value for the same argument. The Haskell language is a pure functional language precisely because of this concept. Here, of course, the question arises, and what to do with functions that, with different calls, can give different results (pseudo-random number generators, for example). And how to change the data? How to work with memory? How to read keyboard input? After all, all this leads to non-determinism. Well, in Haskell there are special mechanisms (“monads”) by which these and other problems are gracefully resolved. We will return to this conversation in the future.

So, open in the text editor QuestMain.hs. So far there is empty - but this is only the beginning, the threshold. Soon mermaids and dragons will fly here. In the meantime, write a simple function that computes the product of two numbers.

prod x y = x * y

Forget about assignment! There is no assignment in Haskell! What you see above is a function declaration. “Equal” means that the prod functions with two arguments x and y, we match the expression x * y. This expression will be evaluated if we call the function. Let's do it. Save the QuestMain.hs file. If you have already closed the ghci console, run it again (ghci QuestMain.hs). If the console is open, enter the command: r - this will force ghci to reload and compile the current file, that is, your QuestMain.hs.

* Main>: r
[1 of 1] Compiling Main (H: \ Haskell \ QuestTutorial \ Quest \ QuestMain.hs, interpreted)
Ok, modules loaded: Main.
* Main> prod 3 5

Works! (If it doesn’t work, check: letter case; whether QuestMain.hs is saved; whether this version is loaded in ghci.) It is easy to guess that numbers 3 and 5 are associated with the variables x and y, respectively. The interpreter substitutes the expression 3 * 5 instead of prod 3 5, which is calculated.

* Main> prod 3 (2 + 3)
* Main> prod 3 (cos pi)

We will write and test a couple more functions. (Hereinafter, I will no longer specify that we write functions in a file, but we test them in ghci.) For example, such:

printString str = putStrLn str
printSqrt x = putStrLn ( "Sqrt of"  ++ show x ++  "="  ++ show (sqrt x))
* Main> printString  "dfdf"
* Main> printSqrt 4
Sqrt of 4.0 = 2.0
* Main> printSqrt (-4)
Sqrt of -4.0 = NaN

In the latter case, the square root of a negative number gives the result “Not a Number”. Suppose that this conclusion does not suit us, and we would like the string “x <0!” To be outputted to negative x. We rewrite the printSqrt function in several ways, and at the same time study a couple of very useful constructs.

printSqrt1 x =
    if x <0
    then putStrLn  "x <0!"
    else putStrLn ( "Sqrt of"  ++ show x ++  "="  ++ show (sqrt x))
printSqrt2 x = case x <0 of
                True -> putStrLn  “x <0!”
                False -> putStrLn ( “Sqrt of”  ++ show x ++  "="  ++ show (sqrt x))
* Main> printSqrt1 (-4)
x <0!
* Main> printSqrt2 (-4)
x <0!

if cannot be without else, because everything that is after the “equal” sign is an expression in which all options (alternatives) should be taken into account. If you didn’t take into account some option, but it fell out, you will get an error that you have an incomplete set of alternatives.

printSqrt2 x = case x <0 of
                True -> putStrLn  "x <0!"
* Main>: r
* Main> printSqrt2 (-4)
x <0!
* Main> printSqrt2 10
*** Exception: H: \ Haskell \ QuestTutorial \ Quest \ QuestMain.hs: (12.16) - (13.41): Non-exhaustive patterns in case

Also note that in the case constructs, indentation (“indentation") is important. They should be the same - this also applies to spaces there or tabs. Try compiling:

printSqrt2 x = case x <0 of
             True -> putStrLn  "x <0!"
                False -> putStrLn ( "Sqrt of"  ++ show x ++  "="  ++ show (sqrt x))
* Main>: r
[ 1 of 1] Compiling Main (H: \ Haskell \ QuestTutorial \ Quest \ QuestMain.hs, interpreted)
H: \ Haskell \ QuestTutorial \ Quest \ QuestMain.hs:
    14:23 : paerse error on input '->'
Failed, modules loaded: none.

Case design is very convenient. It is easier to read and extend than if-then-else. Of course, it can be expressed through a series of nested if-then-else, therefore, the constructions are equivalent. Let me tell you a secret: case is even more functional, and soon we will see it. In the meantime, an additional example. A bit artificial, but it doesn’t matter:

thinkAboutSquaredX x = case x of
                0.0 -> "I think, x is 0, because 0 * 0 = 0."
                1.0 -> "x is 1, because 1 * 1 = 1."
                4.0 -> "Well, x is 2, because 2 * 2 = 4."
                9.0 -> "x = 3."
                16.0 -> "No way, x = 4."
                25.0 -> "Ha! X = 5!"
                otherwise  -> if x <0 then "x <0!" else "Sqrt" ++ show x ++ "=" ++ show (sqrt x)

* Main> thinkAboutSquaredX 1
"x is 1, because 1 * 1 = 1."
* Main> thinkAboutSquaredX 25
"Ha! x = 5! ”

The word otherwise also means:“ otherwise ”. When the rest of the options do not go in turn, otherwise will do , because it is only a synonym for True. Do not insert it in the middle, because then all the lower options will be unavailable.

thinkAboutSquaredX x = case x of
                0.0 ->  "I think, x is 0, because 0 * 0 = 0."
                1.0 ->  "x is 1, because 1 * 1 = 1."
                otherwise  -> if x <0 then  " x <0! ”  else  “ Sqrt ”  ++ show x ++  " = "  ++ show (sqrt x)
                4.0 ->  " Well, x is 2, because 2 * 2 = 4. "
                9.0 ->  " x = 3 . ”
                16.0 ->  “ No way, x = 4. ”
                25.0 ->  “ Ha! x = 5! ”

The code compiles, but we get a warning about the intersection of samples for matching:

*Main> :r
[1 of 1] Compiling Main    (H:\Haskell\QuestTutorial\Quest\QuestMain.hs, interpreted)
    Warning: Pattern match(es) are overlapped
             In a case alternative:
                 4.0 -> ...
                 9.0 -> ...
                 16.0 -> ...
                 25.0 -> ...
Ok, modules loaded: Main.
*Main> thinkAboutSquaredX 1
«x is 1, because 1 * 1 = 1.»
*Main> thinkAboutSquaredX 9
«Sqrt 9.0 = 3.0»
*Main> :t otherwise
otherwise :: Bool
*Main> otherwise == True

Recall what we are doing here: we are writing a quest! There are three things in any quest: locations (by moving paths), objects, and player actions. How could we get a description of the location if we had her number? Here is the solution:

describeLocation locNumber = case locNumber of
            1 ->  "You are standing in the middle room at the wooden table."
            2 ->  "You are standing in the front of the night garden behind the small wooden fence."
            - Insert a description of others here locations.
            otherwise  ->  "Unknown location."
* Main> describeLocation 2
"You are standing in the front of the night garden behind the small wooden fence."
* Main> describeLocation 444
"Unknown location."

Please note: comment! Single-line comments begin with a double minus (as in SQL!). For multi-line characters {- and -} are used:

describeLocation locNumber = case locNumber of
            1 ->  "You are standing in the middle room at the wooden table."
            2 ->  "You are standing in the front of the night garden behind the small wooden fence."

Insert a description of the others locations.             Or here.              adfsdf few fef jel jle jkjlefjaiejeo -}
            otherwise  ->  "Unknown location."

Here you go. We did not get to know all of the basic Haskell constructs. putStrLn, show, case-construction, lines, ghci - all this we will need in the future. We even wrote one function for the quest. Perhaps enough. In the second part, we will begin to work on the quest and along the way we will learn some other great Haskell tricks. Adventures await us!

To fix it, you can solve the following problems:

1. Define the function z and calculate it for some values:
t = (7 * x ^ 3 - ln (abs (a))) / (2.7 * b)
y = sin (t) - sin (a)
z = 8.87 * y ^ 3 + arctan (t)
where x, a, b are variables of type Float.

2. Define the function y and calculate it for some values:
| ln (abs (sin (x))) if x> 5
y = | x ^ 2 + a ^ 2 if x <= 5 and a <= 3
| x / a + 7.8 * a, if x <= 5 and a> 3
where x, a are variables of type Float.

The table of contents, the list of references and additional information can be found in the Greetings

Also popular now: