0% found this document useful (0 votes)
82 views57 pages

TEXTFP

This document discusses setting up a word game grid in Haskell. It shows how to declare a grid as a list of strings in Haskell, aligning the commas and brackets. It also declares a separate list of language names. The grid and languages lists are exported so they can be accessed. The document discusses wanting to print the grid as a formatted block rather than a comma-separated list, and explores using the Hagele search engine to discover Haskell functions that could join the list of strings into a single string with new lines, rather than just printing the list.

Uploaded by

dorris09
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
82 views57 pages

TEXTFP

This document discusses setting up a word game grid in Haskell. It shows how to declare a grid as a list of strings in Haskell, aligning the commas and brackets. It also declares a separate list of language names. The grid and languages lists are exported so they can be accessed. The document discusses wanting to print the grid as a formatted block rather than a comma-separated list, and explores using the Hagele search engine to discover Haskell functions that could join the list of strings into a single string with new lines, rather than just printing the list.

Uploaded by

dorris09
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 57

PATTERN MATCHING

In this video we're going to look at Haskell's pattern matching.


We've already seen some examples of pattern matching in argument unpacking but
now we're going to move
beyond that and start to dive into how to use pattern matching to for example calculate
the expression
data type that we created in the previous video.
We'll then move on to looking at how to pattern match lists.
Here's the expression type that we created in the previous video we noted in the last
video that we
can create an expression we can't actually calculate it.
So let's look at how to create a function that actually performs those calculations.
Let's go ahead and call it calculate now that we have a little more experience of writing
functions
in Haskell we can already think about what kind of inputs and outputs we want from it
and how to create
a type signature.
So we know that we want to calculate an expression but what kind of results are we
going to give.
Well we've already seen that we've defined number as an int and integer value.
So probably we want to return an int from this calculation to define the calculate
function.
We need to handle all the different types of expression.
That's all the different variants.
So let's start from the top by calculating a number.
So a number takes an integer parameter.
So let's go ahead and call that x.
This would essentially bind the variable x to whichever it is stored in the number
expression.
And of course if we want to return an int we can return x.
So let's go ahead and save that.
Now we can have a look at what happens in GHC II.
So let's reload our code and we can check the type of the calculate function and we can
see that it
takes an expression and returns in and exactly as we defined it.
So now we can calculate for example the number 1 and that returns the integer 1.
And this of course works with any integer.
So let's see what happens if we calculate an addition.
And of course addition takes two expressions and Haskel here is giving us an error
message.
Non-exhaustive patterns in function calculate.
And this is precisely Haskel telling us that we haven't defined a pattern for how to deal
with this.
If we go back to our code we can see very clearly that we have to find a function to
handle the number
case but not the addition case.
So let's see how that would work.
Number took a single variable x.
We can see that ADD takes two expressions.
So let's call them x and y.
Now both x and y in this case are going to be expressions.
So they're going to be things that could be calculated.
So we could imagine calculate eggs which returns an integer and calculate Y which is
also going to return
an integer.
So now we simply need to add those two new values together and we can do that using
the plus operator.
Let's see how that works.
So if we reload we can see that this time we do get an answer and the answer is 11 as
we were expecting.
And now it's very simple for us to simply copy that line and add in the final case which is
the subtract
case and we simply need to change this operator the plus into minus.
So now let's try calculating subtraction
and you can see here that we didn't simply subtract one number from another.
We subtract the result of calculating another expression from a number so we can see
that because we
use recursion by calling the calculate function from inside the definition of the calculate
function
then we can actually handle arbitrarily complex expressions and using Haskell's data
types and pattern
matching.
Let us define these very powerful expressions and then calculate them.
Let's look at how we do pattern matching.
Unless.
We previously saw the functions head and tail and both of these allow you to take a list
and return
a value.
Let's see if we can define a similar function ourselves.
So let's create a function called New head and we know that list is either an empty list or
a non-empty
list.
So let's handle the empty list case.
If we try to take the head of an empty list we can see that it gives us an exception.
So let's simply pattern match new head on an empty list with an error and you can see
that we get an
exception empty list.
So we'll look at the case of a list with one element we might think well we could match a
list with
a single element and return that element.
And in fact that works.
But of course if we have a list with two elements that doesn't work and we get the same
error about
non-exhaustive patterns.
So of course we could do this and this and this but really we don't want to define an
infinite number
of patterns just to get the first element of a list which could be of any size.
So let's have a look at the definition of a list.
Again we can see that a list is either an empty list or a list concatenated using the colon
operator.
And that's exactly the same as expression type which is either a number or add or
subtract.
So we should be able to match using the colon.
And because we're using a binary operator one that takes two arguments we can put it
in between the
values so we can put X in front and X's afterwards.
This is a fairly common Haskell convention to use x for a single value an X is for a plural
value and
here we can simply return X
and we can see that this function returns the head for a list of any size.
Now we can do exactly the same for the tail function.
We can create a new tail in the case of an empty list.
We still want to return an error.
And in the case of a non-empty list instead of returning X we want to return X's.
Now you may have noticed that we didn't actually create a type signature for this.
Let's see if we can work out what that might be.
In both cases we know that we start off with a list on the list is of a particular type.
Let's call it a.
Because it could be any type it could be an integer.
It could be a string.
It could even be a list of expression values.
And we're returning from it a single value of type A before we reload.
Let's see what type Haskell had given now function you had.
That's exactly what we wrote except Haskell decided to use the letter T as the type
variable name instead
of AA and of course new tail is very similar.
We'll start off with the list of A and we'll return another list of the same type and again
before we
reload that let's simply find out what type Haskell had to sign that.
And here you can see that Haskell very politely is now reporting the type signatures with
the A TYPE
variable that we gave it instead of the one that is chosen before.
So in the videos in this chapter we've looked at how to develop code using GHC.
And the text that we've explored the built in data structures and we've learnt how to
create our own
data types and we've seen how to use functions and create our own functions using
some of the data types
that we've been exploring in this section.
Next in section 3 we're going to look at how to install dependencies compile run and
test our code in a project.

