From: Richard Cole Date: 2005-04-11T10:05:13+09:00 Subject: Ocaml and Ruby reflection was Re: Welcome to our (ruby-talk ML) Ara.T.Howard@noaa.gov wrote: > On Sun, 10 Apr 2005, Richard Cole wrote: > > > > nice summary. can you comment on ocaml further? specifically why you > wish > you were programming in ruby while using it? i've recently started > learning > it and am curious as to others' experiences. There's a really nice quote by Matz that sums it up for me: But if I look at the code, I need to apply the rule with my brain too. This was taken from an interview: http://www.artima.com/intv/ruby2.html (A good read, I recommend it if you haven't). I'm taking the quote out of context, but for me that sums up the difficulty that I have with Ocaml, and what I like about Ruby. Let me demonstrate this with a little bit of code. This is some ocaml, don't read it too carefully: let sibling_sym_metric_one (lattice: ('a,'b) concept_lattice) (pos: int -> (float * float)) = let count_sib_sym parent children = let points = fold_left (fun x z -> insert (pos x) z) empty children in let (parent_x,parent_y) = pos parent in let count = fold_left (fun x z -> let (pos_x,pos_y) = pos x in if is_member ((2.*.parent_x) -. pos_x,pos_y) points then z else z - 1 ) 0 children in count in lattice#index_fold_left (fun (x,v) z -> z + (count_sib_sym x (lattice#lower_covers x))) 0 What does it do? Basically it iterates over a lattice (think of it as a DAG or a tree) and for each element the algorithm counts the number of children that have a symetric sibling, that is: children who have a sibling whose x-position relative to the parent is -1 times the parent relative x-position of the child. I'm practiced at writing stuff in this style (for good or bad), and also I wrote the function, so I know what it does, but I haven't found very many other people who understand that code. Maybe I'm just bad at writing Ocaml code, but in Ruby it becomes def sibling_sym_metric_one(lattice, position) count = 0 for parent in lattice do points = Set.new for a in lattice do parent_relative_point = [pos[a][0] - pos[parent][0], pos[a][1] - pos[parent][1]] points.add(parent_relative_point) end for child in lattice.lower_covers[parent] do sym_rel_child_pos = [pos[child][0] - pos[parent][0], pos[child][1] - pos[parent][1]] sym_rel_child_pos[0] *= -1 if points.include?(sym_rel_child_pos) then count = count + 1 end end end end I find the Ruby version so much easier to understand. (I haven't compiled or tested it, so maybe it contains errors). It also begs some refactoring (there's a repeated function for instance). When I program in Ocaml I find myself constantly using fold_left instead of for loops and that is just hard work on my poor brain. Ocaml type checks the code which catches very many errors before the program is ever run even once. I'm constantly suprised by bugs that I get in my ocaml code that somehow make it through the type checker, they're usually on of two sorts, (i) some sort of variant of loosing a minus sign in algerba, e.g. folding in the wrong direction in a case when it matters, or (ii) the algorithm was totally wrong to begin with (when it was concieved in my mind). In Ruby I write a lot of unit tests because I expect errors. Also you can't ask the interpreter what the return type of a function is, or what the types of the arguments are, its all implicit so you need to invest more time documenting the code. But that's a good thing right? I'd like to have interfaces in Ruby so I can specify type constraints on arguments and return values, the are constraints there implicitly already, I want to make them explicit. Problem is that getting the addition of interfaces to Ruby right, and finding out how to use them properly, is hard. What I like about both Ruby and Ocaml is that you are not bound to a brain dead static type system, e.g. Java. You have quite a lot of freedom. One warning in respect of Ocaml objects --- generic methods are a bit of pain and require quite a bit of work. I also can never make up my mind between the functional half of Ocaml or the OO half, and so I ended up wraping the functional half in an OO half which was quite a lot of donkey work. Ocaml encourages abstraction. The more abstraction you use the smaller your programs becomes and the more repeated code is removed, but at the same time the program can become harder to understand because they end up being the composition of a lot of abstractions. Which goes back to the quote I started this message with. The other thing about abstractions is: If you write a library of functions on demand, as you need the functions, then over your implemented functions there's a kind of logic which enables you to refactor it, to abstract it, the thing is, one single counter example (i.e. one new function) can totally change your logic and invalidate a lot of refactoring or design. This is kind of a problem with XP driven design, but I don't have a solution for it. One last thing, let me have a go at translating that ruby code back in Ocaml (sans types): method sibling_sym_metric_one lattice position = count <- 0 lattice.each (fun parent -> let points = new Set in let parent_point = pos parent in lattice#each (fun a -> let a_point = pos a in let parent_relative_point = Point.minus a_point parent_point in points#insert(parent_relative_point) ) (lattice#lower_covers parent).each (fun child -> let rel_child_pos = Point.minus (pos a) (pos child) in let sym_rel_child_pos = { rel_child_pos with x=rel_child_pos.x *. -1 } in if points#is_member sym_rel_child_pos then count <- count + 1 end ) ) That's better, having been informed by the Ruby code, but I still prefer the ruby version :) That's two more bits worth. regards, Richard.