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);