Top of page
2010-09-07
When I was little (19) I was learning to program and people kept throwing words
at me and expecting me to understand them. In this series I try to recall such
words and explain what they are in plain English, assuming as little prior
knowledge as possible.
This episode is about frameworks
The concept
This concept builds on the previous one, so if you haven't read that, go read
it here. It was
about what we mean when we say "it handles that for you". Frameworks are a lot
of handling stuff for you.
The important part about a framework as opposed to a library or
module is that a framework usually handles the point of entry for
your program.
This is probably more important than you think. By handling the point of entry
for your program, the framework can take a lot of control. Of course this is a
good thing: if you didn't want it to take control you wouldn't be using the
framework.
The thing it takes control of is also important. You see, almost every piece of
software ever written follows the same basic procedure: accept input, do
something with it, display results. Now, the displaying of results could be
anything from updating a file or database to changing a GUI window or even
creating one; the input could be network stuff, user input stuff, the printer
complaining about no paper; and the stuff it does is literally bounded only by
the imagination (and the machine's capabilities).
This is a very basic description of what we call an event loop. Input,
process, output, repeat. Frameworks hide the event loop, and, depending on the
framework, usually allow you to define the input and the output.
Am I going too fast?
I'm sure by this point I'd be more confused than I would be enlightened by the
concept, were I you. Fear not. Do you recall the previous article about
modules? With the correct
separation of code, you can use various techniques to define, in code, what
your input and output should look like.
The usual way of doing this, or at least one of the ways, is for the framework
to accept callbacks. A callback is a function that you define, but
that is passed, perhaps by name, to someone else's code. Depending on the
capabilities of the language in question, there are many ways of going about
providing callbacks. The fundamentals, however, revolve around the idea that, at
specific points in its execution, someone else's code will return control to
your own code. These are the points at which you can define the processing of
data that actually makes your use of a framework different from other uses.
This is subtly different from modules, but it is this subtle difference
that divides a framework from a module. With a module you use its
functionality in your code. You include the module, and while your code runs,
you can call on its utility methods by sending it data and receiving the output
and continuing. But the point of entry, or perhaps the event loop, is what you
write; it is your own code, and the module's functionality is called from within
it.
With a framework, you tell it to "go". You do whatever the
documentation says in order to "use" the framework in the first place, you
attach your own functionality (possibly as described above), and then you tell
the framework to start processing. In this situation, the framework takes
the point of entry; the framework loops endlessly until the end signal appears;
and it is your code that is called from it the callback being one
such way of this happening..
In Soviet Russia, modules run you.
Building our understanding
Enough faffing around. Let's break the concept down into small parts, and then
put them back together into a better understanding. How else to do this but by
example?
The following example is a thought experiment only, and is unlikely to
work properly in the real world. Take it as training wheels but no bike.
Let's build an IRC framework. What is IRC? IRC is a distributed chat network.
It has servers connected to other servers, and they share data by magic or
something. Clients connect to one of these servers and send it data, which will
usually be a chat message. Then the servers share it and send it to their own
clients. Most of this is voodoo.
The important bit is the client bit. We have pretty concisely already described
the event loop (remember that?) that the framework will encapsulate:
- Accept server data
- Format server data for display
- Print server data
That's the easy bit. The IRC specification is documented
(honestly) and so we
don't have to understand how the server works in order to communicate with it.
[fn]This is in theory. In practice, we often do; many servers will implement
features not in, and sometimes contrary to, the RFC. We can safely ignore this
for the purposes of our thought experiment, and assume that the server we
connect to is compliant with the standards.[/fn]
This is a concept that might be becoming familiar to you: programming is made a
lot easier by means of us knowing what to expect, and not caring how it is
created. Think of it like consumerism. As long as we get what we want for a
reasonable price we don't care who made it.
That was satire.
Moving on, we now know (because we read the specification) a few things:
- What the server can tell us about
- How the server communicates this
- What we can tell the server to do
- How we can tell the server to do it
For example, we know that the data from the server will be plain text and
that it will be ASCII text. This means that every byte will be a
standard character.
Separating your concerns
Now if we look at that previous list we can see that it is divided into two
clear areas of concern. The first is what to do when receiving a message from
the server and the other is what to do when sending a message to the server. The
canny developer will realise at this point that these should be at least
two separate modules. Each needs have no communication whatsoever with the other
- in fact, this is even more formal in this case, because each side has a
different input. The input to one side is the server's messages; the
input to the other side is the client's commands.
We previously discussed separation of concerns when we talked about modules and
it is important enough to reiterate.
![/static/images/posts/npes-2/img1.png [/static/images/posts/npes-2/img1.png]](/static/images/posts/npes-2/img1.png)
Your server and client handling code needs to know the same basic data, like the
server you're connected to. This implies a third module that handles this
data as well.
![/static/images/posts/npes-2/img2.png [/static/images/posts/npes-2/img2.png]](/static/images/posts/npes-2/img2.png)
Our framework is now a conglomerate of three completely separate sections: the
server-handling code, the client-handling code and the connection-handling code.
I'm hoping by this point you are not too overwhelmed. Try not to concern
yourself with exactly how your three components will do what they do. We
are going to cover each section separately anyway, but it is, for the moment,
important that you have in your mind the idea of the server and client sending
and receiving data to and from a connection handler.
Designing software from this level is often very beneficial because it forces
you to actually write down, or at least think about, exactly what your software
needs to do. Being able to think about what it does without concerning
yourself with how it does it is an important skill to learn, because it
is portable to almost all languages.
However, it is quite understandable that without a good knowledge of at least
one language, you can be feeling a bit frustrated that all this is
possible without being adequately explained. Relax. These things take time.
Server stuff
Let's think about how our server stuff is going to work. In order for this to
be a framework rather than a module we have to:
- Handle the event loop
- Provide callback points for the user
[fn]When we're talking about modules, libraries and frameworks, the user is
the programmer who will ultimately use it for their own world-domination
plans. When we're talking about completed software, the user is the person
who runs the software and fulfils said plans.[/fn]
to insert their own code
We'll take the points in order.
The entry point is rather language-specific because some languages compile down
and some are interpreted. However, the entry point also need not be on the
server code. In fact, the entry point should not be on the server code,
because the framework incorporates more than just the processing of the server
messages. This means we can put that concern aside for later. Bonus points if
you are currently thinking about a fourth module to handle this!
So, the server message processing only needs to concern itself with handling the
event loop that receives a server message and does something sensible with it.
Even though up to now we have been talking in terms of one event loop, this
doesn't preclude multiple. Every language will have some way of accomplishing
this task, so if we just assume it's possible, we can allow for it in our design
of the framework.
Server messages come in two basic flavours: stuff we tell the user about, and
stuff we don't. For example, the server will send a PING command
periodically and we should reply to it so the server knows we're still there
and doesn't terminate the connection. The user doesn't care, so we don't tell
them.
Having said that, the bulk of the IRC data will be in the form of messages.
Messages are received because someone sent you one. You get sent one either
privately, sent directly to your nickname, or to a channel you are currently
observing. When we design software, it is often helpful to forget about
inconveniences like the fact we haven't written half of it yet. So at this point
we can totally ignore the fact we haven't even considered how the client
stuff will work - we can assume that, somehow, we are going to register our
interest in messages.
So what we will do is write an event loop that assumes that we are going to
receive messages, and worry about the other stuff later. Our event loop will
therefore look like this:
- Take a message from the server connection
- Inspect the message to determine what the command is
- Run some function whose job is to handle this command
- In that function, chop the command into more sensible bits (e.g remove some
of the special IRC content) and run the user's callback.
You can see we're already considering the points at which the user of the
framework can insert their own code. It makes perfect sense that we should
accept a message from the server, convert it to a more appropriate format, and
then let the user decide what to do with it.
![/static/images/posts/npes-2/img3.png [/static/images/posts/npes-2/img3.png]](/static/images/posts/npes-2/img3.png)
Client stuff
The client stuff is where the second use of a framework comes in. Fundamentally,
however, it is identical. On the server side we saw that the input came from the
server, and the output is processed and then passed on to the callbacks
that we decided would exist (but we left it completely open as to what they
might actually do).
On the client-message-sending side[fn]I don't want to use the term 'client side'
because this whole framework is, technically, the client[/fn] we have to have a
way of allowing the user to a) generate and b) send IRC messages to the server.
Server processing generally does itself. The arrival of a server message is
hardly ever related to something the user does. The sending of a message,
however, is directly related. We have to decide what we are concerned with and
what we leave to the user. It makes sense to split it up like this:
- We provide methods to create messages in the IRC language
- We use our connection to send those messages
- Our messages can be created by using techniques common to the language in
question
and
- The user of the framework creates a way of accepting input from the user of
the application
- The user of the framework converts that input into data that we can then
turn into IRC messages
You could argue that the framework could be responsible for all of this. That is
also a possibility. It is generally up to the writers of the framework to decide
how much control they want to take from the user. In our case, we want to allow
the user to send and receive IRC messages, but we don't want to dictate to them
how they should write their software. This means that the same framework could
create a GUI application, a command-line application, or even a web-based
application. Heck, there might well not be a real person on the end at all; it
might simply be a bot, designed to respond to messages with other messages.
Sometimes you will find that a framework has a core and some
extensions. The core of our framework could be the
server/client/connection stuff, and the extensions could be the different ways
of getting user input.
However you decide to do it, it remains the case that we need to create a way of
converting something the end user (user of the finished program) can understand
into something the IRC server can understand; the five steps listed above are a
(possibly oversimplified) run-down of how to do that.
If we provide modules in our framework, we can combine the concepts of the
previous article with the concepts in this one. The user could use our modules
to construct the necessary IRC messages out of the messages input into the
application, and then let the framework do the hard work of actually sending
them and dealing with the response.
So now our framework is an area of code that receives server messages and uses
callbacks to tell the user about them where relevant; and a bunch of modules
that the user can make use of in order to create messages to send to the server.
![/static/images/posts/npes-2/img4.png [/static/images/posts/npes-2/img4.png]](/static/images/posts/npes-2/img4.png)
Connection stuff
Lastly we should have an area of code that shepherds all the connections we make
to different servers. It will involve a lot of bookkeeping; it has to remember
IP addresses, port numbers, associated names. It has to keep connections alive
or retry them if they drop.
It will be responsible for actually sending and receiving the raw data from the
server. This area will run functions on the server stuff whenever the server
itself sends a message. This area will contain functions that the client stuff
will call on in order to send messages back.
IRC users generally connect to multiple servers. That means we need to provide a
function that will connect to a new server without disconnecting from the old
one. Recall that the framework is handling event loops: that suggests that, when
the framework is told to run, it can look out for special messages that tells
the framework to connect to a new server instead of sending the message onto the
existing server.
It also means each message will have to have a server associated with it,
regardless of which way the message is going. Most messages also have much more
information associated, such as the channel they were posted in, the nickname of
the person who said it, etc.
Anyway, that's pretty much our entire framework mapped out. Using it is simple
too.
Using it
To use it, you simply write code that the framework will run, by means of those
callbacks we described in the server stuff. You also need to write some way of
accepting input from an end user, because they will be the ones typing the
messages.
But as we mentioned, this may well be out of the scope for the framework. It is
certainly out of scope of the core. This separation of interests point that we
have been pressing is helping us prioritise. If we don't concern ourselves with
how we are going to receive data from a user, we can do things like write
small programs that pretend to be a user, and simply send different types
of messages to the server to test that the core of the framework is doing its
job.
Once we have the core running correctly, we can write more parts of the
framework as extensions to provide extra functionality, like a command-line
client. To do this, we would simply write those callbacks we've already
mentioned, and ship them with the core as part of the framework.
Conclusion
This particularly long entry on frameworks has hopefully enlightened you a bit.
From the muddy, impenetrable waters of the word "framework", we've defined a few
high-level concepts, and then pulled the whole idea apart and built it up.
We now know that a framework encapsulates the event loop (or many) of your
program. You may also hear the term program loop, which is basically the
same thing: simply an infinite loop that does everything that needs to be done,
then does it again until something changes that tells it to stop.
We know that an important difference between a framework and a module is that
when you use a module, you import its functionality into your program; but when
you use a framework, you add your functionality to its own, effectively
importing your functionality into it.
We've touched on the concepts of separation of interests - of having areas of
your framework dedicated to doing different, discrete tasks. We mentioned how
doing this can help with prioritising what you do, focusing on writing the
important functionality, and that we can then use that concept to write small
programs as tests for it.
We've even been scared off by our first RFC, which is basically a specification
for a protocol. What's a protocol, you ask? A protocol is just what it means in
real life. It's a set of rules that, if you follow them, will give you the
results you want.
Perhaps most importantly we've got a bit better at thinking in abstract terms.
Everything we've covered here is separate from the actual language you choose to
write in. It's all concepts and fuzz, but it's more knowledge than the word
"framework". Where once the word "framework" meant "As a new programmer I will
have no idea where to even begin", now it should mean, "Ah, this is some code
which will have installation instructions and a tutorial. When I use it I will
provide my own code for parts of the process, while it takes control of many
other parts itself."
Some examples of frameworks
- Catalyst MVC Framework
MVC means model-view-controller. It divides a web application into these three
logical units. The model is what holds the data, and communicates with the
database. The view is simply a set of templates that draws the data, usually in
HTML form. And the controller is where you put code that collates the models and
sets up the correct data for the template.
- Python Raw IRC
Framework
I wonder whether this Python IRC framework does the sorts of things we
considered for our own?
- Ruby on Rails
Rails is another one of those web frameworks, like Catalyst is. It also uses
the MVC pattern. These sorts of frameworks tend to take away a lot of the hard
work of getting the URLs in your site to actually show the page they are
supposed to. They also have lots of plugins and things like that, even to the
extent that you can just attach an entire shopping cart and checkout off of the
framework.
- The SDL library
The SDL library literally provides the entry point to your program, it being
eaiser to do in C++. The SDL library actually takes control of all the event
loops associated with drawing images to a window, and accepting input from your
users' devices. It also provides utilities for altering images in memory.
Top of page
2010-08-25
I've only worked at two companies that use SVN and it seems like most of the
problems with using it are not to do with SVN itself but with the slapdash
process it allows.
Here I list a few of the mistakes I've spotted. I'm not going to make the
mistake of saying this is a "top ten" (or top n), nor to say they are prevalent
or common or insurmountable. But what I do know is that if these companies had
not started off along these lines, it would have been cleaner sailing down the
line.
The problem, of course, is if something allows bad habits, then it might as well
encourage them. We have a similar problem with Perl: since you can code
without strict and warnings, people do code without strict and warnings,
and then they blame Perl.
Mistake 1: Trusting the defaults.
To Perl's no strict/no warnings default is SVN's corresponding failing: to
default to a dangerous condition. By default in SVN, everyone is committing to
the same - and indeed the master, de-facto, controlling, always perfect -
branch. Trunk.
Every source code repository needs a place that is safe from screwups.
Everything goes wrong at some point. That's why we have backups.
In SVN, the major issue is that branching is a bit of a headache. It's not
completely impossible - after all, you don't have to do anything manually. It
does have the branch command. The problem is that merging is hard when
you've finished. It's much easier to commit to trunk as you go along, and fix
merge conflicts one by one as you commit.
The problem, then, is that while development is in progress, the trunk is pretty
much constantly in a state of flux. There can be no point at which you say
"trunk works". You may be thinking that this isn't really an important
consideration if you are in development, but it is. Clients often want to know
progress, and if you have something to show them, you can keep them sweet. This
is especially true of web applications, because you can throw up skeleton pages
and click through them, explaining where functionality will be.
If your trunk, therefore, is almost always broken because it's mid-development,
then it could easily change between the phone call arranging a demonstration and
the time for demonstration, meaning, in a Microsoft-typical moment, that your
application chucks out errors instead of a demonstrative mock page.
My advice to you and your company would be to really learn how svn branch works.
Trust in its power. Merge to trunk only when the branch is working. Then
you can always proclaim that the current trunk is a working thing you can show.
Branch for features. Branch often. It's hard in SVN, I know. But SVN is old, and
you're using it. But whatever you do, make sure you stop polluting trunk.
Mistake 2: Not using the svnserve daemon
SVN supports several protocols for its repositories. There's http, over
which you can serve your files through a webserver. SVN integrates nicely with
apache using, surprisingly, Apache
Subversion. There's file, which points your repository to a
repostiory on your local filesystem. Then there's svn and svn+ssh.
These two use the svnserve daemon (possibly using ssh as the transport)
to define the existence of the repository. The svn+ssh protocol has the
same functionality as the svn protocol, except it uses ssh to send the
requests, rather than opening a socket or somesuch directly to a listening port
on the server.
While they all work, only two are portable: the svn and svn+ssh
protocols. Why? Let me give you a setup that isn't even worth considering, with
which I have had to work.
First, you get a Windows server. That's mistake number one. It's neither
reliable nor safe, nor can you easily administrate it. Then you attach a disk to
it and share this disk as a network drive. You format the disk using NTFS or
FAT32 or something stupid like that because you're using Windows and it doesn't
matter. Then you put a repository on it. Then you check that repository out
onto the same disk, through a network mount on a client computer.
The problem? You've just created a repository that uses the file
protocol, on a Microsoft file system. This will not work with anything
else.
Along comes a developer who prefers using Linux. What happens? Well thanks to
massive efforts in the community, Linux can now browse and mount Windows file
shares without a hitch. They can also write to them. Everything is going well
until, surprise surprise, he wants to commit.
When SVN commits it makes a lockfile and disables access to it. On Windows, god
knows how this works. This company used TortoiseSVN so they didn't have to think
too hard about difficult things. On Linux, the SVN client does this with
chmod.
Whups.
You can't use chmod on a Windows filesystem. It's impossible. The problem
is that file and http do not support any command that says "I am
committing. Lock my files.". However, the svn protocol does. If
you are using the svn protocol, you can use any client system,
because the svn client on that system will ask the server to lock the
files, and do the commit. By any other method, the svn client has to do the file
locking on its own, and this is basically impossible if you cross platforms with
your repository.
Forcing your developers to use Windows, by the way, is a good way of losing
developers.
Mistake 3: Different environments
This isn't really specific to SVN but I'll mention it anyway. Your production
environment and your development environment must be identical in every
possible way. This means the whole thing. The only thing you can omit is the
contents of your parent directory if your production server is running
multiple sites, or compiling multiple applications, or whatever.
Better still, have a staging environment on your production server. Now,
before you flame me, this holds an important benefit: You can show to your
clients exactly what they are getting, as it changes, and nothing can go
wrong when you put it live. In fact, all you need to do is rename the directory
for the new vhost! Of course, this only really applies when your client is a web
client, which they have been in my cases.
Mistake 4: No commit messages
Oh hell don't get me started.
Instead of explaining the same thing everyone else has explained, here's a
concrete example.
The website we made is one of the ones where we have had a dev environment, a
test vhost and a live vhost, the latter two on the same server. So we created a
file called site_status.php which defined a constant which, in turn,
decided for the rest of the site which mode to choose, i.e. which database to
use, what credentials, what base URL, etc.
So of course I'm on my own one week and I'm coming back to this site after
feedback from the client and I update my repository from the SVN. Then I hit my
dev URL and bam. Missing file, site_status.php.
Great. So I check the logs for the containing directory and sure enough there is
a commit where it has been deleted. No commit message.
After spending half an hour determining I am not a mind reader I decide to
undergo a more detailed investigation. The file still exists on the staging
area, as well as the other developers' checkouts and the checkout that tracks
trunk.
Still not a mind reader.
Inspecting every log message since the deletion, none of which is commented
upon, I eventually learn that the folder itself has been altered: It has had its
svn-ignore property set to site_status.php. From this I
(correctly) infer that this file is now intentionally unversioned, so it
can be edited on the various release targets without causing an upset to SVN. So
when I updated, SVN helpfully deleted a file and didn't tell me why.
SVN can only do what you tell it to do. Without commit messages, it cannot
possibly tell you why it is happening. What a waste of time to have to
work these things out.
While we're on the subject, meaningless commit messages are just as bad as no
commit message. If your message doesn't say why this log entry looks like
this, then it better be really astute at explaining what it does.
At $work[0], where I first encountered SVN, we eventually developed a pre-commit
hook. This prepopulated the commit message with a template, similar to the
following. I recommend your company starts using it (the concept if not the
template), especially if you have old projects that are being amended. You
should definitely require this sort of thing after you have released, because
any changes at that point better have a bloody good reason.
Change Request Number/Project Name:
Requested By:
Summary:
Conflicts:
Signed off by:
Notes:
Signed off by? Yes! A thousand times yes! Always have someone check over
your code, especially for new developers.
Yes you have a bug tracking system with trackable IDs. Yes, you have a formal
system for requesting changes. List merge conflicts. Summarise the changes you
made. Add any extra notes that someone looking at the log will want to
know.
Mistake 5: Using Windows
Don't get me wrong. I'm completely prejudiced. I hate Windows and I will never
willingly use it.
Why I hate it is a topic for a different post, but suffice to say that it causes
problems. The problem I had above would never have cropped up if I didn't have
to use Windows.
I don't have to use Windows except in order to use SVN, as explained even
further above.
If I didn't have to use Windows I wouldn't have to use TortoiseSVN*.
If I didn't have to use TortoiseSVN I would not have had the useful log
information hidden from me.
If I had seen the useful log information when I asked for the log, I would not
have spent half an hour trying to be clairvoyant.
That is all.
* I know I don't have to use TortoiseSVN. The alternative, however, is
Cygwin, which is hardly better. It is adequate, but not ideal, and has as many
problems as TortoiseSVN, albeit different ones.
Top of page
2010-06-05
I said that I need to figure out where to start and I was a bit annoyed by
the piecemeal fashion in which the tutorial I was reading was presenting it
to me. However, it was a good primer. Having read the first page or two I at
least have some idea of the important syntax points in Haskell, which is
useful coming from a Perl background because I know what's different. I used
Learn You A Haskell. I'll assume you, the
reader of this, will have read a page or two of that, but I don't think it's
that necessary because you should be able to keep up as we go along.
OK, Go
So this game is going to be entirely based on the call-and-response idea of the
old text adventure games. You type something, and it says something back. If
you type the right thing you progress and score points; if you type the wrong
thing you die or something; and if you type gobbledegook (or perfectly valid
English that it wasn't expecting) it tells you that it doesn't understand.
Anyway it should be obvious from this somewhat trite description that the first
thing we are going to need to learn to do is to read in stuff and print out
stuff.
Haskell's read-in function is called getLine and its print-out function is,
remarkably, putStrLn.
Examine the following code, courtesy of tchakkazulu.
main :: IO ()
main = do
name <- prompt "Hello. Who are you?"
putStrLn $ "Hello, " ++ name ++ ". Nice to meet you."
prompt :: String -> IO String
prompt line = do
putStrLn line
getLine
You should be familiar enough with Haskell just from the first bit of LYAH that
you recognise how functions are created and called. That said, the basics of
how functions are created and called are not all that abundant in this example.
That's because IO is, apparently, the sin bin of Haskell, in which all the
impure code gets put. So we've embarked on a journey, that's for sure.
In this example we've created the prompt function. Using a function
instead of doing this process in main itself makes more sense because we are
likely to do this a fair amount in this program.
The prompt function is defined as taking a String and returning an IO String.
Since IO is the sin bin of Haskell it should suffice to say that an IO String
is a thing that returns a String when you ask for one.
So the prompt function takes a String, prints it, and then reads a string in.
See that we define a function in Haskell by first its name, then whitespace,
then what in other languages would be the formal parameter list,
whitespace-separated, then =, then the body of the function.
We will talk about what do does later.
Whitespace
Whitespace in Haskell is the comma in any C-like language, or C-inspired
language, you care to mention. It separates arguments, or parameters, to
functions. It also separates the function name from its list.
The rationale
is that your most common operator is the shortest, and applying data to
functions is the raison d'etre of Haskell.
The other use of whitespace is to line up the blocks. You can use the old
brace-and-semicolon syntax if you prefer, in which case Haskell will not whine
about whitespace, but if you are going to use whitespace you must line your
stuff up. That's because if it's not lined up Haskell isn't prepared to start
making guesses about what you meant.
$
The $ is also known as 'apply' and has the effect of applying the things after
it to the thing before it.
The $ is an infix function, which means the thing that appears to the
left of it is the first argument, and the thing on the right is its second.
This is denoted by putting the function name in parentheses when you define it:
($) :: ( a -> b ) -> a -> b
This is the type signature of the function. There is a section on
type signatures below, and we'll discuss it there.
You can also use the same syntax in order to use the function as a prefix function:
f $ g = ($) f g
This is sometimes useful, especially when you are experimenting and new to it and
don't want to be confused by both the syntax of Haskell and whatever it is
you are trying to figure out in the first place.
$ is basically a separator, and changes the order in which the statement is
"parsed".
(It doesn't actually affect the parsing so much as it affects what you
have actually written in the first place. The parentheses analogy should clear
it up.)
Mathematically speaking, you can say:
f g h = f(g, h)
f $ g h = f(g(h))
More complexly:
f g h i j = f(g, h, i, j)
f $ g $ h i $ j = f(g(h, i(j)))
Lined up:
f $ g $ h i $ j
f ( g ( h,i ( j )))
With any luck you can begin to see why it is called 'apply'. It has the effect
of treating the first thing on its right as a function to apply as the argument
to the function on the left.
Remember that because Haskell is function-oriented, g is a
function. The $ is deciding whether the function is being passed as a
paramter to f along with h, or whether we are running g
with h as a parameter, and passing the result of g as the
only parameter to f.
The practical difference is easily expressed in terms of a language that
uses parentheses the same way mathematical notation does: It is the difference
between
print("Hello, ") . $name . ". Nice to meet you.";
and
print("Hello, " . $name . ". Nice to meet you.");
You can think of the $ as introducing a new set of parentheses, nested in
any previous set, that closes at the end of the line. In many cases, the exact
outcome of $ will depend on the type signatures of the functions involved, but we
get an idea here. Later we will explore type signatures.
If you think of how f $ g h would work, consider how $ is
defined. f will be its first argument and g h its second. Thus it
fulfils its purpose, which is to separate g h from f; g h
is forced to be seen as a function g with the argument h instead
of two arguments, g and h to the function f. Later we will
explore the way we define functions in Haskell, and delve deeper into the
meaning of the type signature above.
It is possible to use parentheses when calling functions in Haskell. Consider
that you wanted to express f(g(h), i(j)) in Haskell.
You couldn't use the $ notation to separate g(h) and i(j)
because the $ is shorthand for starting parentheses that close at the end of
the line. So we can't use $ to wrap them around h
You'd have to use parentheses:
f (g h) (i x) or f (g h) $ i x
Because the $ has the effect of nesting our analagous
parenthetical sections, this is the way we have for concatenating them instead.
In fact parentheses instead of $ is valid in Haskell, but you will
usually find $ used instead because $ is an actual function.
Type signatures
Let's look at the type signature of prompt. It says that prompt
takes a String and returns an IO String.
prompt :: String -> IO String
The double-colon notation is used to specify that we are giving the type
signature of prompt, rather than its definition. This is familiar
to anyone familiar with a formal OO language, where function signatures are
defined first, then their implementations later.
A list of types then follows. Types start with capital letters. This type list
maps to the parameter list; the last type is the return type of the function.
That means that a function's type is the same as the last type in the list.
(This is not strictly true, however. Later we will see how we can
interpret the type list in different ways, and apply the new knowledge to the
discussion on $ above.)
In C++ you might write:
int main(int, char**)
This tells us that main returns an integer and accepts an integer and a pointer
to a char* (a string array). The arguments are separated by commas (in this case
one comma because there are only two arguments), and the return type goes before
the name of the function. In this simple snippet we start to see the real
difference between C-like languages and function-oriented ones. In C++, this
function signature is basically telling us that this function can be treated
exactly as though it were an integer, provided we give it another integer and a
string array.
In Haskell it is slightly different. Because the function is the fundamental
unit, not the object or variable or whatever, the last type in the list is the
return type iff we provide values for all the others. If you don't, you
end up with a closure. We'll talk about this more under partial parameterisation.
I have not named the parameters here: this signature is only suitable for a
function's declaration. In Haskell, this is always how you do it. In C++ you
will name these parameters when you define the function; this concept
holds for Haskell.
IO Strings are types that interact with the outside world. That is what IO
usually means. An IO String is a type that will return a String when it is
probed for one.
You can tell the type of a procedure or variable with the :t
construct when you are running ghci.
ghci> :t getLine
getLine :: IO String
In C++, the function's parameters and their types both form the signature
of the function, and names are given to the variables themselves when you repeat
the whole signature when you write the function definition of the function we
declared above.
int main(int argc, char** argv) { return 0; }
Here we have the definition of the function, and so the int and char** are given
names. These names are necessary if you actually want to use the parameters.
In Haskell, the names of the parameters are given after the function name and
before its definition.
prompt :: String -> IO String
prompt line = do
putStrLn line
getLine
You can see that these in tandem tell Haskell that line is a String. The
compiler also knows that the do block must return an IO String, which is
the type of getLine.
The do-block in our
definition is basically cheating and turns the function body into a list of
things to do, basically like a plain old procedural language. This is pretty
much what you need to do when IO is around. The do-block returns the last thing
in it, like in Perl. So we can see that the last thing in the prompt function
is getLine, which, being in a do-block, is the return value of the function.
:t getLine tells us it is an IO String, which is what the function returns,
so Haskell is happy.
getLine :: IO String
Type variables
The final thing you should know about Haskell is that the types can be variables
too. The type signature of the apply function uses letters to define the types.
Where the same letter appears, it is the same type.
The type of $, as we saw above, uses lowercase letters to define the types.
These simply mean that any types can be used. Type a can be the same as
type b but it is not required: however, it is the case that all instances
of a will be the same type, and this type can be fixed as soon as the
function is used in context - i.e. with another function. Just like algebra!
This allows functions to be genericised across all types. A similar thing
is available to C++: templating. In C++ the template type has the same basic rules
as these here in Haskell: that to be a valid type, they must support certain
operations. In the apply function, there are no restrictions, and so any types
can be used.
This is often very useful when you know that your function is going to be
completely generic.
Functions as parameters
Haskell is function-oriented, as we keep saying. That means that everything is
a function, so everything you're doing here is defining what functions you can
pass around.
It may have struck you that it would be useful to be able to specify that an
input function have, itself, a specific signature.
You can do that with parentheses, like this:
stringFromInt :: ( Int -> String ) -> Int -> String
This function will return a String, given an Int and a function. The function it
is given must have the type Int -> String and therefore can be considered
a mapping function of sorts.
map :: Int -> String
Then we can say stringFromInt map 1 and the stringFromInt function
will take the map function and the number 1 and presumably apply
one to the other and return the String.
This is a stupid example you will never use but it gets across the point that
a function's signature can be specified in the type signature of the function
that is accepting it simply by using parentheses.
Partial Parameterisation
Let's revisit the concept of the last item in the type signature being the
return type of the function.
Let's say you define a function called add
add :: Int -> Int -> Int
add x y = x + y
As with any function in Haskell, its use can always be replaced with the actual
contents of the function.
Your function is a first-class citizen because Haskell is function-oriented.
That means that it is itself a variable that can be passed around. In fact,
when you call a function in Haskell, it basically replaces the function call
with the body of the function. Therefore, you can think of any situation where
add is seen as simply x + y.
But of course, the x and the y need to be given values. We should amend what we
just said to saying that the use of a function can always be replaced with the
parameterised contents of the function. add 1 2 is exactly
equivalent to 1 + 2 - which is exactly what the function definition says!
add 1 2 = 1 + 2.
In Haskell, the difference is entirely up to the compiler, and so
any real difference is an optimisation thing rather than a grammatical thing.
For the writer of Haskell code, these two things are completely equivalent.
What happens when we see add 1?
If you're wondering why you would see add with only one parameter,
consider that in Haskell, it being function-oriented, it is sensible to define
functions in terms of other functions, just like we define objects in terms of
other objects in OO languages. Therefore you might define something like this:
addOne :: Int -> Int
addOne x = add 1 x
In other languages you may refer to this concept as a closure.
It is a copy of the function where some or all of the variables are given
values: all that remains is for the function to actually be run, in this case
by being given the remaining parameter.
In this situation the second parameter to add is still given in code, but
it is still a variable. But the replacement of add with its contents is
still applicable: this function is indistinguishable from 1 + x.
This is an example of partial parameterisation. The add function
is in this case replacable with a version of the function with one of
its parameters specified.
To follow the logic, note that addOne 1 is completely indistinguishable
from add 1 1. This is the mathematician's friend: it's a problem that's
already been solved. Since we know that add 1 1 = 1 + 1 and now we
know that addOne 1 = add 1 1 we can safely say that addOne 1 = 1 + 1.
We have used commutative logic to show this, but Haskell knows it too. As soon
as we provide a value for the parameter to addOne, we also provide it to
the partially parameterised add, and hence we collapse the whole stack
into 1 + 1.
All this helps us to understand the difference it makes to the type signature
of the $ function. Briefly, it means that any number of the items at the
end of the type signature can be grouped together and considered to be a
function of that type signature
Let's look again at the add function.
add :: Int -> Int -> Int
add x y = x + y
The function signature, from what we know so far, says that it accepts two ints
and returns a single int. However, what we have just done has shown us that it
means another thing! It also means that it accepts one int, and returns a
function that takes one int and returns an int.
That means that these two function signatures are equivalent.
add :: Int -> Int -> Int
add :: Int -> ( Int -> Int )
The parentheses define a single return value, and in this case the return
value is a function whose signature is Int -> Int. This equivalence holds true
for all functions and for all values at the end of the signature list. That is,
you can replace any number of the types at the end of the type signature with a
single function that has that as its type signature.
If you replace all three, you simply get back the add function in the first
place. If you replace two, you get the partial parameterised version. If you
replace just one, it doesn't really make sense, but you still get the original
add function again.
Now we see why there is no distinction between the return type and the parameter
types. Any of the types in the list can be considered the first parameter
of a function whose type signature comprises the rest of the type list.
You can play with ghci to see this. If you ask for the type of a function that
is partially parameterised you will find that the answer is the rest of the
parameter list:
*Main> :t add 1
add 1 :: Int -> Int
You can load the add function by putting it in add.hs; then invoke ghci
from the same directory and type :l add
I don't know whether there's a name for this equivalence, but it is interesting,
and you should remember that it holds, even if you don't remember why it holds.
(Although I do find that remembering why it holds helps to remember that it holds
in the first place).
Now let's put the apply function in the light of this new-found knowledge.
Recall the signature of the apply function:
($) :: ( a -> b ) -> a -> b
We have discussed enough now to understand it. First we know that the letters
are type variables, meaning that they can hold any type. We also know
that the first parameter is a function of signature (a -> b). Third, we
know that the function is infix because it is defined with parentheses
around its name.
These three things tell us that whenever we see a $, the thing on its
left is the first argument and the thing on the right is its second. The thing
on its left must be a function because this is Haskell, and its signature
defines both a and b in the type signature of $.
The unnamed equivalence principle described above means that the type b (the
return type of the first argument to $) can, itself, be a whole function
with its own signature: the variable b can encompass this whole type signature
and so we can ignore its details and concentrate on just 'b'.
Let's take an example.
f $ g h
If we define the signature of f, we can define the types in the
signature of $.
f :: String -> IO
With this definition we can say that
($) :: (String -> IO) -> String -> IO
for this particular use of $
Since the second argument is g h, that means the type of g h must
be String; the IO returned will be the IO returned by f.
In order not to be bothered by what type h is, we have to allow for h
to be any type. That means we can define the type of g as:
g :: c -> String.
If we then wanted to go back, we could define f and g in terms of
$:
($) :: ( a -> b ) -> a -> b
f :: a -> b
g :: c -> a
The astute among us will have noticed that I chose the signature of
f anything but arbitrarily. In fact f's signature exactly matches
that of putStrLn and g is a loose analogy for (++).
Since (++) takes two arguments, we couldn't make a perfect analogy.
But note that
(++) :: [a] -> [a] -> [a]
and since we know that
($) :: ( a -> b ) -> a -> b
and
putStrLn :: String -> IO
then
putStrLn $ "Hello " ++ name
is
($) :: ( String -> IO ) -> String -> IO
(++) :: [Char] -> [Char] -> [Char]
This is because Haskell knows how to convert between String and [Char]. Strings
are just lists of Chars. We've not looked at lists yet, but it should be plain
enough that String ++ String concatenates them.
So you can see that Haskell knows what to do when you give it parameters for its
functions. Try it in ghci:
Prelude> :t ($) putStrLn
($) putStrLn :: String -> IO
You can easily see that because its first parameter is putStrLn, its second
parameter must be a string, and it must return an IO. (I am not currently sure
of the significance of the parentheses in this example so I have omitted them
for now).
This sort of knowing-what-you-mean is part and parcel of Haskell and later I
think we will be doing all sorts of crazy stuff with it. Let's leave it there
for now, though.
Top of page
2010-03-20
Hello. Yesterday I installed Haskell and today I tried to learn it. I am
not much better off now as I was before I installed it, except now I can
run Haskell things.
Why? Because the resource I'm using to learn it, although recommended to me,
has the same problems as all the other resources you use to learn languages.
That is, they give you all the building blocks with incoherent examples
(incoherent between one another, not to say that each example makes no
sense per se) from which you are somehow magically supposed to go "Ah yes,
now I have what I need to write a program in Haskell!".
Believe it or not, it doesn't work. That is why I am going to do it the way
all good software developers do anything, which is to dive right in without
any sort of forward planning whatsoever.
The Game
The game will be a command-line game and it will be a throwback to the early
days of computer gaming. First there was Adventure, then Zork, and Hitch-Hikers
Guide to the Galaxy, and many besides.
Let's relive the glory days in modern style and run a Zork game in Haskell. I
chose this sort of game for three reasons:
- It runs on the command line. This means we don't have to worry about things
like graphics or processor speed or even compatibility. If you can run Haskell
you can run the game.
- The game is simple. It's basically a call-and-response game. You type a
thing, processing happens, and it says something. What it says is the meat of
the program and hence point three.
- It is extensible. Whenever we think of an idea we can add it to the list of
things to do when we get around to it. We can start off simple and finish off
complex and all we need in order to create new things and new adventures is the
sadly rare ability to speak English.
Haskell
SubStack suggested I not use the Debian (or Ubuntu) ports of ghc because as
valiant as the maintainers try to be they are always way behind on this sort
of thing. So I installed it from source. You can get the distribution
packages here.
Distro packages usually suck so you can get the source
here. Follow the
INSTALL file's instructions and you get the basic Haskell compiler,
whereupon you should get the newest version of cabal-install from
here You can simply run the bootstrap.sh that comes with that in
order to install cabal. It installs the executable to somewhere in your home
directory so you might want to put it somewhere like /usr/bin or somewhere
else in your $PATH.
Anyway, cabal is the package manager for Haskell, like cpan is to Perl. It will
become useful.
To check it's working, run ghci. When I did this I got an error about gmp.
Unfortunately, when I got gmp from source and installed it, it still didn't
work, so I ran apt-get install libgmp3-dev. Naturally, this won't work on
non-Debian systems so head over to gmplib.org if your
package manager doesn't have it, and get it.
Remember that Haskell is still maturing as a lanaguage and as a whole suite of
libraries and packages that support it, so stuff being a problem is likely to
happen until a decent implementation of everything comes out of the miasma.
Think about Perl, which is on 5.10 in the mainstream now, and it barely has a
passable package manager!
Next in this series I'll have worked out how to actually start. Tune in!
Top of page
2010-03-01
Welcome to the first blog entry in Podcats Training, and indeed the first Podcats
blog entry ever!
I hastily set up this blog because it occurred to me that there are some things
that we seasoned veterans say to new people that they don't understand when they
are trying to learn to develop software for the first time. Thinking back, I
remember several such turns of phrase, bits of jargon, or concepts that I think
I should understand but in fact I don't.
Today we will deal with the eternal phrase "It handles that for you".
When we write programs we often find that we are simply piecing together other
people's work. Introductions to programming generally do not deal with this
concept quite as heavily as the prevalence of the practice in the industry
suggests they should. That is because introductions to programming languages
tend to concentrate on the basics of the language, such as how to control the
flow of the program.
Then they throw in the idea of frameworks and modules and we get
a bit confused. How do we include frameworks and modules into our code? How do
they work? What do they do, and how do I get them to do it?
To answer the question, consider what it might mean if we say that something
handles something for you. Clearly, there is some task that we want to do, and
of course the purpose of a module (or framework) is that you can delegate
to an existing code library.
A code library, of course, is simply some form of code written by someone else.
In some cases it will be precompiled code, and in other cases it will be the code
itself. That distinction is basically language-specific, although not necessarily.
A bit of further thought on the reasons why we would delegate our task would lead
you to the conclusion that there is a common problem or pitfall in the thing we
are trying to do that has been solved before by someone clever. It is the
solution to this problem that is bundled with your module or framework, and
indeed in many cases it is all the causes of the problem that have been thought
of, captured using clever programming logic, and hidden away from you in the
module's workings.
So we conclude that:
- The thing I am trying to do is difficult.
- There are problems associated with it. Possibly pitfalls.
- Someone else in the past, maybe even before I was born, kept falling
into these pitfalls.
- Instead of simply writing about the pitfalls, they have gone and written
a library that deals with them.
- Therefore, there is code in this library, framework or module that I can
call, and it will deliver the results to me.
The true implication of saying that "that is handled for you" is, therefore,
nothing more than saying that if you have a library function that you can somehow
call, the problems associated with doing that task are thought of and dealt with.
Example
Let's consider a simple example courtesy of Javascript, and, of course,
Microsoft's desire to do things their own way.
You may or may not know that when using Javascript, the way the browser works
with the code is different depending on the browser. Since most Javascript is
used to alter the page in some way, it seems rather a fundamental flaw that IE
would have a different way of getting a hold of the page's contents from
Netscape and derivatives.
Therefore, it was not uncommon to see in JS code some curious control statements
in the JS that would determine whether the browser used method A or method B for
getting a handle on this page.
Well, that particular hack seems so uncommon now that it is actually quite hard
to Google for (corrections welcome) but step in a Javascript framework to handle
that for us. Using jQuery, we do not need to do this
hack because jQuery [i]handles that for you[/h]. With jQuery, you simply need
to ask for the element in the page using a CSS selector, and jQuery will return
something back to you that represents that (or those) element(s)! Of course, the
concept of having something returned back to you is another one of those jargony
phrases you get a lot when you are learning, and that don't make sense until you
already understand it. We'll deal with that later, as well.
This simple example demonstrates that all we need to do is delegate to someone
else's code, and immediately not only the meaning but the benefit of having
something handle something for you is apparent. And all that without any code
in the first place!
Next time: What we mean by "framework".