Thursday, June 18, 2009

Erlang for Java programmer - power of functions

I have been using Java for more than 10 years now, and at the time I had come across Erlang, while I was excited about what Erlang has to offer, I was a bit worried on how to survive in absence of beloved OO features, such as abstract classes, polymorphism, inheritance etc. It turned out that these questions were somewhat irrelevant, instead I should have asked:



How the problems that OOP (Java, in particular) solves using feature "X" can be solved by functional language (Erlang)?




Polymorhysm vs. Higher-order functions



I'd like to show a practical example where Java programmer would use polymorphism, and Erlang programmer would use higher-order functions.



One of my Jabber/XMPP projects required message filtering based on some set of conditions. The customer wanted to implement some kind of censorship for their chat rooms. If incoming message fails any of those conditions, it has to be dropped. Let's assume that for this task we will maintain a list of pairs {condition, text}, where condition is a boolean string function that applies to the message text, for example the list could look like:

{"contains", "@$#*%!"}, {"equals", "This chatroom sucks"}, {"contains", "morons"}

etc.



So to pass the "appropriateness" test, the incoming message should fail each condition in a condition list.

Now, let's limit ourseleves to 2 functions (contains and equals) and see how we go about coding this in Java:




package com.teamhand.messaging.utils;



import java.util.ArrayList;

import java.util.List;



public class Censorship {

List <Condition> conditions = new ArrayList<Condition<();

public Censorship() {

super();

}



public void addCondition(Condition condition) {

conditions.add(condition);

}



public boolean checkMessage(String message) {

for (Condition condition : conditions) {

if (condition.check(message)) return false; // message has been censored

}

return true; // message has passed the censorship

}



public static void main(String[] args) {

ContainsCondition c1 = new ContainsCondition("@$#*%!");

EqualsCondition c2 = new EqualsCondition("This chatroom sucks");

ContainsCondition c3 = new ContainsCondition("morons");



Censorship c = new Censorship();

c.addCondition(c1);

c.addCondition(c2);

c.addCondition(c3);

String message1 = "I'm a good message";

String message2 = "Are you gonna listen, you morons?";

System.out.println(c.checkMessage(message1));

System.out.println(c.checkMessage(message2));

}

}



// Condition class

package com.teamhand.messaging.utils;



public abstract class Condition {

public abstract boolean check(String message);

}



//Implementation classes

//

//

package com.teamhand.messaging.utils;



// ContainsCondition class

public class ContainsCondition extends Condition {

private String template;

public ContainsCondition(String template) {

this.template = template;

}



@Override

public boolean check(String message) {

return message.contains(template);

}



}



// EqualsCondition class

package com.teamhand.messaging.utils;



public class EqualsCondition extends Condition{



private String template;



public EqualsCondition(String template) {

this.template = template;

}



public boolean check(String message) {

return message.equals(template);

}



}









The implementation is typical - we have a polymorphic list of Condition instances, whose classes implement condition-specific check() method.



Now, it's Erlang's turn:




-module(censorship).

-export([check_message/2, test/0]).



condition({"contains", Phrase}) ->

fun(MsgBody) ->

string:str(MsgBody, Phrase) > 0

end;



condition({"equals", Phrase}) ->

fun(MsgBody) ->

(MsgBody == Phrase)

end;



condition(Other) ->

throw({error, {not_supported, Other}}).



%% Returns false is message should be censored, true otherwise

check_message(Message, Conditions) ->

not lists:any(fun(Condition) ->

Condition(Message) end, Conditions).



test() ->

C1 = condition({"contains", "@$#*%!"}),

C2 = condition({"equals", "This chatroom sucks"}),

C3 = condition({"contains", "morons"}),

CondList = [C1, C2, C3],

{check_message("I'm a good message", CondList), check_message("Are you gonna listen, you morons?", CondList)}.





Note how condition/1 function returns another function, so in check_message/2 we have a list of functions, each one representing a condition. As you may have expected, the solutions are very similar, except the Erlang one bears significantly less code.



To conclude, this post is not to start another flame war on merits of languages, but rather for those OOP programmers who have decided to try Erlang and may need some help in finding common ground while exploring functional style.

No comments:

Post a Comment