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 ?
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 . . .
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.Action
s 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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With