⬆️ ⬇️

Haskell Quest Tutorial - The Threshold

West of house

You are in front 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

(Taken)

“WELCOME TO ZORK!

')

ZORK is a game of adventure, danger, and low cunning. By the mortals. No computer should be without one!





Content:

Greeting

Part 1 - The Threshold

Part 2 - Forest

Part 3 - Polyana

Part 4 - View of the canyon

Part 5 - Hall



Part 1,

in which we will get acquainted with not all the basics of the Haskell language and write one useful quest feature.



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



Programming in Haskell takes a bit. First of all - the desire. It may also be useful interpreter and compiler. We will use the GHC compiler (Glasgow Haskell Compiller) on the Haskell Platform for Windows, simply because it’s convenient. (If you have Linux, you don't need advice, you know better than me how to make everything work.) Installation is trivial: download and install. After installation, run the ghci command. If it is not found, add the folder "... \ Haskell Platform \ xyz0 \ bin \" to PATH. Haskell code is stored in "* .hs" files. You can write it in HugIDE or 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 any good 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, a GHCi interpreter console that will help us with 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: 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

four

* Main > 2 + 4 - 7

- 1

* Main > 9 / ( 2 * 5.0 )

0.9

* Main > ( - 1 ) + 4

3

* Main > 7 == 7

True

* Main > - 5 > 0

False




You also have access to mathematical functions: sin x, cos x, tan x, atan x, abs x.



* Main > sin 10 * sin 10 + cos 10 * cos 10

1.0

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

- 4.374758876543559




Haskell is a case-sensitive language (like C ++, Java). I know that you always wanted the "cos" function, but it is not there, accept it! There is only “cos”.



* Main > cos ( cos ( cos 0.5 ) )

0.8026751006823349

* Main > cos pi

- 1.0




This is all expected and understandable. pi is a function that returns the number pi, it has no arguments. Trigonometric functions take 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 and semicolons.



* Main > logBase 2 2

1.0

* Main > logBase ( sin 2 ) 2

- 7.28991425837762




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



* Main > logBase sin 2 2



< interactive > : 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 this equation for 'it': it = logBase sin 2 2

..................




What he tells us, we will not ponder yet. This is not a royal business, we have more important things.



Other functions, including math, can be found in the accompanying documentation, which is in the Haskell Platform ("GHC Library Documentation"). By default, all functions available 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, and therefore gathered together. Check out the Prelude documentation, and you will see something unusual, some strange constructions like this:



words :: String -> [ String ]




or even this one:



Eq a => Eq ( Maybe a )




Unclear? Nothing else to figure it out.





Although what's there, let's still play around, only now with strings. Strings in Haskell look the same as in C: characters inside double quotes. The special symbol \ n ("New Line") also works.



* Main > "Hello, world!"

"Hello world!"

* Main > "Hello, \ n World!"

“Hello, \ n world!”




What, did not work ?? A. When we write a ghci string, it simply repeats it. Similarly, it will repeat one number or True:



* Main > 1,000,000

1,000,000

* Main > True

True




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



* Main > putStrLn "Hello, \ n world!"

Hello ,

world !




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



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

Hello , world !

Here we go !




Brackets are needed for ghci to understand us correctly. Without brackets he will think literally 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 a string to the output to the console of line 1. How can we add a line to the output ?? Here is what will be at the same time abuse:



* Main > putStrLn "Hello, world!" ++ " \ n Here we go!"



< interactive > : 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!" ++ " \ n Here we go!"




The two console outputs do not add up either. Some more erroneous options:



* Main > putStrLn "Hello, world!" ++ putStrLn " \ n Here we go!"

* Main > putStrLn "Hello, world!" PutStrLn " \ n Here we go!"

* Main > "Hello, world!" ++ putStrLn " \ n Here we go!"




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



* Main > "Hello, world!" ++ " \ n Here we go!"

“Hello, world! \ n Here we go!




Having tried to execute each of the erroneous options, you will find out what ghci thinks of you. We know with you all 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 ideal one.



Well, actually, it is. Haskell has its own world and its own laws. Laws require that the types of expressions are correct. Haskell is a very strictly typed language. Functions, parameters, expressions - everything has a specific type. You cannot pass a parameter to a function if it wants a parameter of another type. Try typing a number in a real console:



* Main > putStrLn 5



< interactive > : 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 this equation for 'it': it = putStrLn 5




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



* Main > putStrLn ( show 5 )

five




The show function, if it can, converts the argument to a string, which is then printed using putStrLn. To make sure that by executing 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 is 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 comes in handy as much. 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 they would be “functions”. Make sure of this? In ghci, you can enter some service commands, 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 accepting parameters, but necessarily returning a result. The type of the putStrLn function looks like this:



* Main > : t putStrLn

putStrLn :: String -> IO ( )




If you have not encountered this form of recording (in mathematics, in some other functional languages), then perhaps you will be a little unusual, as I once was. But you quickly get used to the good, and the types in Haskell are pretty good. So good that in the overwhelming majority of cases we do not need to indicate them, Haskell will bring them out himself and even threaten us with a finger, if something somewhere does not coincide. We are still looking at all sorts of designs from the basic types, and you too will feel how comfortable it is. Not like in some C ++, where every variable needs to be described, each element described, painted, registered ...



In this case, putStrLn takes a String and returns IO (). The colon separates the function name and its type. Correctly read like this: “putStrLn is of type from String in IO ()”. String is the type of our input string. The “IO ()” type is an integral part of I / O (Input / Output, you guessed it). The “IO ()” type indicates that the putStrLn function is indulging in an “unclean” program area. Anything can happen in this area, even a catastrophe, and we need to think about it carefully. But so far we have nothing to think about, and even if IO () does 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 are required to 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 as to what to do with functions that, with different calls, can give different results (pseudorandom 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 elegantly solved. We will return to this conversation in the future.




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



prod x y = x * y




Forget assignment! There is no assignment in Haskell! What you see above is a function declaration. Equals means that the prod function 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 and do it. Save the QuestMain.hs file. If you have already closed the ghci console, run (ghci QuestMain.hs) again. 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

15




Works! (If it doesn't work, check: case of letters; whether QuestMain.hs is saved; whether this version is loaded in ghci.) It is easy to guess that the 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 )

15

* Main > prod 3 ( cos pi )

- 3.0




Let's write and test a couple more functions. (Hereinafter, I will no longer specify what we write functions in the file, but we test it in ghci.) For example, such:



printString str = putStrLn str

printSqrt x = putStrLn ( "Sqrt of" ++ show x ++ "=" ++ show ( sqrt x ) )



* Main > printString "dfdf"

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 “Not a Number” result. Suppose that this conclusion does not suit us, and we would like to see the string “x <0!” On negative x. Let's rewrite the printSqrt function in several ways, and at the same time we will study a couple of very useful constructions.



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 have not considered any 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 versions, the indents (“beating”) are 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 expand than if-then-else. It, of course, can be expressed in a series of nested if-then-else, therefore, constructions are equivalent. I will tell you a secret: the case is even more functional, and soon we will see it. For now an additional example. A little artificial, but yes it does not 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 means “otherwise.” When the other options in turn did not fit, otherwise would be appropriate, because it is only a synonym for True. It is not necessary to 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 will compile, but we will get a warning about the intersection of the samples for comparison:



* Main > : r

[ 1 of 1 ] Compiling Main ( H: \ Haskell \ QuestTutorial \ Quest \ QuestMain . Hs , interpreted )



H: \ Haskell \ QuestTutorial \ Quest \ QuestMain . hs: 16:24:

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

True




Recall what we do here: we are writing a quest! In any quest there are three things: locations (paths of movement), objects and player actions. How could we display a location description if we had its number? Here is the solution:



describeLocation locNumber = case locNumber of

1 -> "You are standing in the middle of the wooden table."

2 -> "You are standing behind a small wooden fence."

- Here to insert a description of other locations.

otherwise -> "Unknown location."



* Main > describeLocation 2

“You’re 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 multiline characters, the characters {- and -} are used:



describeLocation locNumber = case locNumber of

1 -> "You are standing in the middle of the wooden table."

2 -> "You are standing behind a small wooden fence."

{- Here to insert a description of other locations.

Or here.

adfsdf few fef jel jle jkjlefjaiejeo -}

otherwise -> "Unknown location."




Here you go. We met with not all the basic constructs of the Haskell language. putStrLn, show, case construction, strings, ghci - we will need all this in the future. We even wrote one function for the quest. Perhaps enough. In the second part, we will start working on the quest and will explore some more great Haskell tricks as we go. Adventures are waiting for us!



To fix, you can solve the following problems:



1. Set 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. Set 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.




Table of contents, list of references and additional information can be found in the "Welcome"

Source: https://habr.com/ru/post/120759/



All Articles