SETTING UP THE WORD GAME GRID

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.

SEARCHING FOR WORDS

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.

SEARCHING IN ALL DIRECTIONS

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.

UNIT TESTING THE GRID WITH HSpec


In this video we're going to look at unit testing the grade using a spec We'll start off by
doing a
small clean up refactoring data out of our model and then we'll start to test our
functions using a
spec.
So let's have a look at the test file.
So in the first video in the section we imported test age spec and wrote a very simple
test simply to
show how to write a test and now we can actually look at testing some of the functions
that we've written.
But first of all it occurs to me that this library lib contains various functions that actually
transform
and display a grid but it also contains these two items of data a list of languages and a
grid.
And this might now be a good time to actually look at extracting those into a separate
place in the
future.
We might read those from a file for example.
So let's create a new library called Data H.S..
We can remove these functions from here and paste them into this new library.
So of course we need to declare this as data and we can export grid and languages.
Like so.
And our tests would also need to import data and of course so will our main.
What are you.
So let's see if that will work and we can see that stack hasn't found the interface for data
that suggests
that we also need to add it to our COBOL file.
And you can see here that only the lib module is currently exposed.
So we need to also add data and we can see that the main module now loads both lib
and data.
It still gets the grid it still gets languages but he gets it from a different place.
So that was just day small tidy up.
So let's go back to our test file and actually test some of the functions.
So let's test the format grade function and we need to give that description.
It should concatenate every line with a new line.
So if we give a sample call to it's format grade we could do it on a very small grade and
we would return
a string with the line separated by the slash and by the newline character I've missed
closing the bracket
that if we run stack tests and here we can see that I expected that we would have a new
line in between
each line.
But in fact we have one at the end of the last line as well but that's perfectly fine.
I can simply update the test by adding a slash and it's a good example of how writing
tests will sometimes
help you clarify your assumptions when you've been assuming the wrong thing.
And this time around that test passes.
So if we import data as well then we'll get access to languages and grid so we could
now for example
write a test for find word.
So if we were to look for the word Haskel on the grid the result should be just how
school and you can
see now that we've tested format grade and find words.
Now this is where the do notation that sequences commands comes in really useful
because we can now
run a test for Perl
retest and we can see that both of those words are now tested if we search for
something that doesn't
exist.
Then we would get a failure.
So in fact this would be a very good test that words that aren't found on the grade
return nothing.
So you can see that we can sequence both describes and it says and of course find
words.
So let's do one more test of find words itself.
So if we were to call find words create languages what would the result of that be.
Because we know that all of the languages are present on the grid.
The result of it should in fact be the same as the list languages.
And we could see that if we were to change the definition of get lines by removing For
example some
of the diagonal entries that this test would now fail
and if we search for a list of languages that don't exist on the grid then that should also
give us
the desired result.
So if you have a look at all of the exported functions you can see that we could now test
each of those
functions so we won't do that in this video.
But it might be a useful exercise for you to do now to practice testing with Haskell.
So in this section we've used stack to create a project to build it and explore it using
GHC II and
run tests.
We've started to model the word game using built in data structures like lists and
maybe we've started
to develop some functions to display search and transform the lists.
So in the next section we're going to look at polishing the word game.
We're going to make it playable by inputting words to search for and outputting the
result in an interactive
way.
We also look at how to fill the grid with random letters and we'll explore some more
container types
like maps and sets.

SECTION 4 – POLISHING THE WORD GAME

GRID COORDINATES AND INFINITE LISTS

In the section we're going to look at polishing the word game.


