“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. |
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 |
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 :
Post a Comment