

2048 in Erlang - skazka16
http://kukuruku.co/hub/erlang/2048-in-erlang

======
mononcqc
\- it might be easier to start your app with
`application:ensure_all_started(erl2048)`. If your dependencies are well set
in your app file, this will boot all dependencies for you.

\- Given you've already compiled, you don't need the `rebar compile
skip_deps=true` part to start the app -- that's just to recompile before you
boot it, but I guess you knew that already.

\- Eventually you get to look in releases, but they're a bit more complex.
Look at [http://relx.org](http://relx.org) for an easier way in. Releases will
let your system know how to boot apps (so you don't have to `-eval` anything),
and lets you pre-configure shell options (`-noshell -detached`) so that you
can just boot the server with a `./_rel/bin/my-release` call.

\- For information on supervisors and what things like `one_on_one` mean, see
[http://learnyousomeerlang.com/supervisors](http://learnyousomeerlang.com/supervisors)

\- The naming convention for Erlang functions is `some_function`, not
`someFunction`. This makes it easier to visually parse between variables
(`SomeVal`) and functions (`some_function`).

\- functions like `withinBounds({X, Y})` can be rewritten as
`within_bounds({X,Y}) -> (X > 0), (X =< ?SIZE) andalso (Y > 0), (Y =< ?SIZE).`
-- no need to pattern match there past the initial tuple. Remove the `_`
matching, and just let it crash if something doesn't fit. You'll see it in
logs (somewhere) and can fix it later.

\- Functions like `getVector(up) -> {-1,0}` could easily be made declarative
by using `vector(up) -> {-1,0}` or just `up() -> {-1,0}`.

\- Break out large functions more. There's often no reason to have a 100+
lines function in Erlang. If you end up nesting `case` constructs more than
two level deeps, start thinking about splitting the functions up and deferring
stuff to other function calls. Your code will also be more testable that way,
easier to reason about when something goes wrong, and easier to delete and
refactor without breaking anything.

~~~
skazka16
wow, that's a lot of useful information. Thank you. I haven't seen relx
before. Very good find, thank you.

~~~
qohen
Another tool you might want to check out is erlang.mk, by the author of the
Cowboy webserver, Loïc Hoguin -- from the announcement[1]:

 _erlang.mk is a rebar replacement. It was initially created for allowing a
faster development process than rebar and for better compatibility with Linux
build tools. It should work on Linux and OSX with GNU Make installed._

Here's how erlang.mk and relx can be used together to build releases (this is
from a post by Loïc)[2]:

 _There is two steps to building a release. First you need to build the
various OTP applications you want to include in the release. Once done, you
need to create the release itself, by including the Erlang runtime system
alongside the applications, a boot script to start the node and all its
applications, and some configuration files.

erlang.mk solves the first step. It is an include file for GNU Make. Just
including it in a Makefile is enough to allow building your project, fetching
and building dependencies, building documentation, performing static analysis
and more.

relx solves the second step. It is a release creation tool, wrapped into a
single executable file. It doesn't require a configuration file. And if you do
need one, it will be a pretty small one._

And here's a quick excerpt from a user-testimonial for erlang.mk, by Jesper L.
Andersen[3]:

 _When compiling from warm, it takes rebar 9 seconds to figure out that there
is nothing to do in the project. erlang.mk does the same thing in 0.2
seconds._

[1] [http://erlang.org/pipermail/erlang-
questions/2013-August/075...](http://erlang.org/pipermail/erlang-
questions/2013-August/075097.html)

[2] [http://ninenines.eu/articles/erlang.mk-and-
relx/](http://ninenines.eu/articles/erlang.mk-and-relx/)

[3] [https://medium.com/p/708597c0dd08](https://medium.com/p/708597c0dd08)

~~~
mononcqc
I personally still prefer to use rebar. If you want to make building a release
with relx part of rebar, use a compile hook:

{post_hooks,[{compile, "./relx"}]}.

in your rebar.config.

------
rdtsc
Very good write up!

Good overview of how to get started with Erlang. Sometimes those that give it
a first try are the best ones at writing a getting started guide.

> I'm not exactly sure what one_to_one is, but it works. It makes sense to
> look through Erlang documentation and find out the definition.

one_for_one just means that if one child of the supervisor dies, it will be
restarted. There other choice is all_for_one, which means that if one child
crashes, supervisor will restart all its children.

You can nest supervisors and create hierarchies of supervisors and workers and
restart them as you need. Each process has an isolated heap (just like OS
processes) and it will be neatly cleaned up by the garbage collector.

------
lostcolony
The *app.src file is used to store metadata about the application, and
compilation will munge it a bit and copy it into your ebin directory (without
the app.src bit I believe).

Essentially, it's what allows application:start(erl2048) to work. The {mod,
{erl2048_app, []}} param is what indicates you want to run the code in
erl2048_app.erl (though obviously the compiled beam file equivalent) as an
application; the application behavior says that starting an application calls
the equivalent start/# function.

So basically the order of startup when you call application:start(erl2048) is

Erlang looks for the munged erl2048.app.src, and loads that metadata.

The mod argument specifies a module to start.

The start function in that module is called (as it is expected to adhere to
the application behavior). That function should start up the top level
supervisor, which in turn is in charge of starting your entire supervisor
heirarchy, and by extension, your actual worker processes.

~~~
lostcolony
Some style stuff, for terseness, you can do things like change -

    
    
      move(left, State) ->
        move(getVector(left), State);
      move(right, State) -> 
        move(getVector(right), State);
      move(up, State) -> 
        move(getVector(up), State);
      move(down, State) -> 
        move(getVector(down), State);
      move(Vector, State) ->
        ...
    

to

    
    
      move(Direction, State) when is_atom(Direction) -> 
        move(getVector(Direction), State);
      move(Vector, State) ->
        ...

~~~
skazka16
ohh, I see. So I can pass Direction as an atom now..that's cool. Thanks!

------
gregkarlin
That was a very helpful tutorial. I plan on messin around with the code later
tonight. I really am starting to like kukuruku, haven't been able to find
content like this lately...

------
rakoo
2048 is the new "Hello world"

