Wednesday Oct 15, 2008

An Example Syntax in Haskell, Erlang and Scala

>>> Updated Oct 16:
I found some conventions of coding style make code more readable for Scala. For example, use

{ x => something } instead of (x => dosomething)

for anonymous function; Use x, y, z as the names of arguments of anonymous functions; Put all modifiers to the ahead line etc. That makes me can pick up these functions by eyes quickly.
======

It's actually my first time to write true Scala code, sounds strange? Before I write Scala code, I wrote a Scala IDE first, and am a bit familiar with Scala syntax now. And I've got about 1.5 year experience on Erlang, it began after I wrote ErlyBird.

Now it's time to write some real world Scala code, I choose to port Paul R. Brown's perpubplat blog engine, which is written in Haskell. And I have also some curiosities on how the syntax looks in Erlang, so I tried some Erlang code too.

Here's some code piece of entry module in Haskell, Erlang and Scala:

Original Haskell code piece

empty :: Model
empty = Model M.empty M.empty M.empty [] 0

build_model :: [Item] -> Model
build_model [] = empty
build_model items = Model (map_by permatitle sorted_items)
                    bid                    
                    (build_child_map sorted_items)
                    (sorted_items)
                    (n+1)
    where
      sorted_items = sort_by_created_reverse items
      bid = (map_by internal_id sorted_items)
      n = fst . M.findMax $ bid

build_child_map :: [Item] -> M.Map Int [Int]
build_child_map i = build_child_map_ (M.fromList $ (map (\x -> (internal_id x,[])) i)) i

-- Constructed to take advantage of the input being in sorted order.
build_child_map_ :: M.Map Int [Int] -> [Item] -> M.Map Int [Int]
build_child_map_ m [] = m
build_child_map_ m (i:is) = if (parent i == Nothing) then
                                build_child_map_ m is
                            else
                                build_child_map_ (M.insertWith (++) (unwrap $ parent i) [internal_id i] m) is

sort_by_created_reverse :: [Item] -> [Item]
sort_by_created_reverse = sortBy created_sort_reverse

created_sort_reverse :: Item -> Item -> Ordering
created_sort_reverse a b = compare (created b) (created a)

In Erlang:

% @spec empty :: Model
empty() -> #model{}.

