I have been working on a number of Erlang-based projects this week; it's spring break! Most of them are just simple libraries. I would think that my code is not the greatest, but I did notice a few things.
1. Having variables not change state does help. Coupling that with unit testing, it makes debugging quite straightforward. The fact is, is that when you focus using functional techniques, by that virtue alone, you remove indeterminacy in what values variables are. Imperative programming did tend to make me a sloppier programmer, I think, and for this, I always felt a bit uncomfortable with the type of code I was writing. In Erlang, most errors that I am making are mostly silly pattern matching errors. I will admit, in this regard, just in terms of the variable issue, I feel more confident with the code that I am writing.
2. I faced some challenges when I was dealing with looping over some data structure that seemed perfect for a regular loop. In these cases, given that I forced myself to pose the question in a functional way, the resulting code was more concise in the end.
3. Besides minor problems that would come up, I have been consistently producing semi-complicated functions that work correctly the first time. I have not documented specifically when this has occurred. All I remember is that it happened a number of times and I was pleasantly surprised. It is a pleasant surprise because it never seemed to happen that way when working in an imperative language. In functional programming, with Erlang, there are cases where I consider what the problem is, write a little documentation to describe it, then just write it (I have not committed myself to TDD yet). Thinking functionally gives me a chance to look at the problem on a very high level, like, in terms of how the data structure is structured, the types of transformations that are applied to the structure, and what the results of functions over the data structure are. With those in mind, producing good-working functions tend to be the result.
4. I will admit that I have not worked much concurrency with Erlang. Most of the work I have done has been using sequential Erlang. Concurrency is a little harder, but the times I have actually worked with it were times when I just sat down and coded something, not exactly knowing what I was coding. I wrote a short program that opens a large number of processes and then each process sends a message to each other process. There was some weird things that were occurring with it, but it works, and not being a multi-threading wizard by a long shot, I think this was really really nice.
5. Pattern matching is probably one of the most useful language features next to variables not changing values (of course though, the two ideas are connected, since variables are patterns.) The type of things that I am doing with pattern matching is just utterly amazing; I use it in every program I write. The power lies in how I am able to describe large ideas through simple functions, by a matter of how the particular function accepts its input. In one tool I am writing (the one I mentioned in the prior post), I can easily categorize a cross-section of features for different parts of the execution of the tool using patterns; the approach is concise and is very clear to me. Here is an example:
when_valid(login, Form, Args) ->
Vals = erlyform_maker:form_values(Form),
Results = yak:login_user(proplists:get_value(username, Vals),
proplists:get_value(p1, Vals)),
case Results of
{error, user_not_found} ->
{failure, "Username or password was incorrect. Try again."};
{atomic, SessionId} ->
yaws_api:setcookie("yak", SessionId}
end;
when_valid(signup, Form, Args) ->
Vals = erlyform_maker:form_values(Form),
Results = yak:create_user(proplists:get_value(username, Vals),
proplists:get_value(p1, Vals)),
case Results of
{error, username_already_exists} ->
{failure, "The username you selected already exists."};
_ ->
ok
end.
In the code above, I handle a case where web form data is valid. I have two such forms in this case, and they are both related to authentication. My tool goes ahead and validates the data that comes in and is able to call the right function above. I can't explain how neat this is. I wish Python had something like this.
(Let me explain a design goal in this. If a user submits a form on the web, it comes to my server and my tool checks to see if the form is valid. If so, it calls the "when_valid" function. If there is no "when_valid" function, it calls a default version. The "when_valid" function just returns data. This is opposed to a normal ErlyWeb controller where you return an ErlyWeb tuple. I separated that out, since I never liked framework-level code to be mixed in such a way with this type of application logic.)
6. Not worrying about OOP has helped me immensely. When I stopped thinking about OOPish things, which is one reason why I started using functional programming more, it has helped me focus on my problems more. There are a number of things that have started to go through my head on this issue. Of course, it is a bit personalized. One of the main issues is that, whenever I am working in an OOP language, I feel obligated to do things in an OOPish way and not in a way that solves the problem most simply, as well as cleanly. This is merely my way of wanting to use the best practices of the language. To me though, the best practices of a given OOP language are not really the best practices to me.
The confirmation of my incremental success that I stated earlier lies in the absence of OOP. Maybe for big enterprise-y stuff it works nice. Maybe it is nice to know what a particular piece of data is, in the context of a software system. Maybe something like data encapsulation on objects really does wonders for ensuring some form of safety of implementation in big software systems. But, for the first, I am not working on enterprise-y stuff. For the second, if I need to say what something is, I put it in a tuple, and document that, for example, the data that is being returned is a tuple that begins with a specific atom. And, for the third, if I need data encapsulation, I always got the support of Erlang's module system.
I could go on what *I* do not need in OOP. This is not the point. I have come to the opinion now if I am worrying myself over side concerns to suit the best practices of a language, maybe I should try another language. This is, at least, for cases where I have complete control over language choice.
I do worry over Erlang standards. I lay out all my projects the OTP way. I try to focus on writing code with the principle of "just let it crash if it fails." But see, these things don't get in my way.
Anyway, I went on too long. So, I'll shush now.