We're going to be making the game complete by doing that.
We're going to start by adding coordinates to the grid.
We're going to look at working with infinite lists in more detail than we have done so
far.
We're going to create another recursive function.
We'll be looking at modeling the game so we're going to use Haskell's powerful data
types and see how
to apply that to a piece of extended code.
Make the game playable.
And that means finally dealing with the IO monad.
And we will add random letters to the grid turning it into the proper jumble that we
normally see in
this video.
We're going to add grid coordinates and to do that we'll explore infinite lists.
So we're going to explore the features and functions to deal with infinite lists in
particular.
We'll look at least comprehensions and we will look at various functions such as repeat
and we will
look at the zip function which would allow us to do various things combining rows and
columns of a list
and also working over two dimensional grids.
So as you can see a coordinate locates a cell in a two dimensional space with the first
value.
In this case implying the row and the second value in the table being the column.
And you can see that very clearly here that the row is zero.
All along the top.
And as you go down the grid it increments by 1.
Similarly for columns the column number is 0.
All on the left hand side and increments as you move towards the right of the grid.
And we're going to be looking at adding that kind of grid to our code because we're
going to be doing
a fairly extended parenthesis where we look at some features and functions that we
haven't dealt with
before.
I'm not going to be working within my normal source Livedoor H.S. file.
So I've created a stub file here called Test dot H.S. which will be in the GET repository so
you can
follow along as you can see at the top of this.
I have simply defined a sample grid of coordinates.
Now we're going to be looking at generating that programmatically throughout this
section but just to
start off with here's a sample of it because we're not including our own code.
We do want to have a word created to play with and I've simply copied the one from
data not HFS And
here we have a function which I've named Oji simply means output grid.
And this will show every line in the grid and then output it in a similar way to a previous
function.
The reason it's named Oji is I'm going to be typing it out many times throughout this
tutorial and so
it makes sense to give it a very short name for convenience.
So if we open our test or take chess we can see that we have a grid and this is a
demonstration of the
O.G. function.
And if we output the coordinates we can see that every line is a list of tuples of integers.
So let's have a look at how we might generate those.
So here is a list range expression and you can see that this gives us the numbers from 7
by giving two
numbers at the beginning of the expression.
We can actually skip over a range of values but Haskell also allows us to provide an
infinite list by
missing out the maximum value.
And obviously this would take an infinite length of time to compute and to display.
I've pressed Control-C to escape out of that list.
So this might seem to be less than useful because we can't actually process it without
spending an infinite
amount of time.
But if we only use the early values for example we can run head against it and we could
table the list
and get the first value of the new had produced by that and so on.
Now obviously these expressions are slightly awkward.
Haskell provides us with a whole variety of functions.
For example we can index into the list using the double exclamation mark operator and
we can also take
a finite number of values from the beginning of an infinite list and if that's not powerful
enough we
can run a function could take a while which returns values while a condition is true.
Now functions that we've seen like map we can run those against an infinite list as well
and you can
see here that all of the resulting values where in fact multiples of two.
It's clearer to see if we take a finite selection of those.
And of course we can run all of the functions like take while and so on against an infinite
list.
In this test not a chess file.
I've defined a function called Div 2 which checks if a value is divisible by 2.
That is if the modulus after division by 2 is 0.
So for example if two of 10 is true 10 is divisible by 2 whereas 9 is false as 9 is an odd
number.
So we can use a filter list.
This is very similar to a map in that we give it a predicate but the predicate needs to
reach out a
boolean as does Div 2.
So we can filter by Div 2.
And again we can give an infinite list and you can see here that all of these values are in
fact multiples
of two and of course we can turn that into a finite list by taking a finite number of
values.
Now there are other ways of dealing with less and one very powerful one is the list
monad.
And we can use the do notation that we've previously seen while writing tests.
Now in the notation we can take values from a list using the left pointing arrow
operator.
So here I'm assigning to the variable i from a list from zero to nine.
And the return key word means to return a value in the specific monad.
So in this case return it back into the last moment and I'm returning the value of times
to.
And when we reload we can see that the mapped value is the same as if we'd called
Map times 2 on that
same list.
And of course the list can also be used in an infinite way.
And here you can see that there are at least 100 values available from it.
And we can also write filter in exactly the same fashion.
Again we can iterate over a range and we can now use what we call a god clause and
what that clause
will do is take a boolean expression that tests the list and let's have a look at the type.
So first of all we need to import it.
For now we can see that its type takes in a boolean expression and returns an empty list
in type f and
we can see the for.
So it may or may not raise an error but in the case of list it will either return a list of 1
value
or a list of no values and 4 maybe it could return a just value or a nothing value.
So let's import control monad and we can see that in the case of list returning that
empty list in fact
filtered the values from the expression
and finally we can combine the concepts of mapping and filtering within a single
function
so although this is a very powerful notation it isn't especially compact and Haskell gives
us another
option which is list comprehensions.
These are written in a very mathematical notation.
We write it using square brackets.
We have our return value the left a pipe character and then we have our left pointing
arrow expression
and we can add our guards with a comma after the list that are iterating over.
So let's have a look at our coordinates grid.
As you can see the row number stays the same.
Along the row and the column number remains the same.
Within a column of the grid if we wanted to use Haskell's list functions to generate a
grid we also
need to look at repeating values.
Integrate as you can see the repeat function will return an infinite list of a single value.
There's also a cycle function which allows us to pass in a list and each value of that list
will be
repeated over and over.
And of course that doesn't mean that cycling over a list of a single element is essentially
the same
as the repeat function.
If we map repeat over a list let's make that finite to make it more easily displayable here
we can see
that we get at least with ones on the first row twos on the second threes on the third
and so you can see that this is very similar to the list of row numbers that we need for
our grid.
Now let's have a look at the zip function as you can see.
This takes a list of a certain type a.
Another list of perhaps a different type B and it will return a list of tuples of type A and
B.
So that means for example that we could zip a list of numbers from 1 to 10 and the list
of letters from
a to J and zip will return a list of tuples of 1 and day 2 and B 3 and C and so on.
Now I wrote H.J. because J is the tenth letter but in fact if we provided the later letter
said we would
get the same list because zip stops processing the list once any one of the lists is
exhausted.
So we could use an infinite list on one side.
If we had an infinite list on both sides then of course simple carry on processing values
as long as
it can.
But it does show that we can work with an infinite set list again by using functions such
as take.
So if we look at codes How would you generate that list of code.
It's one very common way might be to use the list looping construct as we saw with this
list notation.
So we might create a new variable cord's too and we would iterate over row from North
to 7.
Call again from nought to seven and we might return the row and the column as a tuple
and let's see
what that looks like as a first attempt.
These chords and you can see that it has each row within a list and chords to in fact has
exactly the
same numbers you will see there not come a seven followed by one common 0.
However they're not nested in a new list.
So let's try nesting.
Our loop by adding a new do.
And that still hasn't quite worked.
So if you remember that return wraps a value in the list monads.
What we need to do here is to return the entire contents of the in or do expression
within the list
monad i.e. by wrapping it within a list.
And the important thing to notice here is that the do block isn't actually special.
Apart from enabling the left arrow syntax it's simply a first class expression that can be
passed to
other functions like.
In this case return and now we have an ounce or less which contains repeated iterations
of the in the
list on every row.
And of course we can rewrite expressions in the list monad as a list comprehension.
So we know that we want to return a top all of row and column and that we iterate the
column over the
numbers not 7 and the row over the numbers nought to seven.
But of course this expression suffers from exactly the same problem that we had with
our first attempt
with the Listman notation.
So again we need to wrap that in a statement in a nested list comprehension.
I'm not going to use a list comprehension of syntax for much of this tutorial but I hope
that this introduction
has been interesting.
You will definitely see this notation in Haskell code that you read.
Now I hinted before that we could actually use Zipp to generate a grid.
So let's look at how we might do that.
We can see that our columns are the list nawt to infinity repeated over and over again.
And similarly rows are essentially mapping over the list nought to infinity with the
repeat function.
We can consider that using an infinite list of columns and an infinite list of rows might
be very convenient
in order to express a grid of arbitrary width and height but it is going to be harder for us
to demonstrate
in this tutorial where we want to be looking at the values using for example our O.G.
function in the
GHC interpreter.
So let's create a new function that specializes.
Repeat.
By taking 8 values.
So this is a composition.
It will run repeats and it will take values from the result.
So now we can define versions of our rows and calls that work over the list from nought
to 7 and use
our repeat 8 function
selflessly you can see there that Rose and calls are infinite lists.
But if we display the value of rose 8 and we can use O.G. to nicely format that and we
can see again
that the value increments as we work our way down the grid.
And similarly called Zahed the value increment to the right of the grid.
And what we want to do now is to combine those two grids and of course we've already
seen a function
that can combine these grids.
So if we run Zipp against that value in Rose and that value in Coles we get a list of
tuples of exactly
the sort that we're looking for but that's only over a single row.
So how might we do that for arbitrary rows.
So we might think that we want to zip our rows with our columns but the problem here
is that zip has
done exactly what we ask to.
It's taken the first row of rows and the first row of calls and so on throughout the whole
grid.
So really what we'd want is a function that did the same but instead of combining with a
comma then
zipped those two rows together.
So let's have a look at how we combine values into a tuple.
There is in fact a function the comma operator which takes two arguments and
combines both with a comma
in a new two value topple and zip with is a more general version of zip.
And we can see here that if we call Zipp with with the comma operator and then our
two lists that that
does exactly the same as Zipp.
So you can think of Ziph as being essentially Zipp with comma.
So what we want to do now is to instead call Zipp with with a function that combines
the row from rows
and the row from calls.
And of course we know exactly what that function is.
It is Zipp.
And here we can see that zip with Zip has allowed us to combine a grid of rows and a
grid of columns
into a grid of tuples of rows and columns.
And that's such a useful function that we can extract it into a function called Zipp
overgraded
So now if we have a look at our word grade what we really want to do now is to
associate every character
on the word grid with its coordinates and we can see that zip over grid takes a grid of a
and a grid
of type B and it returns a new grid of the typical type a comma B.
So if we run zip over grid with our coordinates grid and our word grid then as you can
see we get exactly
what we were looking for a list of tuples each of which topple contains a further couple
of coordinates
and a character and you can see here that we have some of the words that we had
defined.
We have the programming language C-Sharp and you can see from bottom to top I P
which is the last part
of the programming language Lisp but where did the L go.
And of course if you look at grade this is a 12 by 15 grade whereas coordinates is an 8
by 8 grade and
of course in the way that zip will stop processing once any one list is exhausted zip over
grid will
give up once any axis of the grid is exhausted.
So of course we can now make our coordinates grid larger by increasing this number
here but we know
we already have an infinitely large set of columns and rows so we can easily now define
a new version
of infinite cord's grid and we can use our zip over a grid function over that
and now we can call Zipp grid on our infinite coordinates grid and the grid of words and
you can see
that this gives us the full 12 by 15 grid
in the next video.
We look at fleshing out the grid model.
We're going to incorporate our news zip over grid function and create some new cell
and grid data types.