% @spec build_model :: [Item] -> Model
build_model([]) -> empty();
build_model(Is) -> 
    SortedIs = sort_by_created_reverse(Is),
    Bid = dict:from_list([{I#item.internal_id, I} || I <- SortedIs]),
    N = lists:max(dict:fetch_keys(Bid)),
    
    #model{by_permatitle = dict:from_list([{X#item.permatitle, X} || X <- SortedIs]),
           by_int_id = Bid,               
           child_map = build_child_map(SortedIs),
           all_items = SortedIs,
           next_id = N + 1}.


% @spec build_child_map :: [Item] -> M.Map Int [Int]
build_child_map(Is) -> build_child_map_(dict:from_list(lists:map(fun (X) -> {X#item.internal_id, []} end), Is), Is).

%% Constructed to take advantage of the input being in sorted order.
% @spec build_child_map_ :: M.Map Int [Int] -> [Item] -> M.Map Int [Int]
build_child_map_(D, []) -> D;
build_child_map_(D, [I|Is]) -> 
    case I#item.parent of 
        undefined ->                
            build_child_map_(D, Is);
        P_Id ->
            build_child_map_(dict:append(unwrap(P_Id), I#item.internal_id, D), Is)
    end.

% @spec sort_by_created_reverse :: [Item] -> [Item]
sort_by_created_reverse(Is) -> lists:sort(fun created_sort_reverse/2, Is).

% @spec created_sort_reverse :: Item -> Item -> Ordering
created_sort_reverse(A, B) -> compare(B#item.created, A#item.created).

In Scala

object Entry {
    def empty = new Model()

    def build_model(is:List[Item]) = is match {
        case Nil => empty
        case _ =>
            val sortedIs = sortByCreatedReverse(is)
            val bid = Map() ++ sortedIs.map{ x => (x.internalId -> x) }
            val n = bid.keys.toList.sort{ (x, y) => x > y }.head // max

            new Model(Map() ++ sortedIs.map{ x => (x.permatitle -> x) },
                      bid,
                      buildChildMap(sortedIs),
                      sortedIs,
                      n + 1)
    }

    def buildChildMap(is:List[Item]) = buildChildMap_(Map() ++ is.map{ x => (x.internalId -> Nil) }, is)

    private
    def buildChildMap_(map:Map[Int, List[Int]], is:List[Item]) = {
        map ++ (for (i <- is if i.parent.isDefined; pid = i.parent.get; cids = map.getOrElse(pid, Nil)) 
                yield (pid -> (cids + i.internalId)))
    }

    def sortByCreatedReverse(is:List[Item]) = is.sort{ (x, y) => x.created before y.created }
}

>>> Updated Oct 16: Per Martin's suggestion, the above code can be written more Scala style (the reasons are in the comments). Thanks, Martin.

object Entry {
   def empty = new Model()

   def build_model(is:List[Item]) = is match {
       case Nil => empty
       case _ =>
           val sortedIs = sortByCreatedReverse(is)
           val bid = Map() ++ sortedIs.map{ x => (x.internalId -> x) }
           // use predefined max in Iterable
           val n = Iterable.max(bid.keys.toList)   

           new Model(Map() ++ sortedIs.map{ x => (x.permatitle -> x) },
                     bid,
                     buildChildMap(sortedIs),
                     sortedIs,
                     n + 1)
   }

   // you can use a wildcard anonymousfunction here
   def buildChildMap(is:List[Item]) = buildChildMap_(Map() ++ is.map(_.internalId -> Nil), is)

   private
   def buildChildMap_(map:Map[Int, List[Int]], is:List[Item]) =
       map ++ {  // rewrite for so that definitions go into body -- it's more efficient.
           for (i <- is if i.parent.isDefined) yield {
               val pid = i.parent.get
               val cids = map.getOrElse(pid, Nil)
               pid -> (cids + i.internalId)
           }
       }
       
   // you can use a wildcard anonymous function here
   def sortByCreatedReverse(is:List[Item]) = is.sort{ _.created before _.created } 
}
======

I use ErlyBird for Erlang coding, and Scala for NetBeans for Scala coding. The experience seems that IDE is much aware of Scala, and I can get the typing a bit faster than writing Erlang code.

If you are not familiar with all these 3 languages, which one looks more understandable?

Comments:

Of the three, I am only familiar with Erlang, but Scala seems to be the most clear.

Posted by Matt Williamson on October 15, 2008 at 01:54 PM PDT #

I could probably make that hunk of code in perpubplat more compact and idiomatic. The blog data model was the first part of that app that I wrote, and it was definitely early on in the learning process.

An Erlang version of the app could definitely benefit from some of the built-in middleware that Erlang includes, notably ETS and DETS.

Posted by Paul Brown on October 15, 2008 at 08:17 PM PDT #

really, all three are ugly as sin.

Don't say this is objective,it is fact.

I been professionally developing for 12 years and generally for 15

I've used a lot of crap.

I'll say this, scala is ok looking in syntax, ( I am used to groovy ) but case Nil.... Case _...

WTF

Posted by really on October 15, 2008 at 10:38 PM PDT #


I think a simpler coding would be in an OO style rather than a functional style. Which could be done in Scala anyway. Below is the code in Java.



static class ItemsByInternalID extends TreeMap<Integer, Item> {
ItemsByInternalID( final Item... items ) {
for ( final Item item : items ) { put( item.internalID, item ); }
}
}

static class ItemsByPermatitle extends TreeMap<Integer, Item> {
ItemsByPermatitle( final Item... items ) {
for ( final Item item : items ) { put( item.internalID, item ); }
}
}

Model buildModel( final Item[] items ) {
if ( ( items == null ) || ( items.length == 0 ) ) { return new Model(); }
return new Model( new ItemsByPermatitle( items ),
new ItemsByInternalID( items ) );
}



Note I didn't code build child tree since I didn't understand this bit. Also I didn't bother with the max function since this is in a tree map already.



PS Keep up the good work on the Scala IDE - works great.

Posted by Howard Lovatt on October 16, 2008 at 05:13 AM PDT #

Are you sure Howard?
Full functional implementation goes in up to 30 LOC. Your partial implementation goes with 20 LOC and doesn't do "child tree since I didn't understand this bit" :-) It is funny consideration that "simpler coding would be in an OO style rather than a functional style". Code whole bunch of issue in OO style and compare than.

Posted by Hynek (Pichi) Vychodil on October 16, 2008 at 09:19 AM PDT #

Scala

IMHO pattern matching in method build_model is unnecessary because you don't
extract any values/objects to use in subsequent code. A simple if(){}else{} should suffice - making the code more readable(again IMHO :D ).
Andy

Posted by Andy on October 16, 2008 at 09:58 AM PDT #


@Hynek,



I don't necessarily associate shorter with simpler, quite the opposite in many cases, and line count is very dependent on the layout style used. In the Java code I presented each line is simple, most people tend to be able to follow a sequence of simple lines better than a few complicated ones.



The Java code for child is something like this:



static class ItemsByParent extends HashMap<Integer, Set<Integer>> {
ItemsByParent( final Item... items ) {
for ( final Item item : items ) {
final Item parent = item.parent;
if ( parent == null ) { continue; }
Set<Integer> children = get( parent.internalID );
if ( children == null ) { children = new HashSet<Integer>(); }
children.add( item.internalID );
put( parent.internalID, children );
}
}
}


PS It isn't great that the child code is hard to follow and hardly an advert for simple coding :)



PPS I am familiar with functional programming - but I don't think it is always the best solution :)

Posted by Howard Lovatt on October 16, 2008 at 11:40 AM PDT #

Of course the expression "readable code" is highly subjective. Compact Haskell code (and other functional code) tends to use a lot of operators which makes it totally unreadable to anyone not familiar with the operator precedence and meaning. Although, for an experienced Haskell programmer the code is easy to read.

Java code on the other hand is often very "wordy" and thus can be read quite easily by a non-Java expert. I think this is one of the reasons Java has become so successful.

The compromise between these two approaches is not easy to make and Scala offers a good middle-ground solution allowing method names to be used as operators, and by making it easy to "add" new methods to existing classes. Disclaimer: I'm in no way implying that Scala syntax doesn't have it warts :)

Posted by Jesper Nordenberg on October 16, 2008 at 01:23 PM PDT #

@Jasper,

I agree with your comments. The Java code I wrote could be written in Scala and would certainly be shorter and probably not loose clarity, i.e. Scala strikes a good balance between conciseness and clarity. The main change I made to make the code clearer was to change away from a functional style to an OO style. I was making the point that functional isn't always the best choice.

Posted by Howard Lovatt on October 17, 2008 at 12:12 AM PDT #

Too many browsers, and now too many scripting languages... Anyway the situation is better than everyone using Microsoft. Atleast we have a choice, whether its for good or not so good.

Posted by Litty Joseph on October 18, 2008 at 12:10 PM PDT #

The Erlang version can be made slightly more declarative by converting the case into function clauses in <code>build_child_map_/2</code>. I find the constrained syntax of Erlang makes it easier to understand than either of the other solutions.


% @spec empty :: Model
empty() -> #model{}.

% @spec build_model :: [Item] -> Model
build_model([]) -> empty();
build_model(Is) ->
SortedIs = sort_by_created_reverse(Is),
Bid = dict:from_list([{I#item.internal_id, I} || I <- SortedIs]),
N = lists:max(dict:fetch_keys(Bid)),

#model{by_permatitle = dict:from_list([{X#item.permatitle, X} || X <- SortedIs]),
by_int_id = Bid,
child_map = build_child_map(SortedIs),
all_items = SortedIs,
next_id = N + 1}.

% @spec build_child_map :: [Item] -> M.Map Int [Int]
build_child_map(Is) -> build_child_map_(dict:from_list(lists:map(fun (X) -> {X#item.internal_id, []} end), Is), Is).

%% Constructed to take advantage of the input being in sorted order.
% @spec build_child_map_ :: M.Map Int [Int] -> [Item] -> M.Map Int [Int]
build_child_map_(D, []) -> D;
build_child_map_(D, [#item{parent=undefined}|Is]) -> build_child_map_(D, Is);
build_child_map_(D, [#item{parent=P_Id, internal_id=Id}|Is]) ->
build_child_map_(dict:append(unwrap(P_Id), Id, D), Is).

% @spec sort_by_created_reverse :: [Item] -> [Item]
sort_by_created_reverse(Is) -> lists:sort(fun created_sort_reverse/2, Is).

% @spec created_sort_reverse :: Item -> Item -> Ordering
created_sort_reverse(A, B) -> compare(B#item.created, A#item.created).

Sorry for the weird spacing. Firefox seems to have line-encoding issues with textareas on Linux.

Posted by Alain O'Dea on October 21, 2008 at 09:11 PM PDT #

Post a Comment:
Comments are closed for this entry.