⬆️ ⬇️

Haskell Quest Tutorial - Glade

Clearing

It is a small forest path.




Content:

Greeting

Part 1 - The Threshold

Part 2 - Forest

Part 3 - Polyana

Part 4 - View of the canyon

Part 5 - Hall



Part 3,

in which we will learn magic with ADT and learn the magic transducers Show and Read.

')

In the last part, we invented various describeLocation options, and at the end we created three algebraic types - Location, Direction, Action. I mentioned the magic and amazing possibilities of ADT, but said that we will consider them later. We just inherited our types from the type class Eq, in which the operations "==" and "/ =" lie, and now ...



Do you want miracles? Well ... Let's look again at the Location type:



data Location =

Home

| Friend'sYard

| Garden

| OtherRoom - Added new constructor.

deriving ( Eq )



* Main > Home / = Friend'sYard

True

* Main > Home == OtherRoom

False




Very good! In the first part, we learned that there is a show function that translates something into a string. Let's try:



* Main> show Home



< interactive > : 1 : 1 :

No instance for ( Show Location )

arising from a use of ' show '

Possible fix: add an instance declaration for ( Show Location )

In the expression: show Home

In this equation for 'it': it = show Home




It did not work ... We have already encountered a similar error at the end of the second part. There we tried to compare two constructors, but nothing came of it, because ghci did not know how to compare them. We solved the problem by adding a “deriving (Eq)” spell at the end of the Location type, and got the “factory” comparison function "==". Can we do something like this to get the show function? We can! It is enough to inherit the Show type class :



data Location =

Home

| Friend'sYard

| Garden

| OtherRoom

deriving ( Eq , Show )



* Main > : r

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

Ok , modules loaded: Main .



* Main > show Home

"Home"

* Main > "Current location name:" ++ show Home ++ "."

“Current location name: Home.”

* Main > show Friend'sYard

"Friend'sYard"




How can this be used? Oh, in a variety of ways. Let's improve the describeLocation function now. Rewrite the last alternative (" otherwise "):



describeLocation :: Location -> String

describeLocation loc = case loc of

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

Friend'sYard -> “You ’ve been behind a small wooden fence.”

Garden -> “You are in the garden. Garden looks very well: clean, tonsured, cool and wet. "

otherwise -> "No description available for location with name" ++ show loc ++ "."




And now, without resorting to ghci, tell me: what happens if you call "describeLocation OtherRoom"? Watch where the OtherRoom constructor will go, how the case will work, which of the options will be chosen, and what line this option will return. Is it done? Check yourself:



* Main > describeLocation OtherRoom

“No description available for location with name OtherRoom.”




I, unfortunately, do not have pies for you; but if you guessed correctly, you can be proud of yourself. You just took a show function from the Show type class and converted the constructor to a string. Handsomely? Yes, I think so. Try, for example, in C ++ it is just as easy to convert an element of an enumeration into a string ...



The show function is very useful. Inherit from the Show type class the Action and Direction types. I promise you will not lose!



Type constructors, such as Home, Friend'sYard or Garden, are in fact special features that are allowed to begin with a capital letter. And since these are functions, they have a type. What will the ": type Home" command give? It's elementary, Watson.



* Main>: type Home

Home :: Location




You know, something doesn't suit me here. Look at the quotes from Zork at the beginning of each of the parts: the location name is first displayed there, and then - from the new line - the description. Let's rewrite the describeLocation function ... Yes, yes, again, don't moan like that! .. I want the location name to appear before its description. The solution "in the forehead": I just implemented the location name in the text string.



describeLocation loc = case loc of

Home -> "Home \ " You are in the middle of the wooden table.

'S 's Y Friend Y>>>>>>>

Garden -> “Garden \ n You are in the garden. Garden looks very well: clean, tonsured, cool and wet. "

otherwise -> "No description available for location with name" ++ show loc ++ "."




Work, of course, will be. If you want to pollute the description, please. I don't want to. Option number two:



describeLocation loc = case loc of

Home -> Show Loc ++ " \ n " ++ "You are standing at the wooden table."

'S behind « « «« «« «« «« «« «« «« «« «« «

Garden -> show loc ++ " \ n " ++ “You are in the garden. Garden looks very well: clean, tonsured, cool and wet. "

