This tutorial is brought to you by ErlangCamp 2010 - Chicago, October 23 and 24 - already at 95% capacity! It's gonna be totally sweet.Erlang is probably not the first language you'd think of for building console applications. Here's a typical "Hello World" application in Erlang:
After compiling the module, you'd run it as an application like this:-module(hello).
-export([hello/0]).
hello() ->
io:format("Hello World!~n").
Whoa, that's a lot of work just to print a simple message to standard output!$ erl -run hello hello -run init stop -noshell
Hello World!
Here's the same thing in Python:
And using it:#!/usr/bin/python
print "Hello World!"
Now that's more like it! It's no surprise that script languages like Python and Perl are used extensively to build console applications.python hello.py
So why bother using Erlang for this sort of thing? Erlang's core strength is handling extreme concurrency problems and building long running, fault tolerant applications. Surely you'd be better off sticking to Python, Perl, or even bash!
Actually, that thinking is basically correct. If you're competent in a scripting language, you probably want to start there. But consider Erlang for these reasons:
- You're using Erlang for other applications and want to avoid introducing another runtime dependency for your console apps
- You're a True Believer in functional languages and want to extend the goodness to your scripts
- You need to communicate with Erlang nodes or work with Erlang persisted terms (e.g. config files)
- You want to brag to your chums that you're an Erlang hipster and have entered the ranks of the cool kids!
Improving Erlang's Hello World
Enough strategy - let's fix that fugly "Hello World" app! Create a file named "hello" that looks like this:
Not as drop-dead simple as the Python version, but pretty close.#!/usr/bin/env escript
main(_) ->
io:format("Hello World!~n").
Let's run it:
If you want to execute it directly, change its permission:$ escript hello
Hello World!
Nice!$ chmod 755 hello
$ ./hello
Hello World!
The secret here is escript, which is installed with the standard Erlang distribution. If you can run erl, you can run escript. By using the shebang as the first line of the script, we turned this simple file into an bona fide executable Erlang application!
At this point, we have all we need to write Erlang console applications. If you want to automate something on a system and have a hankering to use some Erlang, this is how you'd do it.
Super Charging Erlang's Hello World
Let's take things further and carve out a full fledged console application famework. It's simple!
Grab the latest getopt Erlang source from github:
http://github.com/jcomellas/getoptThis is a terrific module that lets you parse command line arguments into validated Erlang terms using the nearly ubiquitous getopt convention.
What's getopt? If you've ever run an application from a command line, you've probably already seen the convention. Here's a quick summary:
- Command line options are differentiated from command line arguments
- By convention, options are always, well, optional
- Arguments may be optional but are frequently required
- Options are designated by either a leading single-dash "-" (short form) or a double-dash "--" (long form)
- Short form options are always a single character
- Options may have values, which follow the option name and a space (or alternatively an equals sign "=" for long forms)
Now that's the sort of high quality interface we want for our Erlang console apps!$ man ls
Let's tweak our "Hello World" app with a some new features:
- Support for a custom message
- Align the message - left, right, or center - within a particular number of spaces
- Print help/usage info if we ask for it
"Too much work" you say! Fear not - with the getopt module, it's really simple.$ ./hello --help
Usage: hello [-a] [-w ] [-h ] [message]
-a, --align alignment: left (default) | right | center
-w, --width width used by alignment (79)
-h, --help display this help and exit
message message to print (Hello World!)
Prints a message to standard output, aligning it with a
particular number of spaces (width).
First, we need a specification that getopt will use to parse command line argument. We'll add the following Erlang macro to the hello script:
You can read more about specifications in the getopt module documentation. The spec tells getopt what to expect in terms of arguments. This is used for both parsing the arguments and for printing usage documentation.-define(SPEC,
[{align, $a, "align", atom,
"alignment: left (default) | right | center"},
{width, $w, "width", {integer, 79},
"width used by alignment (79)"},
{help, $h, "help", boolean,
"display this help and exit"}]).
In our case, we have three options: one specifying the alignment, one for the width, and another for printing the program help. Each option has:
- A name, used to identify parsed values
- A short form (char) and long form (string) of the option
- A type and, optionally, a default value for the option
- Help text
There are several functions that we still need to define, but the core application logic is all there.main(Args) ->
case getopt:parse(?SPEC, Args) of
{ok, {Opts, MsgParts}} ->
maybe_help(Opts),
Msg = case MsgParts of
[] -> "Hello World!";
_ -> string:join(MsgParts, " ")
end,
Align = proplists:get_value(align, Opts),
Width = proplists:get_value(width, Opts),
io:format("~s~n", [format(Msg, Align, Width)]);
{error, _} -> usage(1)
end.
The function first parses the arguments passed on the command line. getopt:parse/2 returns {ok, {Options, Arguments}} if the command line args comply with the specification. Otherwise, it returns {error, Reason}. In main/1, we print a message given validated input or display the program usage if there are problems.
Once the arguments are parsed, getting the user input is a simple matter of reading from Options (a propery list - see Erlang's proplists module for details) and from Arguments (a list of non-option arguments). Values provided by the user are converted to the expected type and missing values are filled in with default values from the spec.
Next, let's define usage/1.
Here we use getopt:usage/4 to print the expected usage of the program to standard output. The 4-arity variant lets us specify additional help text for the usage. We also print detailed help text if the application is exiting normally (exit code is 0). If the application is exiting abnormally (e.g. the input from the user is invalid), we just display the usage. Finally, we terminate the application using erlang:halt/1.usage(Exit) ->
getopt:usage(
?SPEC, "hello", "[message]",
[{"message", "message to print (Hello World!)"}]),
case Exit of
0 ->
io:format("Prints a message to standard output, "
"aligning it with a particular number "
"of\nspaces (width).\n");
_ -> ok
end,
erlang:halt(Exit).
Our next function is maybe_help/1:
This function checks for the help option and calls usage/1 if it was specified.maybe_help(Opts) ->
case proplists:get_bool(help, Opts) of
true -> usage(0);
false -> ok
end.
Here's how we format the message:
format(_, _, Width) when Width < 0 -> error("invalid width");
format(Msg, undefined, Width) -> string:left(Msg, Width);
format(Msg, left, Width) -> string:left(Msg, Width);
format(Msg, right, Width) -> string:right(Msg, Width);
format(Msg, center, Width) -> string:centre(Msg, Width);
format(_, _, _) -> error("invalid align option").This is a dense bit of code, but it's very simple. It formats a message using one of the alignment functions in the string module. If there are problems with the input, it uses error/1 to complain:Pretty straight forward - print a message and exit with a non-zero value, indicating that an error occurred.error(Msg) ->
io:format("ERROR: ~s~n", [Msg]),
erlang:halt(1).
That's it! We have a strangely sophisticated "Hello World" application - and it's written in Erlang! Who'd have thunk?
Here's the complete hello source.
Let's try it out.
Worked as advertised! You're encouraged to try it our for yourself. Can you handle the power??$ ./hello --align=center --width=40 You looking at me?
You looking at me?
Feel free to this script as a template for your own console applications.
Recap
We started with the user interface. It's a good idea to build your console applications around "usage" documentation. We kept the required inputs to a minimum (zero actually) relying on defaults to fill in values the user doesn't care about. The getopt scheme works perfectly for this.
We always handle the --help option when provided (i.e. maybe_help/1) by printing the full usage, including any detailed documentation, and exiting.
We leveraged the goodness of functional decomposition and Erlang's pattern matching to write clean and maintainable code.
One finally point: escript applications have access to all of Erlang's core modules and any user defined modules that are in the Erlang path. You're also free to dynamically modify the path to link to your custom modules (see the code module). Take full advantage of this by building the core of your application as compiled Erlang modules and use escript code to call into them.
Now, using your new powers, go out, write and deploy Erlang console applications throughout the world - may our jobs as Erlang developers be duly secured!
No comments:
Post a Comment