OCamel, Oh what?

Started by
122 comments, last by GameDev.net 18 years ago
Quote:Original post by MDI
The horrific C++ templated mess gets transformed to a few lines of clean O'Caml. Functional languages really are king.

I'd like to remind you that equivalent solution in O'Caml hasn't arrived yet. And it won't, until O'Caml supports GADTs.

Since Scala added support for GADTs recently, I can give you a glimpse of what's to be expected:
object wtf{	abstract class Term[T]	case class Lit(i : Int) extends Term[Int]	case class Inc(t : Term[Int]) extends Term[Int]	case class IsZ(t : Term[Int]) extends Term[boolean]	case class If[T](c : Term[boolean], t : Term[T], e : Term[T]) extends Term[T]	case class Pai[A, B](c : Term[A], t : Term) extends Term[Pair[A, B]]	case class Fst[A, B](t : Term[Pair[A, B]]) extends Term[A]	case class Snd[A, B](t : Term[Pair[A, B]]) extends Term	def eval[T](t0 : Term[T]) : T =	{		t0 match {			case Lit(i) => i			case Inc(t) => eval(t) + 1			case IsZ(t) => eval(t) == 0			case If(b, t, e) => if (eval(b)) eval(t) else eval(e)			case Pai(t, u) => Pair(eval(t), eval(u))			case Fst(t) => eval(t) match { case Pair(x, _) => x }			case Snd(t) => eval(t) match { case Pair(_, x) => x }		}	}	def main(args : Array[String]) : Unit = {		val t = Pai(IsZ(Lit(0)), If(IsZ(Inc(Lit(10))), Lit(1337), Lit(999)))		Console.println(eval(t).toString())	}}
"Dawn is a tramp." Dusk.
Advertisement
Quote:Original post by wahoodra
Quote:Original post by MDI
The horrific C++ templated mess gets transformed to a few lines of clean O'Caml. Functional languages really are king.

I'd like to remind you that equivalent solution in O'Caml hasn't arrived yet. And it won't, until O'Caml supports GADTs.


I still don't see how the O'Caml program is not equivalent. What can the C++ version do that the O'Caml one can't?
This is a good thread in my opinion, I think it is worthwhile to understand the differences. I have only worked with SML and scheme, but not ocaml but I wanted to give a few humble additional points that are related.

One thing about choosing your tool for a solution is the maintainability. From my experience on a very large project with C++ and java code, the maintainability is very good because they have taken the time to introduce templated design patterns in just the right places to make common activities (object creation -- the factory pattern) and persistence easier to manage for folks who are trying to fix bugs. In my case we are working with financial use cases -- you don't really want to have an obfuscated block of code in front of you that takes advantage of closures or lazy evaluation for cases that are high-traffic in making transactions. What C++ allows you to do in this case that a more purely functional language does not is having a close proximity to english in explaining what is happening. A small example:

bool AnalyzeReferenceSpending(MoneyTransaction trans)
{
ReferenceChain refChain = trans.GetReferenceChain();

int totalRefSpending;
int totalRefAvailable;
for (iterator on refChain obj)
{
totalRefSpending += (*iterator).GetSpent();
totalRefAvailable += (*iterator).GetAvailable();
}
if (trans + (totalRefSpending - totalRefAvailable) > 0 )
return true;
else
return false;
}

This is a contrived example but it does illustrate the style of transaction processing that we have in financial applications. When you have someone coming in to make a change to this function, you want it to be CLEAR what is going on. Not only that, but in this case we also have a clear parallel between the written code and what is being executed at the machine level -- you could write this in assembly in about 5 minutes if you have some practice at doing that.

OCAML could be fine to write financial transactions, sure. But it is not just about making it run fast and being smart enough to be sneaky without being sneaky. This code above is simple... it's meant to be simple, and not ever get any more complicated.

I personally think that SML and its ilk are very powerful and well-suited to a certain class of problems. I am interested in how they wrote a ray-tracer in it, but I think maintenance is one of the most important aspects of the software development cycle. Additionally, the first step, design, can also be more costly if you are working in a functional language because you have to be more careful. All the best,

Jeremy
Here's a quote from Paul Graham quoting some other guy:

Quote:I think it would seem more persuasive if you limited the scope of where Lisp is supposed to be better. It probably isn't better, for example, in:

- communications, interface, format conversion
- real-time control
- numerically intensive code
- operating system implementation
- quick little scripts to munge files

The above actually constitute a large fraction of all software written.

It is better for information processing: running very complex algorithms across complex data sets. It's only recently, in the WWW age, that people actually do a lot of this.

Before the rise of the Web, I think only a very small minority of software contained complex algorithms: sorting is about as complex as it got. Can you think of a complex algorithm in FreeBSD? The 50 million lines of code in a Nortel switch probably didn't even contain a sort. It was all about managing hardware and resources and handling failures gracefully. Cisco routers also have millions of lines of software, but not many complex algorithms.


That's the common wisdom; best tool for the job. But look at this guy. He's implemented several languages and a virtual machine using ocaml. It seems that for complex problems functional languages give people who know their stuff (ie leet haxors) alot of productivety.
------------------<a href="http://jsgc.sourceforge.net>jsgc
Quote:
I'd like to remind you that equivalent solution in O'Caml hasn't arrived yet. And it won't, until O'Caml supports GADTs.