FLESHING OUT THE GRID MODEL


In this video we're going to look at fleshing out the great model.
So we're going to add parameterized types of the names to create different types of
Grade.
We're going to create a new data type called cell and we're going to incorporate the
coordinate grid
that we worked on in the last video into our program.
So we're going to carry on working within that H.S. very briefly just to look at a few
more things and
then we'll incorporate this code back into the main code base.
So let's create a new data type called cell.
So this will have a constructor called cell and it will take a tuple of integers and of course
a char
and again we'll add the deriving clause of Q order and show so that we can compare it
to other cells
and display it on the screen.
So let's consider our coordinates grade.
And we saw that if we ran our news Zippo of a grid function with the coordinates grid
we returned a
couple of coordinates and chars for each position in the grid.
But now that we've created a data type for cell we can see that this might not be really
exactly what
we want.
In fact in that first position what we want is a cell data type with not cosmonaut and the
underscore.
And if we look at Zippos the grids we saw that it used zip with zip.
So what we really wanted to do now
is so we saw that we had to use zip with Zerbe in order to not simply join things with a
comma but to
use.
So instead of using plain zip which joins two values of A list with a comma we used zip
with that allowed
us to parse in an arbitrary function.
Now if we look at cell as a type we can see that this is a constructor function and it takes
in an integer
and it takes in a char and it returns a cell like say and so where we might join the values
of the tuple
and the character with the comma we could have done it with the cell instead.
So here instead of using z we could use Zipp with comma and this would be identical to
what we had before.
So now instead of using that comma operator if we substituted it with the cell
constructor function
we would now get a grid of cell values.
And this suggests that we could write this as a new function called Zip overgrazed with
and it would
take a function a grid and another grid and it would run zip with zip with that function
exactly as
we saw in JCI and of course we can now simplify this.
We can remove the aned B from the right hand side we can remove this outer
parenthesis.
And now we can see a pattern that is identical to that of list composition.
So we can not turn that into the dot operator there separate the argument with a dollar.
And of course we now have F on the left and right of both sides which means that we
can delete there
and we can see that Zippos the grid with is in fact defined as the composition of Zipp
with a dot zip
with.
So we can run Zippos agreed with cell codes and great.
And if we shrink the screen a little we can see what that looks like.
Formatted as a grid
so let's open up our live
and we can start to incorporate the definition into our code.
So here to write the code grade I'm going to define the roles and the calls inside the
function using
a let clause.
And that just keeps things neater.
And we can define a function that takes in a grade A word grade
and returns a grade full of cells.
So you can see we have many different types of grades.
We have grades of cells we have grades of coordinates.
We have grades of Rose
and so if we look at our definition of grade which was a list of strings we could think
about it as
being a grade of characters and what we really want to do now is to turn it into a
parameterized type
that is to say a grid of any type of character and we can now start treating each grid as
being a grid
of a different type.
This means that we're actually embarking on a refactoring of our data types within our
project.
And so we can even use GHC to help us with that by showing us the error messages and
then fixing those
in turn.
So if we run actually HCI you can see that we'll now get errors because we've declared
certain things
as a grid expecting it to mean a grid of Charles and grid is now a parameterize type that
needs to be
specialized.
So we need to tell that in each case that we're using a grid of char.
So if we look for every existing example of grid in our code we can simply add char in
each case
so we're done.
Let's try reloading that.
We still have some errors and it looks like we missed a couple in diagonalize and skew.
So congratulations there if you spotted my deliberate mistake.
So we changed one of the grid.
One of the great types but not the other.
And now all of the existing grid types are marked up correctly.
But now of course we can use the fact that grid is no longer specialized to char by
default by giving
types to all of the new functions that we've created.
So for example Zipp overgraded takes a grid of Taipei a grid of type B and returns that
grid of Stupples.
Let's add that function to our exports list and then we can explore it in GHC II.
So if we check the time now we can see that GSI reports the grits and the names.
If we look at Zipp overgraded we've we haven't yet told GHC to consider Zippos regret
with our new types
in them.
So it still reports it has a list of a list.
But we can change that by adding the type signature here.
And you can see that the first function takes a value of Taipei and the value of type B
and returns
a value of type C.
And of course that's reflected with the input grades grade A and get B.
And the final output grade which is a grade of type C and of course codes grade is a
value a variable
rather than a function.
But we can still give it a type.
And in fact it's a good idea to wherever possible and of course that's a grade of a tuple
type and you
can see that at this stage I'm going back to GHC II to confirm that the code compiles
and occasionally
to check the types of various functions to make sure that they match with what I'm
expecting.
So the grid with chord's function takes a grid of Chaar and returns a grid of cell
so let's have a look at our main function because now it would be nice now start using
this grid of
cells so previously where we simply output a chargrilled
We now want to output a grid of cells.
And of course making that change won't work because output grid is expecting a grid
of char and not
a grid of cells.
So where we had main equals output grid grid which is a grid of char Let's actually
create a grid with
coordinates we can call it GWC for example
and now we want to output not the grid of char but the grid with coordinates.
And if we reload Of course this will fail as we expect because output grid is expecting a
grid of char
and we passed it a grade of cell.
So if we look at the output grid function we can now change this type into grid of cell
and it calls
format grids which again takes a grid of char we can change that to grid of cell and
format grid itself
is currently defined as the lines function which of course joins up a list of strings
and we now have not a list of strings but a list of lists of cells.
So we're going to need to make some changes here.
First of all we need to define a function that outputs a character when given a cell we
need to create
a function that returns a character when passed in a cell and we can use pattern
matching here to match
the cell and its coordinates tuple we don't care about the coordinates tipples away using
the underscore
there and the character C will simply return.
See.
So now we want to map.
We want to create a chargrilled from our cell grid and leave it undefined for now and
then we can return
on lines of that chargrilled.
So how would we turn our grid of cells into a grid of char.
So if we look at Zipp if we look at the Zipp over grid function we can see that we called
Zip with zip
and zip over agreed with was in fact zip with composed with zip with zip with dots zip
with and on a
hunch here and in a similar way if we had a single line we would call map Selda char on
that line.
But in fact we have a grid.
So how would we map Celta char over every character in the grid.
And almost on a hunch here I'm going to try substituting map with map dot map.
We're going to give that a go.
And you can see that the code works as expected.
So mapped up map is a way of mapping a function over a nested set of lists.
And we're not going to go into detail in that but in the same way that we derived zip
overgraded with
earlier it might be useful for you to go through the code of mapped up map and see if
you can work out
why that works as an exercise.
So in the meantime we're going to define map overgraded as a useful little helper
function.
And this gives us the opportunity to add a type signature so we can see
explicitly what the type of this expression is.
And of course we can now simplify this function
and we can spot that.
This is beginning to look like a composition of functions at which point it's easy to
simplify that
and we can now say that format grid is the composition of map overgraded with the
CELTA function and
lines.
So let's look at refactoring the other functions in our file.
We can see for example that get lines can easily be refracted into a grid of cell.
It does need to return
a list of cells.
We could write that as grid of cell but because we're not returning a great shape I felt it
was more
useful to retain that as essentially the type synonyms here are documentation for us as
programmers
rather than for the computer
but the rest of the function doesn't refer specifically to
to char.
So we can now save that and see what GHC tells us so we can see that some of the
functions that get
lines calls are expecting char where we have now given themselves and so we can
continue to refactor
making changes to the type signatures.
And we can see that the if it diagonalize is now gone.
There isn't Araf askew and GHC is pointing us at the colon operator.
So if we look at our code we can see that in fact we are using colon to concatenate a
char with the
line and that of course will confuse things because we know that line is now going to be
a list of cells.
So we do need to now parse in a cell value and we could create a dummy cell value here
for example but
that's not very neat because it's saying something that's not true about a program that
there's a cell
at position 0 0 and it's represented by an underscore whereas in fact what we're really
doing is we're
passing an indentation value and we could call that indent for example
and what we'd want to do now is to look for a definition of cell
and add an alternative which is the constructor in the end it doesn't need any
parameters.
And now we have fewer error messages.
We can see that
even though get lines it's self didn't have a constraint.
It's being called from another function which is expecting char and therefore Haskel will
complain when
get lines is being used in inappropriate ways.
So now we can change the grade into a grade of cells.
Of course find word is being passed in is being passed in the string that's the word that
we're searching
for.
So we need to keep that where the result is not going to be a maybe list of cells which
will be the
word and the coordinate positions that the words found
and of course find word in line is still giving us errors if we haven't look that we could
see that
they took a string the word being searched for another string being the line and
returned to boolean.
So we could start to refactor that by taking that second string into a list of cells.
And of course is infix of won't work because as you can see if we import data list that we
can check
the type of is infix of.
And you can see that it takes a list of A and A list of a.
So you can't check is infix solve with a list of strings and a list of cells.
So for now let's simply leave this function as undefined.
And when we do that Haskell will honor our type signature because undefined is a
member of all types.
That might allow it to compile though of course you'd still expect the is at runtime at
this point.
So I ask because now pointing a set the find words expression
and we can tell that this needs to be a grade of cells.
We still want a list of strings being the list of words to be found.
And at this point we want a list of lists of cells.
But if you consider that previously we were told simply a boolean if the word was found
and then returned
the word itself.
Now of course we need to return a maybe list of cells which means that find where in
line is going to
need to return something quite different.
So it would need to return
a maybe a list of cells and that being the case mapping find word in line over lines
wouldn't return
a list of boolean values that could be simply all words together but would return a list of
maybe values
and so we have quite a few missing pieces here.
This is not the we're not able to simply resolve by replacing char with cell.
So I'm going to again comment out that function and write the definition as undefined
and that would
allow us to compile the code.
Now let's add some imports.
Now let's add some exports.
We're going to add the cell data type with both of which constructors cell and indent
and you can see
that we can call Celta char on a cell.
But of course if we try calling it on an indent that fails.
So let me just go back and record.
So we're going to look at defining find word and that family functions in the next video.
In the meantime here's a small point.
If we look at our cell to char function we can see that it only has an expression for the
cell type.
But of course we now have a new constructor called cell to char.
So let's have a look at that in GHC.
If we export Celta char and the cell data type will need to export both constructors cell
and indent.
So in HCI we can see that we can display both indent and a cell and if we call Celta char
on that cell
value we get a character back.
But if we call it on an indent value we get an exception about non-exhaustive patterns
which isn't very
helpful.
So it's important to add a pattern so that every type of value will be dealt with Weygand
simply output
a question mark
in the next video we're going to implement those missing functions to find words and
find word functions
and look at.
Searching the grid with recursive functions.

