Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OO alternative to polymorphism in F# when calling F# from C#

Tags:

c#

oop

f#

I want to create a library that will create objects using a user-defined function and modify them using another user-defined function.

I have an OCaml background and see a fairly straightforward way to implement it:

//user side
type userType = { mutable time : float }

let userInitialization () = { time = 0. }    
let userModification t = t.time <- t.time+1.

//my side
let algo initialization modification n =
    let a = Array.init n (fun _ -> initialization ())
    modification a.[0]
    a

The problem is that I want that library to be easy to call from C#: having functions as arguments is probably a bad idea.

Interface and abstract class (that would be inherited by userType) seems to be the usual OO solution, but I cannot initialize an object if I don't know the (not yet defined) userType making the initialization step, inside my function, impossible.

The only workaround that I can think of is to ask the user for an instance of his userType as an argument that would be used to call initialization, but that seems very inelegant.

Is there a way to solve this problem ?

like image 608
Nestor Demeure Avatar asked Jun 04 '16 09:06

Nestor Demeure


2 Answers

You could define an interface like this:

type IInitAndModify<'T> =
    abstract member CreateNew : unit -> 'T
    abstract member Modify : item : 'T -> unit

and perhaps also an object the user can use to pass it to your implementation:

type Algo<'T> () =
    member this.Execute (initAndModify : IInitAndModify<'T>, n) =
        algo initAndModify.CreateNew initAndModify.Modify n

From C#, it looks like this:

public interface IInitAndModify<T>
{
    T CreateNew();
    void Modify(T item);
}

public class Algo<T>
{
    public Algo();

    public T[] Execute(IInitAndModify<T> initAndModify, int n);
}

A client developer could use it like this:

public class UserType
{
    public float Time { get; set; }
}

public class UserInitAndModify : IInitAndModify<UserType>
{
    public UserType CreateNew()
    {
        return new UserType { Time = 0 };
    }

    public void Modify(UserType item)
    {
        item.Time++;
    }
}

and write a program like this:

static void Main(string[] args)
{
    var a = new Algo<UserType>();
    var values = a.Execute(new UserInitAndModify(), 10);
    foreach (var v in values)
        Console.WriteLine(v.Time);
}

When running the above Main method, the output is this:

1
0
0
0
0
0
0
0
0
0
Press any key to continue . . .
like image 88
Mark Seemann Avatar answered Nov 11 '22 11:11

Mark Seemann


I would be inclined to reject the idea that you shouldn't expose functions as arguments in a library exposed to C#, it's becoming pretty common to do so, you need only look at LINQ, TPL, etc. I don't think too many C# developers would be scared off by that.

I would, however, suggest that you avoid exposing functions with curried arguments to C# as these are not at all convenient to work with.

You can quite easily wrap your algorithm in a function that accepts arguments in tupled form and exposes System.Func and System.Actions to C#.

let csAlgo (initialisationFunc : System.Func<'a>, 
            modificationFunc : System.Action<'a>, 
            n : int) =
    algo (initialisationFunc.Invoke) (modificationFunc.Invoke) n

in C# you could then do this:

var res = Module.csAlgo(() => new UserType(0), t => t.Time = t.Time + 1, 16);

As a small aside, you can also use the CompiledName attribute to you can get your casing conventions right in each language. Tag your function

[<CompiledName("ExampleFunction")>]
let exampleFunction () = 1

Then in C#, it looks like this:

var num = Module.ExampleFunction();
like image 26
TheInnerLight Avatar answered Nov 11 '22 09:11

TheInnerLight