F #: need help converting C # to F #

I am trying to write a small little script engine for playing with bullet hell, and I would like to do it in F #. I wrote C # code to conceptualize it, but it's hard for me to port it to F #. The C # code is posted below, and I would like some help porting it to F #. I have a feeling that the corresponding F # code will be significantly smaller. I am open to any creative decisions :)

interface IRunner { Result Run(int data); } struct Result { public Result(int data, IRunner next) { Data = data; Next = next; } public int Data; public IRunner Next; } class AddOne : IRunner { public Result Run(int data) { return new Result(data + 1, null); } } class Idle : IRunner { public Result Run(int data) { return new Result(data, null); } } class Pair : IRunner { IRunner _one; IRunner _two; public Pair(IRunner one, IRunner two) { _one = one; _two = two; } public Result Run(int data) { var res = _one.Run(data); if (res.Next != null) return new Result(res.Data, new Pair(res.Next, _two)); return new Result(res.Data, _two); } } class Repeat : IRunner { int _counter; IRunner _toRun; public Repeat(IRunner toRun, int counter) { _toRun = toRun; _counter = counter; } public Result Run(int data) { var res = _toRun.Run(data); if (_counter > 1) { if (res.Next != null) return new Result(res.Data, new Pair(res.Next, new Repeat(_toRun, _counter - 1))); return new Result(res.Data, new Repeat(_toRun, _counter - 1)); } return res; } } class Sequence : IRunner { IEnumerator<IRunner> _runner; public Sequence(IEnumerator<IRunner> runner) { _runner = runner; } public Result Run(int data) { var res = _runner.Current.Run(data); bool next = _runner.MoveNext(); if (res.Next != null) { return new Result(res.Data, new Pair(res.Next, new Sequence(_runner))); } return new Result(res.Data, new Sequence(_runner)); } } 
+4
source share
4 answers

Here is something that is almost a direct translation of the same solution strategy.

However, I think there might be a better / simpler choice, I am still pondering it.

 type Runner = int -> Result and Result = Result of int * option<Runner> let AddOne = fun x -> Result(x+1, None) let Idle = fun x -> Result(x, None) let rec Pair(r1,r2) = fun x -> match r1 x with | Result(data,None) -> Result(data, Some(r2)) | Result(data,Some(next)) -> Result(data,Some(Pair(next,r2))) let rec Repeat rn = fun x -> if n = 0 then rx else match rx with | Result(data,None) -> Result(data, Some(Repeat r (n-1))) | Result(data,Some(next)) -> Result(data, Some(Pair(next, Repeat r (n-1)))) 

EDIT

Here is another way that is a bit more sophisticated ... I'm still trying to see if there is a good way to work in the "list" as the results seem to be isomorphic to cons cells ...

 type Runner = Runner of (int -> int * option<Runner>) let AddOne = Runner(fun x -> x+1, None) let Idle = Runner(fun x -> x, None) let rec Pair(Runner(r1),R2) = Runner(fun x -> match r1 x with | data,None -> data, Some(R2) | data,Some(next) -> data, Some(Pair(next,R2))) let rec Repeat (Runner(r) as R) n = Runner(fun x -> if n = 0 then rx else match rx with | data,None -> data, Some(Repeat R (n-1)) | data,Some(next) -> data, Some(Pair(next, Repeat R (n-1)))) 

EDIT

Another version, it uses lists, but now I feel it is strange here ...

 type Runner = Runner of (int -> int * list<Runner>) let AddOne = Runner(fun x -> x+1, []) let Idle = Runner(fun x -> x, []) let rec Pair(Runner(r1),R2) = Runner(fun x -> match r1 x with | data,xs -> data, xs @ [R2]) // inefficient let rec Repeat (Runner(r) as R) n = Runner(fun x -> if n = 0 then rx else match rx with | data,xs -> data, xs @ List.init (n-1) (fun _ -> R)) // inefficient 

It is almost the same as an “action queue”, a list of functions int->int . But each guy can create some “suffix actions” that start immediately after him (but before remaining work in the potential queue), and trying to maintain ordering with a purely functional data structure is potentially inefficient (without the right tree / queue library at hand). It would be interesting to know how this will be used / consumed, as perhaps a small change can lead to a completely different strategy.

+9
source

Forget C #, go back to the project documents (or something else) and repeat the implementation. I mean, literally, forget C #. The worst thing you can do in F # is to write C #. This, of course, is an example of a general rule: the worst thing you can do in X is to write a program in Y. Link X and Y as you wish.

+5
source

I assume that IRunner and Result are predefined, as if you hadn’t redesigned the system to focus more on FP concepts without all this inheritance.

In any case, here is the binary (I suppose) counterpoint to this example.

 type AddOne = interface IRunner with member this.Run(data) = new Result(data+1, null) type Idle = interface IRunner with member this.Run(data) = new Result(data, null) type Pair(one:IRunner, two:IRunner) = interface IRunner with member this.Run(data) = let res = one.Run(data) if res.Next <> null then new Result(res.Data, new Pair(res.Next, two)) else new Result(res.Data, two) type Repeat(toRun:IRunner, counter:int) = interface IRunner with member this.Run(data) = let res = toRun.Run(data) if counter > 1 then if res.Next <> null then new Result(res.Data, new Pair(res.Next, new Repeat(toRun, counter - 1))) else new Result(res.Data, new Repeat(toRun, counter - 1)) else res type Sequence(runner:System.Collections.Generic.IEnumerator<IRunner>) = interface IRunner with member this.Run(data) = let res = runner.Current.Run(data) let next = runner.MoveNext() if res.Next <> null then new Result(res.Data, new Pair(res.Next, new Sequence(runner))) else new Result(res.Data, new Sequence(runner)) 
+1
source

You can use the built-in sequence support in F # (along with very nice expressions ) . This example is a little far-fetched, but you might think of the program as a sequence of sequences, the "queue of actions" that Brian alluded to. By adding an external sequence, it allows the internal sequence to fully control the lifetime.

 // an infinite sequence, repeat the last element of the underlying sequence // now we can avoid using option types or null/boolean checks in our fold let lastInfinite (a:seq<'a>) = seq { let last = ref Unchecked.defaultof<'a> for i in a do last := i yield i let last' = !last while true do yield last' } let addOne = seq [ fun x -> x + 1 ] let idle = seq [ id<int> ] let repeat run counter = Seq.truncate counter (lastInfinite run) let sequence = Seq.concat let pair one two = lastInfinite (Seq.append one two) let program = seq [ repeat idle 5; repeat addOne 100; idle; ] // use Seq.scan if you need lazy access to the intermediate results // or perhaps Seq.map... let result = Seq.concat program |> Seq.fold (fun state value -> value state) 0 printfn "result: %A" result 
+1
source

All Articles