

Subject: [ANN] rocaml: Ruby extensions in Objective Caml

From: Mauricio Fernandez <mfp@ m r

Date: Fri, 22 Jun 2007 20:35:43 +0900

rocaml allows you to write Ruby extensions in Objective Caml. I never seem to manage to release things when I should, so here's a pre-release announcement to let you know about this so you can play with it before the actual release, which could take longer than necessary. http://eigenclass.org/repos/rocaml/head/ Young as it is, rocaml is very usable and the generated extensions are reliable, since they enforce type safety and handle exceptions both in Ruby and OCaml (OCaml exceptions are passed to Ruby). Developing Ruby extensions with rocaml is easier and more convenient than writing a plain old C extension since rocaml performs Ruby<->OCaml conversions for a wide range of types, including abstract types and arrays, tuples, variants and records of values of any supported type (e.g. arrays of arrays of variants of tuples of ...). Making an extension with rocaml involves two steps: * implementing the desired functionality in Objective Caml, and registering the functions to be exported (using Callback.register : string -> 'a -> unit) * creating the extconf.rb file (just modify the sample extconf.rb distributed with rocaml) defining the interface of your Objective Caml code. ** At no point is there any need to write a single line of C code when ** ** using rocaml. ** The mandatory trivial example ============================= Let's create an extension with a 'fib' function. Here's the OCaml code: let rec fib n = if n < 2 then 1 else fib (n-1) + fib (n-2) let _ = Callback.register "Fib.fib" fib Here's the interface declaration in your extconf.rb: Interface.generate("fib") do def_module("Fib") do fun "fib", INT => INT end end That's it. Running extconf.rb will generate all the required wrappers and make will link them against your ml code, creating a normal Ruby extension that can be used simply with require 'fib' p Fib.fib 10 Set of strings using a RB tree ============================== Here's a simple set based on an RB tree, specialized for strings (see examples/tree for how to create several classes from a single polymorphic structure). The (unoptimized) RB tree takes only ~30LoCs, but lookup is 3X faster than with RBTree, which takes >3000 lines and over ~250 lines for the equivalent functionality, without counting the *manually written* wrappers for the underlying C data structure. This shows how rocaml handles complex types, including variant and recursive types. Given this interface definition: Interface.generate("tree") do string_tree_t = sym_variant("string_tree_t") do |t| constant :Empty non_constant :Node, TUPLE(t, type, t) end def_class("StringRBSet") do |c| t = c.abstract_type fun "empty", UNIT => t, :aliased_as => "new" fun "make", string_tree_t => t method "add", [t, STRING] => t method "mem", [t, STRING] => BOOL, :aliased_as => "include?" method "dump", t => string_tree_t method "iter", t => t, :aliased_as => "each", :yield => [STRING, UNIT] end end You can use the generated extension as follows (you can find the OCaml code below): require 'tree' set = StringRBSet.new set2 = s.add "foo" # the RB set is a functional, i.e. persistant # data structure # see how rocaml handles conversions for recursive variant types p s.add("foo").dump p s.add("foo").add("bar").dump The above will print [: Node, [:B, :Empty, "foo", :Empty]] [: Node, [:B, [:Node, [:R, :Empty, "bar", :Empty]], "foo", :Empty]] showing you the structure of the RB tree. That's it for now, enjoy. Further updates on eigenclass.org. PS: For the sake of completeness, here's the OCaml code. You can find the full example in examples/tree. exception Found module RBSet = struct type color = R | B type 'a t = Empty | Node of color * 'a t * 'a * 'a t let empty = Empty let rec mem x = function Empty -> false | Node(_, l, y, r) -> if y < x then mem x l else if y > x then mem x r else true let balance = function B, Node(R, Node(R, a, x, b), y, c), z, d | B, Node(R, a, x, Node(R, b, y, c)), z, d | B, a, x, Node(R, Node(R, b, y, c), z, d) | B, a, x, Node(R, b, y, Node(R, c, z, d)) -> Node(R, Node(B, a, x, b), y, Node(B, c, z, d)) | (c, a, x, b) -> Node (c, a, x, b) let add x t = let rec ins = function Empty -> Node(R, Empty, x, Empty) | Node(color, a, y, b) -> if x < y then balance (color, ins a, y, b) else if x > y then balance (color, a, y, ins b) else raise Found in try match ins t with Node (_, a, y, b) -> Node(B, a, y, b) | Empty -> assert false (* ins always returns Node _ *) with Found -> t let rec iter f = function Empty -> () | Node(_, l, x, r) -> iter f l; f x; iter f r end external intset_yield : int -> unit = "IntRBSet_iter_yield" external stringset_yield : int -> unit = "StringRBSet_iter_yield" let identity x = x open Callback let _ = let def_set t = let r name f = register (t ^ "RBSet" ^ "." ^ name) f in r "empty" (fun () -> RBSet.empty); r "add" (fun t x -> RBSet.add x t); r "mem" (fun t x -> RBSet.mem x t); r "dump" identity; r "make" identity; in List.iter def_set ["Int"; "String"]; register "IntRBSet.iter" (RBSet.iter intset_yield); register "StringRBSet.iter" (RBSet.iter stringset_yield); -- Mauricio Fernandez - http://eigenclass.org - singular Ruby