“Hello, OTP!” from F# revisited
That previous F# code was just too ugly to leave as it was; so after a little bit of work, I've factored out a general purpose wrapper to convert the Otp.Erlang
types into values of a discriminated union on the F# side of the fence. Like this we can directly pattern match, without the descent into massive nesting that we had before, and keep the Otp.Erlang
types encapsulated into wrappers for method calls on the Otp
types.
With this addition the main program above can be revised to become
namespace ErsharpClient | |
open System | |
open Otp | |
open Tinesware.Unfurling | |
module Main = | |
let ConnectTo serverNode cookie = | |
let clientNode = new OtpSelf("clientnode", cookie) | |
let serverNode = new OtpPeer(serverNode) | |
clientNode.connect(serverNode) | |
let ReceiveRPC (connection : OtpConnection) = | |
Wrapper.ToTerm <| connection.receiveRPC() | |
let MakeRPC (connection : OtpConnection) (moduleName:string) (functionName:string) (arguments: Term seq) = | |
connection.sendRPC(moduleName, functionName, arguments |> Seq.map Wrapper.ToOtpObject |> Seq.toArray) | |
ReceiveRPC connection | |
let StartGenServer (connection : OtpConnection) = | |
match MakeRPC connection "mathserver" "start_link" [] with | |
| Term.Tuple [ Term.Atom "ok" ; pid ] -> pid | |
| Term.Tuple [ Term.Atom "error"; Term.Tuple [ Term.Atom "already_started" ; pid2 ]] -> pid2 | |
| start -> raise (new System.InvalidOperationException(start.ToString())) | |
[<EntryPoint>] | |
let Main arguments = | |
let connection = ConnectTo "servernode@YourNodeNameHere" "cookie" | |
let pid = StartGenServer connection | |
let args = [6I; 9I] | |
|> Seq.map Term.Integer | |
match MakeRPC connection "mathserver" "multiply" args with | |
| Term.Tuple [ Term.Atom "ok" ; Term.Integer value ] -> | |
Console.WriteLine("Return Value:" + value.ToString()) | |
| result -> Console.WriteLine("Failure:" + result.ToString()) | |
0 |
which looks a lot more like the language we're trying to write in. All the magic happens in this file
namespace Tinesware.Unfurling | |
open Otp | |
type public Pid = { Node : string; | |
Id : int; | |
Serial : int; | |
Creation : int } | |
type public Port = { Node : string; | |
Id : int; | |
Creation : int } | |
type public Ref = { Node : string; | |
Creation : int; | |
Ids : int list } | |
type public Term = | |
| Double of float | |
| Integer of bigint | |
| List of Term list | |
| Tuple of Term list | |
| Atom of string | |
| Binary of byte[] | |
| Pid of Pid | |
| Port of Port | |
| Ref of Ref | |
module Wrapper = | |
let rec ToTerm (term:Erlang.Object) = | |
match term with | |
| :? Erlang.Atom as atom -> Term.Atom <| atom.atomValue() | |
| :? Erlang.Binary as binary -> Term.Binary <| binary.binaryValue() | |
| :? Erlang.Double as double -> Term.Double <| double.doubleValue() | |
| :? Erlang.List as list -> | |
let contents = list.elements() | |
|> Seq.map ToTerm | |
|> Seq.toList | |
Term.List contents | |
| :? Erlang.Long as long -> Term.Integer <| bigint (long.longValue()) | |
| :? Erlang.Pid as pid -> Term.Pid {Node = pid.node(); Id = pid.id(); Serial = pid.serial(); Creation = pid.creation()} | |
| :? Erlang.Port as port -> Term.Port {Node = port.node(); Id = port.id(); Creation = port.creation()} | |
| :? Erlang.Ref as ref -> Term.Ref {Node = ref.node(); Creation = ref.creation(); Ids = ref.ids() |> Array.toList } | |
| :? Erlang.Tuple as tuple -> | |
let contents = tuple.elements() | |
|> Seq.map ToTerm | |
|> Seq.toList | |
Term.Tuple contents | |
| _ -> raise <| new System.InvalidOperationException(term.ToString()) | |
let rec ToOtpObject (term : Term) = | |
match term with | |
| Term.Atom atom -> new Erlang.Atom(atom) :> Erlang.Object | |
| Term.Binary binary -> new Erlang.Binary(binary) :> Erlang.Object | |
| Term.Double double -> new Erlang.Double(double) :> Erlang.Object | |
| Term.Integer long -> new Erlang.Long(int64 long) :> Erlang.Object | |
| Term.List list -> new Erlang.List( list | |
|> Seq.map ToOtpObject | |
|> Seq.toArray ) :> Erlang.Object | |
| Term.Pid pid -> new Erlang.Pid(pid.Node, pid.Id, pid.Serial, pid.Creation) :> Erlang.Object | |
| Term.Port port -> new Erlang.Port(port.Node, port.Id, port.Creation) :> Erlang.Object | |
| Term.Ref ref -> new Erlang.Ref(ref.Node, ref.Ids |> List.toArray, ref.Creation) :> Erlang.Object | |
| Term.Tuple tuple -> new Erlang.Tuple ( tuple | |
|> Seq.map ToOtpObject | |
|> Seq.toArray ) :> Erlang.Object |
Of course, as OTP.Net is some revs behind the current Erlang distro's JInterface, this type mapping is incomplete (no BitStr
or Fun
types) compared with what we could be using. The main advantage is that it works right now.
No comments :
Post a Comment