SEARCHING THE GRID RECURSIVELY


In this video we're going to look at searching the grade recursively in the previous
video.
We started to refactor our model to use the new cell data structure which contains
coordinates as a
tuple of integers.
In this video we're going to be changing the easy fix of function to work against our
new data structure
and to do that we'll have to create two kinds of recursive function.
We'll make a recursive search that does a prefix.
And then we'll build that together into a recursive infix set.
So let's start off by opening GHC by
if we look at our great
we can see that this line of text contains the word Haskel and we can see that it is the
second element
of the grid.
Now if we look at our grid with coordinates and take the second element of that you
can see again that
the word Haskel is contained there.
But of course it is listed along with the integer positions of the Great.
Let's have a look at the definition of the.
Is infix of type and you can see that it takes two lists both of which must be the same
data type.
So we can ask whether the string Haskell is an infix of a string containing that words.
And there's another related function called is prefix of.
And you can see here that it fails for the whole string but succeeds for string where
Haskell is at
the beginning of another string.
And that's a simpler type of function that we can look at implementing first.
So we would want to check whether the word Haskel matched that portion of the line.
So let's start by defining a new function we could call it to find word in cell line prefix.
And of course it will take a string which is the word we're searching for and a list of cells.
And whereas the previous function originally returned a boolean we know we want to
return a maybe type
maybe a list of cells if it's found.
And so of course we'd call it with the word and the line and we could think of the word
as being a character
followed by a list of characters and the line is a cell followed by a list of cells.
And at this point because cell and character begin with the same letter we're going to
disambiguate
and I'm choosing to rename the cell the cell variables to be Xs.
I'm renaming the character variables to be x' while the cell variables will be c.
We're going to add a guard clause where we compare X against C and of course X is a
character while
C is a cell.
So we need to call a function against C that turns into a cell.
And luckily we already have one defined as we wrote seld char earlier.
So this branch of the subroutine.
So this branch of the function will only run if the cell and the character compare.
And if they do will return the cell followed by a recursive call in to find word in solemn
prefix and
with pass the axes of this line and the CS and then we need to write the base case where
they don't
match.
And in that case we fail.
And of course because we're returning a maybe type here we return nothing.
And of course that makes us realize that we've done the wrong thing here because
we're returning a list
concatenated with the colon operator.
So instead we need to wrap that in and maybe type so we wrap it with the just
constructor.
But of course that won't work because now we're trying to append a cell and a maybe a
list of cell which
would be the result of find word in seldom prefix.
So we can tell that we're going to have to do a little bit of restructuring here.
And one common technique for recursive functions that have this kind of complexity is
to parse in what
we call an accumulator a new list that will accumulate the values that we're trying to pick
up
and so now we can simply call the function again passing in the accumulator and the
tale of the two
letters that we're comparing.
And of course because we need the accumulated to accumulate something we would
need to add the cell
to that list.
Now the plus plus operator in Haskell is a little bit inefficient because it has to iterate all
the
way through the list on the left hand side before it can get to the left on the right hand
side.
And so often when you do this kind of function instead of adding the new variable to
the end to the
right hand side you'd actually prepend it to the left hand side again using the colon
operator and of
course that means that we'll get our list in the wrong order.
But then it's very simple and efficient to reverse it on the other end.
And of course our second base case now needs to be split up into several cases.
So for example if we know that the list of strings to compare is now an empty list that
means that we
must have gotten to the end of the comparison and the comparison has matched.
And that means that we can return the accumulator here.
And of course because this has now passed this is a successful call we would need to
return just accumulate
and if we have any other case then we can throw away the result of the cumulate.
So we can match it with an underscore because we won't need it.
We don't care what the rest of the string is because nothing's matched there's nothing
left to do.
And we don't care what the rest of the line is and we simply return nothing.
So you can see we have three cases the case in which we recurse into the function
adding to the accumulator.
This is a potentially good case.
Then we have the case where we know we've got to the end of the match and we can
return a value a good
value and we have all the other cases where we've either got to the end and failed.
We've run out of cells to match and so on.
And in these cases we return the failure of value which in this case is nothing so of
course we now
need to export this function so we can see again that it needs to be called with an
accumulator
I will use an empty list for that.
So if we set the line to be the second line of the grid.
The grid with coordinates.
Now we only want the prefet starting from a
we can see that calling it without the accumulator of course is a type error.
So let's run that again.
With the empty list and of course this fails because we're starting the comparison from
the beginning
of the line.
And of course the first character is an underscore rather than an 8.
It's called the drop function against the line.
You can see that this returns a new list that starts with the h.
So if we call our function against that expression you can see that this now returns all of
the cells
that have been matched HA S K E L L.
So our function works but it does return things in a rather odd order because we've
been prepending
the new results of the cube later to the beginning of the list.
So I mentioned we need to reverse it.
And of course forgot to do that.
Let's go ahead and reverse the order accumulator before we return it.
And if we rerun that we find exactly the result when looking for.
So we're now at a point where we can edit our find word in line function and rewrite it in
terms of
fine word in cell line prefix.
So we can start off taking a word and a line and we can ask whether the word is found
at the head of
the line as a prefix we need to pass in the accumulator an empty list will pass through
our word and
we pass through the line.
And of course this new expression will be a maybe value so we can pattern match use in
case against
the result of that.
And of course if nothing was found this isn't necessarily a failure because we can now
recurse into
our self and we still want to pass in Word because the word that we're searching for
hasn't changed
but now we want to recurse into the tale of 9 so that we can check whether the word is
found at the
next position of the line.
And of course if the soul was found
then we need to return that value.
And of course that's a little bit awkward because we've repeated the expression just
seize on both sides.
So instead we could use an apt pattern to match just because we don't care about the
value inside that
so we can use an underscore there and use the circle at pattern to match the whole of
that expression
and then we simply return that expression.
So again we can select our line and now we can see whether we can find words in line
and that now fails
to find it on the first position.
Falsified it in the second position and then recurses into the third position and finds the
words
say.
So far so good.
And just to confirm that we've definitely found the right cells if we now index into 2
position 2 2
we can see we do in fact return an age and if we try that against two three we can see
that we do return
in a.
Now we can go on to rewrite our find word function
so we can uncomment out some of these lines.
We still need to get lines grade
and of course found words will now return a list of lists of cells the expression found
words will in
fact be a list of maybe lists of cells.
So if we call cat maybe's on that list we will get simply a list of lists of cells so let's have a
look
at what that might look like.
So you can see here that running cat may be against two just values and to nothing
values returns only
those values that were just values.
And of course because we are expecting the world to be found only on one line of the
grid or no lines
of the grid the result of that will essentially be a list that contains either no values or one
value.
And in this case we can use a function called list to maybe let's see what that looks like
list may
be of an empty list is nothing and list maybe of a list with one element is just that
element and you
can see that if we call it against a longer list we ignore all of the additional parameters
all of the
additional values in the list and simply return the first one wrapped in just.
And that's exactly what we want.
In this case because we're not expecting a word to be found in more than one position
on the grid.
So here we need to wrap our ACAP maybes found words in list to maybe we need to
add that import out
the top of file
now we could try running find word against our grid with codes of the word Haskel.
And here we have an exception.
There's an empty list when we call tail and we know that we've added a tail expression
here.
And in fact this is the culprit because if we've got to the end of the line without finding
anything
then of course we can't recurse.
So what this means is that we were missing a base case.
So let's write that in here.
If we have a word we don't care what the word is at this point and an empty line then
we need to return
nothing.
And at this point find word will iterate over the whole grid and find our word.
And this is a good occasion to rerun our tests or we can see of course that we have a
whole load of
test failures because we've made a reasonably large refactor without changing our tests.
And of course the tests are getting back lists of cells where they expected lists of Cha's
say for example
format grade.
Now take a grid with coordinates.
So we need to simply wrap that call like say and in the case of the find we're tests we
don't want to
change the comparison here to include the cells because that would involve us having
to hard code exactly
the cell positions that we found the word Haskel on.
So instead we could map the left hand side to confirm that the value of the word found
searching for
Haskell is in fact Haskel
and that's a little bit awkward looking.
So let's extract a few variables.
We'll get a GWC for grid with chords and in fact will write a small helper function which
takes a word
and extracts the results of what we were looking at below.
So it tries to find the word
maps self-discharge on the result.
And then returns should be expression that the task was running
so we'd belt to replace all of these complicated things with simply test find word on the
other hand
the test below doesn't need to be changed substantially because the results of
searching for word not
on the grade would still be nothing.
Similarly finding words that don't exist.
Would still return an empty list.
So this test doesn't need to be substantially changed.
Whereas again finding words that contain cells we would need to run a map against
those words
and you can see here that we're running map with a nested map
in order to
map a results over every list of strings.
And this is comparable to our map atop map that we saw in the previous video.
Now if we rerun the test we can see we still get an error
and we have a parser error on input in and that's because we're running in a do block.
So I was mistaken in writing the keyword in here.
So if I simply remove that
then we can rerun and we still get an error.
If we have two areas here
I've attempted to call a function char to string whereas in fact the function is named cell
to char.
So let's rename that.
And the remaining error
is about maybe expressions and we can see here in our test find word function that
result in fact is
a maybe value.
Now here we can match just against the just result because we know that we're only
testing values that
we're expecting to be just's if we made a mistake then obviously Haskell would give us a
runtime error
but we can deal with that.
And this is only test code and we have another error about just and of course here now
we can remove
the just from that side.
Essentially we've moved the maybe typing into the right place and so this kind of
debugging with
this kind of debugging by looking at error messages can seem a little frustrating.
But the interesting thing that you may have noticed is that in every case the compiler is
telling us
about a real error and it's being very accurate about where to look for that real error
and that can
be really helpful compared to developing in some environments.
And now when we rerun the test you can see that everything passes in the next video.
We're going to finally look at making the game playable.

MAKING THE GAME PLAYABLE

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.

SOME FINAL POLISH

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.

You might also like