otherwise -> "No description available for location with name" ++ show loc ++ "."




Already better, although it adds a lot of work with all these pluses ... And to repeat - a bad form ... There is a simpler and more elegant way! Watch your hands:



describeLocation loc = show loc ++ " \ n " ++

case loc of

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

Friend'sYard -> “You ’ve been behind a small wooden fence.”

Garden -> “You are in the garden. Garden looks very well: clean, tonsured, cool and wet. "

otherwise -> "No description available for location with name" ++ show loc ++ "."




The trick is that a case construct is one big expression, from the word "case" to the end of the last alternative. We can embed case inside other expressions. In our case, case always returns a string, which means we can add it to another string. The code becomes more readable, slimmer and more beautiful. If you test all three options, you will see that they give out what you need.



* Main > describeLocation Home

"You are standing in the middle of the wooden table."

* Main > putStrLn ( describeLocation Home )

Home

You are standing in the middle of the wooden table .




The case design is definitely good. There are, however, cases when it is inconvenient. If you solved problem # 2 from the first part, you can already guess what I mean. I recall that there it was necessary to implement the following function for some x and a:



  |  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 




Function as a function, in mathematics such darkness. But try implementing it in Haskell with an if-then-else or case:



y x a = if x > 5

then log ( abs ( sin ( x ) ) )

else

if x <= 5 && a <= 3

then x ^ 2 + a ^ 2

else x / a + 7.8 * a



y 'x a = case x > 5 of

True -> log ( abs ( sin ( x ) ) )

False -> case x <= 5 && a <= 3 of

True -> x ^ 2 + a ^ 2

False -> x / a + 7.8 * a




The function is difficult to read due to several levels of nesting. Can it not be otherwise? .. Well, of course! Security expressions! And the Haskell function becomes similar to the function in mathematics. See:



y '' x a | x > 5 = log ( abs ( sin ( x ) ) )

y '' x a | x <= 5 && a <= 3 = x ^ 2 + a ^ 2

y '' x a | otherwise = x / a + 7.8 * a



- Or the same:



y '' x a | x > 5 = log ( abs ( sin ( x ) ) )

| x <= 5 && a <= 3 = x ^ 2 + a ^ 2

| otherwise = x / a + 7.8 * a




It is easy to understand that the function takes the form "log (abs (sin (x)))" if x is greater than five. Guard expression (guard) is an expression between the characters "|" and "=". For security expressions, the same laws apply as for alternatives to the case-construction: the set of expressions must be complete, and otherwise it always works.




But let's get back to designing the game. In any game there is a code where event handlers are called again and again, graphics, physics, AI are calculated. Our game is easier. The user enters a command from the keyboard - and something happens, then he re-enters the command, and again something happens, and so on. There will be about the following algorithm:



0. We explain the gaming environment:

- display the description of the current location;

- display the description of objects in the location.

1. We are waiting for the team from the player as a string.

2. We are trying to recognize the command.

3a If the command is recognized:

- we execute it;

- back to point 0.

3b. If the command is not recognized:

- give a message about it;

- back to paragraph 1.



Half point 0 is ready: this is the function “describeLocation”. We do not have objects yet, we will add them later. So, go to step 1. How to get input from the keyboard? In the first part, I talked about the function putStrLn, which prints a string in a real console; time to get acquainted with the opposite function - getLine. Consider the following spell:



run =

do

x <- getLine

putStrLn x




It's time to upgrade your Literacy and Eagle Eye skills! What happens in the run function? A few simple steps. We are waiting for a string from the keyboard (getLine); This string is associated with x; print x in a real console. And in order to link actions in a chain, the “do” keyword is used - this is a feature of the Haskell language. And now we will test:



* Main > run

Hello ! - What I introduced

Hello ! - What the putStrLn function printed

* Main > run

kjljfs

kjljfs




Once again: the getLine function asks for a string. The string is associated with the variable x, and in the next step, the function putStrLn prints x. Let's clarify by adding the "Enter command:" prompt before entering a line. Let the user see what they want from him.



run = do

putStr "Enter command:"

x <- getLine

putStrLn x



* Main > run

Enter command: Look

Look




