TEXTFP
TEXTFP
In this video we're going to look at setting up the word game grid.
We'll see how to declare a grade in Haskell.
We'll look at how to print it to the screen in order to do that.
We'll see how to discover more about Haskell functions using the Hagele search engine
and we'll see
the lines function here is our label dot H.S. module that we started to work on in the
previous video.
I've declared a grid here.
Now let's have a look at how we've declared it as before we can see that lists in Haskell
are declared
using the square brackets and they're separated by commas.
Now this is a very typical Haskell formatting convention where you align the commas
and the opening
and closing braces on the same column.
You don't have to type your code in this way but it is fairly common.
The reason we've put these on different lines is so that the strings will line up making
this look like
a word grid and you can see here that we have the names of the programming
languages that we saw in
the instructors video in this section and here is Haskell is Python and here are some of
the diagonally
written languages like Ruby.
And you'll notice that instead of filling out the whole grid with a jumble of random
letters I've made
all of the letters that are not the name of a programming language be the letter
underscore.
And the reason for that is really so that we can see what's going on and we can fill those
in later
with random letters and as well as declaring this grid.
I've also declared a list of languages and again exactly like the grid above.
This is also a list of strings.
But in this case the strings are of different lengths.
This isn't a grid.
This is just a list of strings.
Now as we've added these declarations languages and grid will need to export them
from here so that
they can be seen in the executable.
So if I add grid and languages like so then when I run stack JCI I can type grid and
languages and I
will see my list now in the case of the languages list this is perfectly reasonable.
We can say that we have a list of strings but in the case of a grid we'd really like to do is
to see
that formatted as a block rather than a comma separated list of lines.
So really what we want to do now is to figure out how to format that.
So we could look at writing a function to join up these lines separated by the new line
character.
But before we do that it does sound like the kind of thing that might exist in Haskell
standard library.
It would be interesting to find out how do we discover that.
Now obviously we know how to get the type of an existing function.
So for example the length function we know is a foldable usually a list to an and.
What we really want to do now is knowing that we have a list of strings and we want to
get an output
which is a string.
We want to somehow get the definition of functions that match that type.
And there is a very handy tool called Hagele.
No not Google Google.
And if I search for that in appropriately Google Google will helpfully find me results for
Google.
However I can now search instead for Hagele and having done that we can see that on
Haskel dot org there
is a link to Huckle a Haskell API search engine.
So let's click on that.
And now we can type in our type signature and we can see if there are any functions
that match that
signature to see here that there are a whole load of suggestions.
Some of them don't quite match.
It's being helpful by giving us signatures which are related.
So for example here is concat which would take a list of generic arrays and return a
single generic
array concatenating a list of lists.
Now that seems quite interesting we could have a look at that.
So if we were to try concat grid we'll see that it does concatenate them.
However it doesn't add the new line at the end of it.
So let's see if there are other functions and the first function in the list of results here
looks really
promising.
It joins lines after pending a terminating new line to each.
And that's exactly what we want to do.
So if we try an lines grid we'll see that again we have a string which has joined up all of
our lines
but this time it's added slash n in between each line and that's the new line.
So if we were to put stra learn put string with New Line on lines grid that will output a
grade exactly
in the way that we want it to.
So we could remove these functions here.
As we no longer want them and we could create a new function called format grid and
that will take a
list of strings and return a string and of course the definition of format grid lines is
simply on lines.
Lines.
And as we saw in previous videos we can use the point free style and simply say that
format grid is
in fact on lines we could simply not bother defining this function and use the function
on lines.
And really all we're doing here is saying format grid is an alias name that would help us
remember what
we're doing better than something more generic like on lines or so later on when we
change the definition
of the grid.
Then having a function named format great is going to come in helpful.
And so we might want to create a new function which takes an array of strings and
outputs them the output
grid with a grid.
Would simply do put stra learn put straight line with format grid.
Great and of course having added this function output grid we need to export it from
Lepto H.S. as well.
Now that we have exported output grid in the main that H.S. we could run output grid
against the grid
variable that we've defined in H.S. And now our word game will simply outputs the grid
to the screen.
And the next video will look at how to search the grid for the words in the wordlist.
It in this video we're going to search for words in the grid that we created in the
previous videos
in order to do that.
We're going to look at how to find the string inside another string using functions like is
infix solve.
We're going to look at how to transform this using functions map and reverse and we'll
see how to use
the boolean and maybe data types in the previous video.
We saw how to output the grid.
And of course we also defined a list of words to search for.
In this case programming languages and we want to take these words like Haskell and
search for them
in the grid from left to right from right to left top to bottom bottom to top and of
course diagonally.
And that right now seems like quite an ambitious task.
So let's simplify it as much as possible and say that for now we look for a word just from
left to right.
And so in the case of Haskell we can see that looking for it on this line wouldn't find the
result.
Nor on this line but when we get to this line we actually would find the word here.
So that's what we're going to do in this next section.
So if we open our code we can see that we might be wanting to add a function find
word.
And this again would take a grid and then we can refer to it as a list of strings.
At this point it seems like it might be a nice idea as we're using this pattern in several
places to
actually give it a synonym and we can say that type grid is a synonym for a list of strings.
So here where we outputting the grid we can simply taking a grid and where we're
formatting it we can
take a grid.
And again here we can take a grid type.
This is exactly the same as what we were doing before.
But the word grid gives us a little bit more information to us as a reader a human reader
of the source
code as to what's going on.
So we start off with the grid and we take a string that we're searching for and we want
to return a
value.
And we're either going to find the word or we're not.
So it looks like we want to return a boolean value.
So how would we define that.
We're going to look for the word on every line.
And so I think we're not yet ready to do that.
First of all we want to find the word on a single line and then we can look at whether we
found the
word on the whole grid.
So let's do this simpler function fine word in line and this is going to take a string being
the word
that we're searching for a string.
The line that we're searching and it's going to output a boolean of course we need to
add the function
to the list of exports at the top of the module.
So the definition of this we're going to take the word and the line and we're going to
find out whether
that word is contained within the line.
Now looking for a word incise another word is a fairly common thing to do.
It might be worth seeing whether there is a function for that in Haskell standard library.
So let's have a look at the search engine again and we can see whether there is a
function that takes
a string and another string and returns a boolean and we can see here that Google isn't
giving us specific
results for string to string to boolean but it is giving us results for a list of a list of A and
a
boolean.
And of course the string is simply a list of characters.
So is infix of takes two lists and returns true if the first list is contained inside the second
list
and we can see that this function is defined in data dot list.
So let's see if this function does what we want.
So if we import data at least in GHC II we can see is infix of will find the word inside the
line that
we're looking for now because this is a function that takes two arguments one on the
left and one on
the right there is another style of calling functions that is quite handy and that's to
surround it
in back ticks.
And that allows us to put the function in the middle of the two arguments so we could
write like so.
So here it looks like what we want to do is to write word is infix of line and you can see
here that
that reads quite nicely.
The function will return whether the word is infix of is contained in the line.
So in order to compile this we would need to import data Dart's list and we only want
the function is
infix of.
So now we could ask whether we can find words in line like say.
Now you might have noticed that if we formatted this in the original way with is infix of
used as a
simple function is infix of word line you can see that both is infix of and find word in line
have exactly
the same list of parameters.
And that means that using the reduction that we used before we can first of all remove
line because
it's contained the right of both sides of the function definition and we can remove word.
So really this means that find word in line is simply a synonym for is infix of.
And we could simply use is infix of.
But it does make sense to use the Find word inline line function as it documents our
intent better.
But of course we want to be able to know whether we can find the word not just on a
single line but
on any line.
If we look at our grid we can get an element of the grid using the double exclamation
mark operator.
So we could see whether Haskell is contained in the first line line 0 in line at index 1 and
that index
2.
And at this point we can see that it is in fact found here.
Now what we want to do is to check whether the word is found on every line in the grid.
And we can do that by using the map function.
Now if we imagine a simple function like length which takes a string and returns an
integer the length
of that string map can be used to take a function like length and a list of strings for
example and
return a list of integers.
So for example here we might be mapping the function length over a list of strings and
returning a list
of integer lengths of those strings.
So we know that the grid is a list of lines.
So we could apply a function to that.
So for example mapping the length function over the grid will return a list of integers.
And of course each of those is 15 because it's a square grid.
So if we run the find word in line function over the grid that won't quite work because
Find word in
line takes two strings.
The word that we're looking for and the line that it's in.
So what we need to do is to put this in parentheses and we can return a function that
will find the
word Haskel in a line and map that over the grid.
And as you can see here we get a list of boolean values most of which are false.
But one of them is true.
Now of course what we want to know is where any of these values.
True.
So if we go back to Hugo we can see whether there's a function that takes a list of
booleans and returns
a single boolean
and you can see here that we have two very likely looking candidates the and function
and the OR function
and we can see what would happen with both of those.
So and true false returns False True true would be true.
So that's not exactly what we want because any one of the results was a true value.
So Or is the function that we want.
So if we go back to our map here we could run all over the map finding the word in line
on the grid
and we can see that returns a true value.
And if we enter another word that we know isn't on the grid.
That word isn't found.
And of course if we return another word that is on the grid but which he hasn't written
left to right
at the moment that what we found because we haven't catered for it.
So let's go back to our code and write out the definition for the find word function.
And of course we'll have to take in those parameters grid and word
Let's see if that works.
OK.
So this line now is beginning to look a little bit unwieldy with all of these parentheses
and one thing
we can do because this parenthesis goes from here all the way to the end of the line.
We can replace it with the dollar character
and we get exactly the same result.
Now you don't have to write in that style but it's useful to recognize.
So we've seen how to search for words from left to right and we're simply using a
function which is
a synonym for is infix of.
Now we could write a function to search the grid from right to left.
But in fact it would be much simpler to simply use the same function but transform the
grid.
So let's see what I mean by that
if we were to reverse one of these lines
we could search that line for a programming language.
So if we take a line that we know has the name of Burgman language written backwards
then we'd be able
to find that using exactly the same function.
And if we try to reverse the grids
obviously we would just get the grade from top to bottom the wrong way round and
that won't help us
find any new words within there.
What we actually want to do is to reverse each line.
So we can see that in this case we could use the map function.
So what map reverse grid would do would be to apply reverse to every line within the
grid.
And return a new grid and say you can see here that we have the language Perl now
visible.
So if we have a look at the type of word.
So if we try to run find word on one of these reversed grids we would now get a true
value.
So in order to search the grid from both left right and right to left instead of only
searching within
just the untransformed grid we could create a new grid that contains both all the lines
within grid
and the new grid created by mapping the reverse function over every line of the grid in
the expression
or map find word in line word over lines.
So as you can see we now have a find word function that will take a grid and a word and
will return
a true value if it can find that word either running from left to right or from right to left.
So it would be nice to know which of these words can we find in the grid.
So now that we know about map we know that we could run some function against
languages and we could
find out those words and we know that we have a fine word function.
And this takes a great let's see what happens when you run this and this result isn't
especially useful
because although we get a list of false and true values and we can see here that we've
matched three
languages against the grid it's a little annoying to see which ones obviously we can see
that basic
wasn't found Koeppel wasn't found C-Sharp wasn't found Haskell was but a much nicer
outputs would actually
be just the list of languages that were found and so it seems like perhaps boolean isn't
in fact the
best return result for find words.
So let's have a look at how we could write this.
So if we were to create a new function find words that takes a grid and a list of strings in
this case
I do mean a list of strings rather than a grid and returns a list of strings.
So we know that simply doing this won't work because in fact that would return a list of
bool.
So we need to do something else.
And the problem here is of course we're returning a boolean value from find word in
line and we're passing
on that boolean value in find where I'd be what we could do here is instead of returning
a boolean we
already have the string we have the word.
At this point.
So we could return maybe string.
And if you remember maybe allows us to write for example just Haskell or nothing and
you can think of
nothing as being false and just Haskell as being a true value.
So instead of returning this expression we can assign it to a new variable and we could
return a value
based on whether we did in fact find the words.
So if it's found we'd return just words.
And if it wasn't we could return nothing.
And at this point let's comment out the signature and we can see what result we get.
We also need to find words to the list of exports.
And now we get a much more useful list.
We have the expressions.
Nothing.
For all the words that were not found and just Haskell just Perl and in fact just ph.
P for those words that were found.
So that's quite nice but what we really want would be only those words which have just
values.
So how can we get from one to the other.
And again you may be spotting a pattern here.
I'm going to recommend that we go to Google and find out if there's anything in the
standard library.
So if we had a list of maybe something we wanted to get a list of just something and we
can see that
there is a function cat maybes that takes a list of maybes and returns all the just values.
And that's exactly what we want.
So this function is defined in data.
Maybe so let's import that function.
And as you can see I'll find words routine now returns only those languages that we can
find on the
grid in the next video.
We're going to continue the work we've been doing here and see how to search the
grid in all directions
from top to bottom bottom to top and diagonally.
In the previous video we saw how to search the grade for words from left to right and
right to left
in this video we're going to extend that work and look at how to search in all directions
in this video.
We're going to use the reverse function that we've already seen.
And also the transpose function and we'll see how to write our own recursive functions
to manipulate
the grid in order to search diagonally.
So as we can see if we look at the grid we've already searched from left to right and
right to left
and by using the reverse function what we want to do now is to search from top to
bottom.
We'd like to search a line containing the characters of the first column the second
column the third
column and the fourth column and so on.
It's a good idea as always to look at high school and see what kind of functions are
available.
So we know that we have a grid which has a list of a list of characters and we want to
return another
grid but with the characters in a different order.
So let's see what kind of existing functions there are to map from one sort of grid to
another great.
And the first one of these looks very promising.
It's transpose and you can see that it transposes the rows and columns of its arguments.
Now another thing you may wish to do is to click through to the data get less
documentation so it's
worth looking through the documentation for important modules like data ductless to
see what kind of
things are available.
This will help in future when you're looking for a particular tool to apply to a problem
you've got.
But for now let's have a look at the transpose function.
Again we need to import data not list.
And we can see of course that it goes from a grid to another grid.
So if we ran transpose grids we can see already here that words like C-Sharp and Python
now are jumping
out at us.
So let's try doing an output grid of the normal transposed grid and you can see here
that Haskell and
Perl and ph P are running.
And if we run that on the transposed grid we'd still see Haskell running from top to
bottom.
And now of course C-Sharp and pif and running from left to right.
So let's have a look at how we would modify the code to handle this.
So as you can see we've already got the variable called lines that contains the grid plus
the reversed
grid.
Let's extract that into a function
and we can see that that has exactly the same effect as before.
We've simply refactored that code.
So now what we might want to do is to add the transposed grid and reverse that as well.
This is beginning to get a little bit unwieldy.
So let's start to extract some more variables here.
The horizontal is essentially the grid.
The vertical is the great transposed.
So all the lines is the horizontal Plus the vertical
and we can now return the lines and the reversed lines.
Of course know that to do this we need to import transpose.
Now we've already imported data at least but only the is infix of symbol.
So now we need to add transpose and now when we call find words grid languages we
can see a whole load
of new languages appearing.
Obviously we haven't yet found all of them but we are now doing from left to right right
to left top
to bottom and bottom to top so we've seen the grade transposed.
But now we want to search diagonals as well.
So for example we might want to search the line containing just the letter X and then I L
and so on.
Now before we continue it's really worth having a think about how you might want to
do this.
I spent quite a lot of time figuring out what my approach would be.
Do feel free to post the video now and see if you can write a function that will turn this
into diagonals.
Now don't worry too much if you don't find an obvious way to do this.
I took a fair bit of time figuring out a good approach for this.
If we simply offset each of the rows we could now read down the columns like so.
And in order to do that we could simply use the transpose function that we've already
used.
So the first thing that we'd want to do is to write a function that skews a grade by
prepending it with
some character.
So let's have a look at creating a function call skew and that will take a grid and it will
return a
skewed grid that we can easily then call transpose on.
So if we look at the grid we can see that we could easily enter line by prepending it with
another character.
So for example if we wanted to prepend with the character star we could do like this.
Using the colon operator.
And that would put the star at the beginning of the line.
So if we had a function called prepend that took a character and a line
we could now map that function over a list of lines.
But of course we don't want to map over every line we want to keep the first line as it is
add a single
asterisk to the second line to the following line.
Three to the one after that and so on.
So it's looking very much like we want to have a recursive algorithm and so when you're
writing a recursive
algorithm you always need to remember the base case.
If we had an empty grid we would simply return an empty grid and the other option is
that we would have
a grid which contains a first line we'll call the L and a list of lines.
We don't want to skew the first line so we'll return that unchanged but we want to
follow it with a
list of lines that have been indented.
And again we need to define a function for indent Instead of defining that at the top of
this function.
I'm going to define that in a WHERE clause.
This is very similar to let.
There are some differences but it's largely a question of style and preference.
I'm going to prepend with the underscore because that's the empty character that we're
using for the
rest of the list.
So let's see what that looks like.
Again we have to export that.
So let's reload and see what this looks like.
So that doesn't look very promising but you can see here that the first line is now
shorter than all
the rest of them because the first line has been left unchanged and all the others have
an extra character
at the beginning.
But that's no good because we wanted each successive line to have an extra underscore
before it.
So instead of simply mapping and over the rest of the lines we want to recurse into the
skew function.
Now we can see that each successive line gets an extra character of indentation.
Now you can see for example that scheme can be read from top to bottom whereas C-
Sharp that was previously
from top to bottom is now running diagonally so we could now run transpose skew grid
and we would see
something that doesn't look very much like a grid at all but does have scheme and
COBOL running backwards
in it.
So this is looking like we'd be able to plug it into our get lines function and suddenly a
whole lot
of new words are going to show up in our find words routine.
So diagonal is transpose of skew great and the lines are not just horizontal and vertical
but also the
diagonal lines.
And you can see that scheme and COBOL are also found but that's not all the languages
were missing basic
and Ruby.
So why is that.
So we can see that scheme and COBOL were running in this direction.
But to Ruby and basic We're running in the opposite direction.
So we're going to need to do a different transpose.
So if we call this diagonal one we now need a diagonal to.
So let's see if this was the horizontal grid and this was the vertical
whether this will give us the results that we want.
And we are still missing basic and Ruby.
So it turns out that we get the diagonals in this order.
If we output the transposed grade we get the diagonals in this order.
And that still contains scheme and COBOL.
So what we actually need to do is to
reverse the grid like this.
And at this point the diagonalize is going to find Ruby and basic.
And now we can see basic Ruby in our list.
So that's looking a little bit awkward.
So what we really want is a new function which we call diagonalize which will take a
grade and return
a grant.
It'll essentially do what we can see above.
And as a final little clean up this pattern here of calling a function skew on a parameter
and then
calling another function like transpose on that result is very common within Haskell and
it referred
to as composing those two functions together and that can be done using the dot
operator.
And now that we've rewritten that with a composed function you can see that the
parameter a grid is
on the right hand side of.
Here and here.
And so we can delete it.
Now delete those parentheses and you can see that diagonalize is the composed
function of transpose
and skew
and the next video we're going to look at.
Unit testing all of these functions that we've created using the H spec library.
In this video we're going to look at making the game playable Previously we've been
building up functions
and data types that we can use in order to play a word game.
And now we're going to put together those pieces.
So we're going to model the game data type.
We're going to look at inputs and outputs and in Haskell that means using what's called
the IO or not.
We're going to have a quick look at the controlling buffering
so we previously created a data type for cells that now create the idea of a game.
So of course the game data type needs a constructor game.
I would need to think about what kinds of things we're going to store in that
so of course we know that we need a grid of cells and we might think of a list of words
or a list of
strings that are the words contained in the game and we might want an integer for
example for the score.
But if there are other ways that we could do this because if we know which of the words
have been found
we can calculate the score from that so perhaps we don't need to actually store an
integer value at
all.
And again do we really want a list of strings or do we want a dictionary type that maps
those strings
to the position of the words found on the grid.
So let's have a look.
In GHC I at the data type data dot map and you'll see that this is actually located in
another package
called containers.
So first of all we need to open up our Kabyle file
and we'll need to add containers as a dependency of our code.
And now we can import data dump map and we can see what kind of functions it
contains.
And you can see that there are a large number of functions to deal with this map or
dictionary type.
So of course we would import a library normally like so import data type map but have
a look at the
functions that have been exported map and look up many of these overlap with names
of functions that
we've already used.
So of course we could refer to those using syntax data dot map dot map for example
but that's a little
bit awkward.
So Haskell gives us the opportunity of importing a library qualified and then we can give
it a short
name to use instead.
So we've imported dated a map qualified as am and this means we could look at
changing our list of strings
into a map which we have to refer to as dot map and the map has a specialized type
where you have to
give the type of the keys and the type of the values.
And we might want to map the string being the key to the value which was which is a
list of cells.
And of course it's a maybe a list of cells because initially that value would be nothing
and if and
when the word is found in the grid we turn it into a just a list of cells.
So in order to test this will lead to export game.
And of course it's constructor game.
And we can see the type of it in GHC.
And of course actually in putting the grid cell on the map is a little bit awkward.
So we can use our existing grid with chord's function to create the list of cells
but how are we going to create the map of strings.
And this is looking a little bit awkward.
So instead let's create a helper function that will create a new game we can call it make
a game and
instead of taking a grid with coordinates why don't we just pass in a grid of char
because that's how
as the author of a new word puzzle we want to be thinking about the game and we can
pass in a simple
list of strings exactly as we had before.
And out of that we can return a new game.
And within this function we can then start to create the data types that the game
construct needs.
So we can assign GWC using our group with codes function how we can create a
dictionary just for now
to check that this works.
Let's create an empty dictionary using the m dot empty function
in GHC I we've imported the library unqualified.
So we're going to refer to functions prefacing them with the data dump map and you
can see that data
dot map.
Empty returns the value from list empty list.
And that's the library's way of representing an empty list.
So we can use this function now to return a valid game object.
And let's derive just the show type class in order to be able to conveniently explore the
type from
GHC I.
So now we can run make game against our existing values and we can see that it does
in fact create a
game with a list of cells and an empty dictionary.
But of course we don't want an empty dictionary.
We actually want a dictionary that contains all of the words each of them pointing to
nothing.
So if let's have a look at the from list function that we've seen
so we know it takes a list and we've only seen it with an empty list.
But in fact it takes a list of tuples of a key and a value.
So instead of empty we could have called from list an empty list or we can actually call
that with a
valid list.
So let's create that list
and we need to create it from words.
And essentially we need a new function which will generate tuples out of those words.
And so we can create that in line and we want to have the word is the key.
And they may be value as the value if we rerun our game function we can see that that
has created a
map from list with the names of each of the words and nothing.
Now we may want to extract the grade and the words from our data type and we can
write these functions
game grade and game words that do exactly that using pattern matching because a
grade is made up of
a grade and words.
But in fact this is such a common task for Haskell's data types that there is another more
convenient
syntax for it.
Let's see what that looks like.
If we now put the types inside curly braces
and we give them a name and the double colon syntax to indicate the type we now
need to separate them
with a comma.
Our code will work exactly as it did before.
But it will also declare these new functions game grade and game words and we can
export those in our
exports list.
We can also remove the export of the game function itself because we already have
make a game.
There's no reason for the user of our code to ever directly call the game data type.
You can see that make game of course still works and we can now use game words to
extract just the dictionary.
So if we assign the dictionary to a new variable we can now use the lookup function
within data dot
map.
So let's see what that looks like.
We can see that it takes a key a dictionary a map and returns a maybe value.
Let's see why that returns a maybe value.
So if we call look up let's look up Haskell in our dictionary we get a value just nothing
which looks
a little bit odd.
Let's look up a word that doesn't exist and we see that we get nothing.
And this shows us that Haskell actually differentiates between the idea that a word was
found in the
dictionary and its value happens to be nothing from the concept that a word wasn't
found at all which
would then be simply nothing.
And this is very convenient if you used dictionary types in other languages.
Without that you may have come across situations where you have to make that
distinction yourself and
it can be quite awkward.
So let's look at some of the other functions that are available.
The function keys returns all of the keys currently present in the dictionary and of course
that's a
list and we can call functions like length on that list.
And that's of course the precise number of words that can be found within the word
great.
And if we call Elam's we get all of the values let's import data that may be so that we can
use the
cat maybe's function and you can see that right now that we have a whole list of
nothings as our values
the length of cat maybes of that is zero.
And that happens to be effectively what our current score is.
So let's use those two concepts to create two new functions.
So our total words function will take in the game and return an int.
So we know we need to call game words on the game and we can start constructing our
pipeline of functions
backwards.
We want to say call keys on it.
And finally length
and we can copy that to great a very similar function for school.
So of course we want to get the Elam's instead of the keys.
And we do want to run kept maybes so that we get only the just values which are the
values that have
actually been solved and let's explore both of those functions now.
And we can see that the score is currently 0 and the total words is 10.
So let's move on to creating a function that will actually play a game.
So we know it would take in a game and it will take a string which is our move.
The word that we're searching for.
And here's the interesting thing.
We're going to return a new game so let's see what that actually looks like.
So we know we can extract the grade using our game grade function and now we can
bring in all of those
functions that we created in the previous videos so we can use find word to search the
grade for that
new words.
And of course found word is either going to be a just value or a nothing value.
So again we can call case on that if the word wasn't found then we can simply return
game because the
state hasn't changed at all.
If we found the words
so that's a list of cells
that we want to return a new game and a way of doing that would be to
update the dictionary.
So let's get
take it into a variable and then insert the word into the new dictionary.
Of course we need to give the word and a value.
And of course we know that found word is a value of the right type say just value.
And now we can create a new game of course the grid is the same as it was before.
But the dictionary is now Newtek it and then we can return our new game
so let's export our new play game function and have a look at that in GHC.
So you can see here that Haskel in the dictionary now has a just value which is a list of
cells whereas
And if we call play a game on the result of that you get a new game that contains just
values for Haskell
and Ruby but nothing values for all of the other languages.
Now this isn't a particularly pleasant or convenient user interface for playing a word
game.
And we're not going to make our players use that.
So let's see what we can do to improve things.
So the first thing we want to do is to actually output the grid in some kind of useable
way.
And we've already seen that we can output a grid.
So we might want to use pattern matching here.
And simply call format grid on the grid as always let's explore how a new function
can see the format game returns a string separated by new lines.
And if we run put straight line putzed return on that's then we see our output grid so
that's only part
of the status because we're not showing the score.
So let's
expand this string by adding some new lines
for those on the other line
and we can also show the score because of course score is an integer.
So we need to convert it into a string in order to be able to concatenate it.
So we can call our function that
we want to refer to game.
So we using our ATS pattern and then we can show you the total words as well.
And now you can see that the grade is shown and the score 0 out of 10
we can now call that on the results of playing the game with the words.
And you can see that we now have a score of 1 out of 10
and in fact our at pattern matching didn't turn out to be useful in this case.
So I'm simply going to remove the pattern matching
and of course we do want the grid extracted.
So let's just sign that and use our game grade function.
So now we can simplify play a game as well and we can see that new game we simply
returned.
So let's just do that.
There's no point to assigning it to another variable and another minor tweak is that
instead of creating
an entirely new game using the game constructor itself we can actually update an
existing data type
by simply assigning game words is new dict in curly braces.
This of course does exactly the same it does return a new game value but it's a little bit
more convenient
as syntax and works exactly the same way.
So now let's look at our main dot H.S..
So let's start off by creating a game
and now calling format own game instead of output grid.
Again we're going to use string line.
And now that we want to make
this function more complicated we can take advantage of the fact that it's within a
monad.
I'm honored by using the do notation that we saw when writing tests before so of
course we don't need
the in keyword within a do notation and we can add additional string outputs.
We can ask.
Please enter word.
Now we can ask for word so when we will look at the list monad we use the left arrow
notation to iterate
over every item of the list.
Now each monad works in a different way and in the case of the IO monad the left
arrow is used to get
some piece of information out of the environment.
So you might be for example getting something from the file system all the time or a
random number.
And in this case we can ask for user input from the keyboard using the getline function
and just for
now let's simply output the string that the user entered will need to separate that from
put in line
with the dollar character.
Or use in parentheses
and you can see that when I prompted for a word.
And once we typed that in we get the text you entered Haskel now we'd rather have the
input on the line
where we asked for it.
So instead of string line just simply put the strings put str.
And now instead of simply outputting the word that we entered Let's actually use our
play game function.
Now we're not calling an IO action so we'll simply use let
to create a new game.
And now we can use the same technique we used above to output the new game.
Now if we run main Again we can enter different words
and we get either 0 out of 10 or 1 out of 10 depending on whether we enter the word
that was found on
the grid.
But of course this number never goes up because we're simply invoking the function
again and everything
starts again from zero.
So now we want to repeat.
And in Haskell the easiest way to do this is to recurse into a function call and rather than
the simply
recursing into main Let's create an entirely new function and we can call it play turn and
we can use
the do notation still because play turn will in fact still be in the IO monad.
So now from Plato and we can recurse into the same function this time of parsing in
New game instead
of game
and now we keep coming back to our prompt.
And as we type the names of different programming languages our score increases.
And if we type in words that don't exist our score doesn't.
And you can see here that if we type in a word twice we don't get scored twice because
it's already
in the map.
But look at this.
If I type in part of the word S.O.P or Ha x then my score increases but also so does the
number of total
words even Bresson return increases the score.
Of course typing these partial words again doesn't increase the school because what's
happening is that
they are being added to our map and once they're there then they can't be at it again.
And of course any set of adjacent characters in any of the eight directions is currently
being counted
as a word.
So let's press control-C to exit that game.
And let's fix our code.
So we're going to create a new variant of our function and we could use a god
expression where the word
is not a member of game words game.
So the word is not in the dictionary then we simply return game.
And because that's the first variant of the function that God runs first.
And so of course any invalid words will simply be ignored.
And valid ones still score points.
So if you type in all of the names of languages on our grid we're still asked to enter a
word and of
course that's because we never added a condition
that would make our game stop.
So let's Control-C out of that and let's make sure that we don't play this word game for
the rest of
eternity and we can still use our control structures like expressions
and in the case of completed which is a function we haven't yet defined we would
output Congratulations.
Otherwise we would recurse into play.
And.
So let's add that completed function
and of course this will return a boolean.
So a yes or no value.
True or false.
And what's the criteria for completion.
Well the score of the game is the same as the total number of words in the game.
So again we need to export this new function
and that's carried through again if we enter all of the words we were still asked for word.
But on the next time round we finally get our congratulations message.
So we have a very small bag.
Let's have a quick look at the code and see if we can resolve that
and can you see the bug here.
I entered game instead of new game.
So of course the completion criteria didn't occur until the following round.
Let's look now at compiling our code and running it without the interpreter.
As you remember the executable is called Words and we can execute it by running stack
exec words.
And this looks a little bit odd.
There's no prompt here.
The grids there the scores there but where's our prompt.
So let's see if we can type in a word typed in Haskell press return and now we can see
our prompts and
the output of the grid looks a little bit odd.
And the score has increased so our game is still working correctly but the output has
gone a little
bit skew wiff.
So I press control-C to accept that.
So there's nothing wrong with our code as such but it turns out that GHC I and GHC
compiled Haskell
both treat buffering in slightly different ways.
So we need to use a function h set buffering to set buffering of standouts to no
buffering.
And of course if we run that we get an error because a set buffering isn't in scope.
So we simply need to import one of the call libraries call system that either and now
would be compile
and rerun stack exec words our input and output all occur in the right place.
And once we enter the final word congratulation message is printed and we return to
the shell in the
next video we're going to look at adding some final polish to the game.
So we're going to fill in the grid with a jumble of random letters and we're going to look
at formatting
the unsolved and the solved parts of the grid.
In this video the last video of the course we're going to look at adding some final polish
to our game.
So we're going to investigate how Haskell handles random number generation in order
to create a jumbled
up grid of words for our game.
And we're got to look at highlighting the words that we found.
So let's open up our words dot Kabyle file and add the random library.
Now if we open up JCI we can have a look at system not random.
And one of the exported functions is called random odds and as you can see this takes a
tuple of a certain
type A and returns a list of that type.
And also this mysterious G which is a random genotype.
So another function is the new standard Jeon which returns a standard generator within
the IO monad
and GFCI is running within that monad.
So you can see that if we call the function we get a new seed of a random number and
that's essentially
what the random number generator means.
And you can see that once we've extracted a number from the monad using the left
area operator that
number stays the same and using this new random number generator we can pass in a
top hole being minimum
and maximum numbers.
And our new generator and we get back an infinite list of random numbers between
those values and you
can see here a short list makes that clearer.
But it's not only numbers that we can put in here we can put anything that can be
enumerated.
So for example characters and you can see that if we call this multiple times using that
generator we'll
get exactly the same characters.
And that's because as a purely functional language the generator itself doesn't change
of course if
we now got a new generator from the IO monad and we ran our random hours
expression we would now get
a different set of random letters.
So if we look in our source library the first thing we might do is to create a random grid
and of course
we need to take in a random number generator and using that we can return a list of
random odds exactly
like so and because we want to create a grid we could append that row with another call
to make random
grid so we can make a recursive list.
And that's quite a common idiom within Haskell.
So for a much simpler example you can define a function called ones which is the
number one prepended
to the same list ones itself.
And we can see that this is in fact an infinite list of the number one repeated.
So now we of course need to import the system not random library
and of course we need to pass in our generator.
And that certainly looks like it returning a list of random letters.
So let's look at the first row of that.
I will take 10 characters from that and if we look at the second row and third and so on.
You can see that again we have exactly the same list of jumbled letters.
And if we look at our code we'll see that we recurse passing in the same generator of
random numbers
back into make random grid.
So of course it's going to return the same set of random or rather pseudo random
characters.
So what we need to do now is to use a function that will give us a modified random
number generator
and system that random provides that and calls it split.
And that will return a tuple so we can match Gen 1 and gen to over this.
And now we can create our first row using the first generator and recurse using the
second if we get
a standard gen from the homeowner.
Now when we look at successive rows of our grades we can see that they are in fact
random and of course
we could use our Zippos the grid function to zero but the random grid without a grid of
characters.
Now you can see that the first value of each of these triples is a random character and
the second is
the word on the grid.
So now we could imagine creating a new function called fill in blanks.
And again this will take in a random number generator and a word grid we can simply
pass through that
generator to make random grid.
And we want to do something with Zippos the grid but actually we want to use Zippos
agreed with because
we don't want to simply zip the grids together.
We want to fill them.
And of course we need to define what we mean by Phil if we have a character in the
word grades.
And the carrot and the random grades and the carrot and the word grade is an
underscore.
Then we want to return the random character and you can see that I'm changing the
names of variables.
And of course if the carrot and the word grid isn't an underscore that we want to just
return that
and we can see the fill in blanks.
Looks like it's returned a plausible looking grid and if we output that with N lines we can
see that
words like Haskell are still there and you may be able to see other words in the grid.
There's Perl written backwards and all of the characters that were previously
underscores are now random
So now we need to incorporate this into our game.
So we want to pass in the field and grade and in order to do that.
We need to fill the word great in but of course we need a generator but we are within
the IO monad so
we can simply call new standard gen to get that.
So one really interesting thing is that functions like filling in blanks are entirely pure
functions.
The only time we actually deal with the outside world with the random number is within
the I am on ADD
right at the top in our main function.
And again of course we need to import system not random.
And now our game still works exactly as before.
But now it's rather hard to see the hidden words.
And of course just because there are new letters on the grid doesn't mean that our
game will let us
type in those random letters because of the check that we made in the previous video.
But it is very hard to see what we're doing.
So it would be nice would be if we could format the words that we found differently
from the words that
are still left to be found.
So if we create a new function call format game grade this will now have access to not
only the grid
of cells but also the dictionary.
So of course we can get chargrilled out of that and return the value but now we want to
incorporate
our dictionary.
But if we want a different type of look up here because our dictionary maps as we can
see from strings
to cells and what we actually want is a list of cells or a set of cells.
We're going to simply use a list for this example.
So the set of cells is going to be related to the elements in the dictionary.
Now we know that the dictionary contains maybe cells.
So of course we'll want to call cat maybes on that.
We don't want a list of lists of cells so let's use another function called Concat.
Let's see that in action.
So we'll import data it up map as am and using cat maybes we can see that that current
returns a list
which is empty.
But of course once we filled in some words we actually see a list of lists of cells.
I'm going to represent that in JCI just with lists of characters just to make it simple.
And what the concat function does is concatenate those into a single list and then we
could use a function
called L-M for element to check whether one of these values like H is an element of that
list.
And you can see that this is true of each of those characters.
We're going to do the same shortly with cells.
And just to show that we can do that even with complicated data types.
Let's see whether this sample sale is an element of a list that contains the same Sal and
of course
that works because the cell data type derived the equality type class b que.
So now that we have our set of cells we can write a function called format cell and the
value of that
is going to depend on whether that cell is an element of the cell site.
And in that case we're going to return the carrots in upper case.
And of course all the characters on the grid are already uppercase will simply return char
and otherwise
were returned the lowercase character the two lower function needs to be imported
from data to char.
So now in format game instead of calling format grinde as we did before we can call a
format game grid
and we already have a game variable.
So we no longer need to extract the grid into a variable so we can remove that let
And let's look at that in action.
So now you can see that the grid starts off all in lower case and as we type in words
there outputted
in upper case so we can see which words we've already entered.
So what's next.
If you go to Haskel dot org and click on the link for documentation you'll see a whole
list of tutorials
and videos and books.
And of course there are links to things like the Hagele API search that we've used in
several of these
videos.
I really hope that you enjoyed this tutorial on high school and continue to learn more
about this very
exciting language in this series of videos.
We've seen how to install Haskel using the stack tool.
We've explored HOUSECALL using the G8 see-I interpreter and Hagele and we've dived
into the language
looking at functions data types and data structures and of course we spend most of the
last two sections
developing a complete project and say through this example of the word game we've
looked at modeling
a complex software project we've seen how we develop and test Haskell and compile it.
Thank you very much again again I hope you've enjoyed this course and all the best of
luck with learning
Haskell.