Look, either put up or shut up. You've repeatedly been asked what the C++ version can do that the O'Caml one cannot. Now either answer the question honestly or admit that your example is so contrived as to be an irrelevance.
Quote:Original post by SamLowry
Since I love surprises:

**code snipped**


let fst pair () =  let (x, _) = pair ()  in  x ();;let snd pair () =  let (_, y) = pair ()  in  y ();;


should be

let fst pair () =  let (x, _) = pair ()  in  x;;let snd pair () =  let (_, y) = pair ()  in  y;;


I don't think pair is lazy since it always evaluates both of it's contents. Not sure if that's important though.
Quote:Original post by SamLowry
It's actually not a big surprise, I had already noticed that Haskell/ML/OCaml looked a lot like Lisp without the outer parentheses. But then again, wasn't ML based on the typed lambda calculus with some syntactic sugar on top? It's Lisp's statically typed twin in a way.


Statically checked, yes. Don't forget pattern redundancy/exhaustion checking etc.

Here's my OCaml equivalent:

let rec eval = function    `Lit i -> `Int i  | `Inc e -> (match eval e with `Int i -> `Int (i+1) | _ -> invalid_arg "int")  | `IsZ e -> `Int (if eval e = `Int 0 then 0 else -1)  | `If(p, t, f) -> eval(if eval p <> `Int 0 then t else f)  | `Pai(a, b) -> `Pai(eval a, eval b)  | `Fst e -> (match eval e with `Pai(x, _) -> x | _ -> invalid_arg "fst")  | `Snd e -> (match eval e with `Pai(_, x) -> x | _ -> invalid_arg "snd");;eval(`Pai(`IsZ(`Lit 0), `If(`IsZ(`Inc(`Lit 10)), `Lit 1337, `Lit 999)));;


What's wrong with this?

Cheers,
Jon.
Quote:Original post by phantom
I got bored and decided to make the C++ code more concise;


That's much better, thank you.

Quote:
list<int> &haar(list<int> &s, list<int> &d, list<int> &l) {	if (s.empty() && l.size() == 1) return d.push_front(l.front()),d;	if (l.empty()) return haar(l, d, s);	list<int>::iterator i = l.begin(), j = i++;	s.push_front((*j)+(*i)), d.push_front((*j)-(*i)), l.erase(j,++i);	return haar(s, d, l);}


ok, so I took advantage of a couple of tricks there (operator , for example), but I'm not a C++ expert by any means, as such a competent C++ coder should be able to understand that code as well and its about 2 lines longer than the O'Caml version (infact, if you were to move that return statement up a line and tack ',haar(s,d,l)' onto the end then its only one line longer due to the '}').

The test code is longer, but even that can be simply shortend;
int _tmain(int argc, _TCHAR* argv[]) {	int data [] = {1,2,3,4,-4,-3,-2,-1};	list<int> l(data,data+8);	list<int> l2=haar(list<int>(), list<int>(), l);



You've taken references to two temporaries here. Tut tut.

Quote:
	copy(l2.begin(), l2.end(), ostream_iterator<int>(cout, " "));	cout << endl;	return 0;}


no real point to this, as I said, I was bored and trying to put off doing an assignment [grin]

You're right, fst and snd were defined incorrectly.

Updated version:
let lit x () =  x;;let inc x () =  x () + 1;;let is_zero x () =  x () = 0;;let select cond x y () =  if cond ()  then x ()  else y ();;let pair x y () =  (x (), y ());;let fst pair () =  let (x, _) = pair ()  in  x;;let snd pair () =  let (_, y) = pair ()  in  y;;let expr =  (pair     (is_zero (lit 0))     (select	(is_zero (inc (lit 10)))	(lit 1337)	(lit 999)));;expr ();;


And I have to admit that pair is indeed not fully lazy, but at least it's coherent with the C++ version :)

# let p = pair (lit 0) (lit 1);;(* When evaluating p, I expect (0, 1) *)# p ();;(0, 1)


However
# let p = pair (lit 0) (never_ending_computation);;# fst p ();;(* no answer, while it should give 0 *)


I don't see how I could (elegantly) make pair properly lazy without changing the entire design. I should be able to send messages (OOP-like, as described in SICP), I'll try it out.

EDIT: lazy version
type pair_message = Both | Fst | Snd;;let pair fst snd ?(msg = Both) () =  match msg with    Both -> (fst (), snd ())  | Fst  -> (fst (), fst ())  | Snd  -> (snd (), snd ());;let fst (pair : ?msg:pair_message -> unit -> 'a * 'b) () =  let (x, _) = pair ~msg:Fst ()  in  x;;let snd (pair : ?msg:pair_message -> unit -> 'a * 'b) () =  let (_, y) = pair ~msg:Snd ()  in  y;;

Ugly. Also, could someone explain to me why I need to explicitly state the type of the argument "pair" in fst and snd? If I don't, O'Caml assumes msg is not optional and complains about pair being a "?msg:pair_message -> unit -> 'a * 'b" while it expects a "msg:pair_message -> unit -> 'a * 'b".
Quote:Original post by jdh30
You've taken references to two temporaries here. Tut tut.


hmmm, you raise a good point there... I wonder if I've wandered into 'undefined' areas of the C++ map... infact, now I consider it, I'm slight amazed it works as I'm returning a reference to a tempory from the function... *chuckles* thats what you call not paying attension.. but VC++8 seems to let it work just fine...

This topic is closed to new replies.

Advertisement