I used the function putStr: it prints something, but the cursor on a new line does not translate. In general, here is a complete analogy with Pascal: writeLn <=> putStrLn, write <=> putStr.



You, of course, noticed that I wrote "we connect with x", but not "we appropriate x". There is no assignment in Haskell, which is why the arrow ("<-") is there, and not the assignment mark ("=", ": ="). The arrow shows where we take the result and what we associate it with. There is a significant difference between assignment and binding with far-reaching consequences. But as long as we do not use these effects, then we should not worry.




Now we need to execute the command entered by the user. To do this, we will invent a simple function “evalAction” and call it from run:



evalAction :: String -> String

evalAction strAct = "Action:" ++ strAct ++ "!"



run = do

putStr "Enter command:"

x <- getLine

putStrLn ( evalAction x )



- We are testing:



* Main > run

Enter command: Look

“Action: Look!”

* Main > run

Enter command: Quit

“Action: Quit!”




Ho ho! Our preparation, without a doubt, works! Only here evalAction accepts a string, not a special Action type. Because of this, we can pass into the function any abracadabra.



* Main > run

Enter command: lubaya _ abrakadabra

“Action: lubaya_abrakadabra!”




We are misled. There is no action such as lubaya_abrakadabra ... We have somehow done the trick with replacing the string with the Location in the describeLocation function. What if we repeat it here? Replace the line with Action:



evalAction :: Action -> String

evalAction act = "Action:" ++ show act ++ "!"



run = do

putStr "Enter command:"

x <- getLine

putStrLn ( evalAction x )




It seems that evalAction looks good: you cannot convey abracadabra in principle, and the constructor will be processed by anyone, albeit in a primitive way. But this code is a bit problematic: it will not compile.



* Main > : r

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



H: \ Haskell \ QuestTutorial \ Quest \ QuestMain . hs: 46 : 38 :

Couldn't match expected type 'Action' with actual type ' [ Char ] '

Expected type : Action

Actual type : String

In the first argument of 'evalAction' , namely 'x'

In the first argument of ' putStrLn ' , namely ' ( evalAction x ) '

Failed , modules loaded: none .




GHCi tells us that the types do not match. The evalAction function wants an Action type, not a String at all. We made a mistake here: “putStrLn (evalAction x)”. Heh ... But this idea was good! ..



When programming in Haskell, you will often see typing errors. There is nothing wrong with that; it says in them where the discrepancy is, what was expected to receive (Expected type), and what they actually received (Actual type). It is impossible to compile the wrong code. With a large refactoring, up to several dozens of errors can occur, or even more, and you will have to fix them all, one, another, the third ... When the errors finally disappear, the code with a high probability will work exactly as you expect. And this, I tell you, is very, very cool!




To get an Action type constructor from the “x” line, there are several solutions. To begin with, let's try to come up with the convertStringToAction function. Question on the "top three": what will be the type of the function that converts a String into an Action? It is obvious!



convertStringToAction :: String -> Action




The easiest way is to use case. In the last alternative, we will re-insure and return Quit, if suddenly something.



convertStringToAction :: String -> Action

convertStringToAction str = case str of

“Look” -> Look

"New" -> New

otherwise -> Quit




It is best to insert it when calling evalAction in the run function. Like this:



- We process the action.

evalAction :: Action -> String

evalAction act = "Action:" ++ show act ++ "!"



- Convert a string to Action

convertStringToAction :: String -> Action

convertStringToAction str = case str of

“Look” -> Look

"New" -> New

otherwise -> Quit



- We receive input from the keyboard, convert it into action, call the handler, display the result.

run = do

putStr "Enter command:"

x <- getLine

putStrLn ( evalAction ( convertStringToAction x ) )




And now let's check:



* Main > : r

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

Ok , modules loaded: Main .



* Main > run

Enter command: Look

Action: Look !

* Main > run

Enter command: dfdf

Action: Quit !




Well, this is a victory! Now the evalAction function does not work with a string, but with an Action. Everything is good, but ... Do you see how much work is ahead when we want to add another command besides Look? We have ten of them in type: Look, Go, Inventory, Take, Drop, Investigate, Quit, Save, Load, New, - and others may appear. And what, again and again to expand the case-construction of the function convertStringToAction? I do not really want ...



