Tuesday, December 28, 2010

“Hello, OTP!” from F#

Nothing earth-shattering here, just recording the results of a little bit of playing around with OTP.Net and F#. This is all building on the original work done elsewhere Integrating .NET and Erlang using OTP.NET and Integrating F# and Erlang Using OTP.NET, as a little bit of a five-finger exercise using the OTP framework as now explained in Erlang and OTP in Action.

So, simple gen_server to export the same sort of API as the mathserver example in the former:

%% ---------------------------------------------------------------------
%% File: mathserver.erl
%%
%% This is a simple implementation of a gen_server callback module.
-module(mathserver).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([
start_link/0,
stop/0,
multiply/2
]).
-define(SERVER, ?MODULE).
-record(state, { }).
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
%% @doc Starts the server.
%%
%% @spec start_link() -> {ok, Pid}
%% where
%% Pid = pid()
%% @end
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% @doc Stops the server.
%% @spec stop() -> ok
%% @end
%%--------------------------------------------------------------------
stop() ->
gen_server:cast(?SERVER, stop).
%%--------------------------------------------------------------------
%% @doc multiplies two integers.
%% @spec multiply(X::integer(), Y::integer()) -> {ok, Product}
%% where
%% Product = integer()
%% @end
%%--------------------------------------------------------------------
multiply(X, Y) ->
gen_server:call(?SERVER, {multiply, {X, Y}}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call({multiply, {X, Y}}, _From, State) ->
Reply = {ok, multiply_impl(X,Y)},
{reply, Reply, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% API internals
%%%===================================================================
multiply_impl(First, Second) ->
First * Second.
view raw gistfile1.erl hosted with ❤ by GitHub

Compile this using erlc and start werl -sname servernode -setcookie cookie. Meanwhile the matching F# program to build and run is

namespace ErsharpClient
open System
open Otp
module Main =
let (|Atom|_|) text (term : Erlang.Object) =
match term with
| :? Erlang.Tuple as tuple ->
let terms = tuple.elements()
if terms.Length <> 2 then
None
else match terms.[0] with
| :? Erlang.Atom as atom ->
if atom.atomValue() = text then Some(terms.[1])
else None
| _ -> None
| _ -> None
let ConnectTo serverNode cookie =
let clientNode = new OtpSelf("clientnode", cookie)
let serverNode = new OtpPeer(serverNode)
clientNode.connect(serverNode)
let StartGenServer (connection : OtpConnection) =
connection.sendRPC("mathserver", "start_link", Array.empty<Otp.Erlang.Object>)
let start = connection.receiveRPC()
match start with
| Atom "ok" pid -> pid
| Atom "error" value ->
match value with
| Atom "already_started" pid2 -> pid2
| _ -> raise (new System.InvalidOperationException(start.ToString()))
| _ -> raise (new System.InvalidOperationException(start.ToString()))
[<EntryPoint>]
let Main arguments =
let connection = ConnectTo "servernode@YourNodeNameHere" "cookie"
let pid = StartGenServer connection
let args = [| new Otp.Erlang.Long(6L); new Otp.Erlang.Long(9L)|] : Otp.Erlang.Object array
connection.sendRPC("mathserver", "multiply", args);
let result = connection.receiveRPC()
match result with
| Atom "ok" value -> Console.WriteLine("Return Value:" + value.ToString())
| _ -> Console.WriteLine("Failure:" + result.ToString())
0
view raw gistfile1.fs hosted with ❤ by GitHub

Matching the dynamic types possible with Erlang, and the F# way with type hierarchies, makes this code somewhat ugly (as in multiply nested decision points and the start of the arrow anti-pattern) when trying to unpick the return values, especially trying to decode the possible return values when requesting that the far end server start up.

Some of the repeated noise has been factored out into the Atom active pattern for dealing with {Reason, Payload}, but the underlying data model in OTP.Net is simply not very F# friendly.

No comments :