By the way, food for thought: two more ways to write the function convertStringToAction. Theses, without explanation.



- Security expressions (guards)

convertStringToAction ' :: String -> Action

convertStringToAction 'str | str == "Look" = Look

| str == "New" = New

| otherwise = Quit



- Pattern matching (pattern matching)

convertStringToAction '' :: String -> Action

convertStringToAction '' "Look" = Look

convertStringToAction '' "New" = New

convertStringToAction '' _ = Quit



- Check in ghci

* Main > convertStringToAction ' "New"

New

* Main > convertStringToAction '' "New"

New




“And what, again and again to expand the case-construction of the function convertStringToAction? I don’t really want to ... ”- what kind of despair? .. Haskell is a lazy language, and a real programmer should also be lazy not to write extra code where it is not needed. Do not want to expand the case? Do not need! What will we do? Get ready! We are learning a new spell! Write down: " class of types Read, in which functions read and reads lie". We inherit all our ADT types from Read and see what it will lead to.



data Location =

Home

...

deriving ( Eq , Show , Read )



data direction =

North

...

deriving ( Eq , Show , Read )



data Action =

Look

...

deriving ( Eq , Show , Read )




To get a feel for the read function, let's play a little in ghci. Two examples for comparison:



* Main > describeLocation Home

"You are standing in the middle of the wooden table."



* Main > describeLocation ( read "Home" )

"You are standing in the middle of the wooden table."




What conclusion can be made? The describeLocation function has not changed, it still needs the Location type. In the first example, we pass the constructor, and in the second, we get it from the string “Home”.



* Main > describeLocation ( read ( show Home ) )

"You are standing in the middle of the wooden table."




The read function takes a string and tries to parse it to the type that is needed in this place. Where does read know about types? He takes them from the surrounding expressions. In this case, he sees that (read "Home") is a parameter of the describeLocation function, and the type of the parameter is strictly set. There are cases when the type to take nowhere, but very rarely. A simple example: if you call 'read' Home 'in ghci, the compiler will not understand us:



* Main > read "Home"



< interactive > : 1 : 1 :

Ambiguous type variable 'a0' in the constraint:

( Read a0 ) arising from a use of ' read '

Possible fix: add a type signature that fixes these type variable ( s )

In the expression: read "Home"

In an equation for 'it': it = read "Home"




But we can help him by explicitly specifying the type using a special notation:



* Main > read "Home" :: Location

Home

* Main > read "5.5" :: Float

5.5

* Main > read "True" :: Bool




Magic, isn't it? Introducing read into the convertStringToAction function, we get a shorter and more functional code.



- We process the action.

evalAction :: Action -> String

evalAction act = "Action:" ++ show act ++ "!"



- Convert a string to Action

convertStringToAction :: String -> Action

convertStringToAction str = read str



- We receive input from the keyboard, convert it into action, call the handler, display the result.

run = do

putStr "Enter command:"

x <- getLine

putStrLn ( evalAction ( convertStringToAction x ) )



- Check in ghci:

* Main > run

Enter command: Look

Action: Look !

* Main > run

Enter command: Quit

Action: Quit !




Unfortunately, read has one major drawback: if we pass on the abracadabra, we will get an exception, and the program will end.



* Main > run

Enter command: abrakadabra

Action: *** Exception: Prelude . read : no parse




Dont be upset! We have a more secure reads function. Of course, I will show you how to use it, but I will leave explanations for the future. And today - "Action: Quit!". Rest and learn spells.



convertStringToAction :: String -> Action

convertStringToAction str = case reads str of

[ ( x , _ ) ] -> x

_ -> Quit



* Main > run

Enter command: abrakadabra

Action: Quit !




Tasks for fixing.



1. Objects of the quest and action Investigate.

- Make an “Object” ADT, add any objects there.

- Create a function describeObject, which gives a description of the object.

- Create an investigate function that queries the user for the name of the object and displays a description of this object.



2. The program "Dumb calculator."

There are integer operations "Add", "Subtract" and "Multiply".

Write a program that requires the user to integer1, then requires an integer operation, then requires an integer2. When this is all received, the program must perform the corresponding operation on the numbers and output the result.




Sources to this part .



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

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



All Articles