我正在尝试为子弹地狱游戏编写一个小小的脚本引擎,我想在F#中进行。我写了一些C#代码来概念化它,但是我把它移植到F#时遇到了麻烦。 C#代码发布在下面,我想帮助将它移植到F#。我觉得匹配的F#代码会明显变小。我愿意接受任何创造性的解决方案:)
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));
}
}
答案 0 :(得分:9)
这几乎是对同一解决方案策略的直接翻译。
那就是说,我认为可能有一个更好/更简单的表示选择,我仍然在考虑它。
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 r n = fun x ->
if n = 0 then r x else
match r x with
| Result(data,None) -> Result(data, Some(Repeat r (n-1)))
| Result(data,Some(next)) -> Result(data, Some(Pair(next, Repeat r (n-1))))
修改
这是另一种更精致的方式......我仍然试图看看是否有一种很好的方式在“列表”中工作,因为结果似乎与cons单元同构......
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 r x else
match r x with
| data,None -> data, Some(Repeat R (n-1))
| data,Some(next) -> data, Some(Pair(next, Repeat R (n-1))))
修改
还有一个版本,它使用了列表,但现在我对这里有什么奇怪的感觉......
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 r x else
match r x with
| data,xs -> data, xs @ List.init (n-1) (fun _ -> R)) // inefficient
它几乎就像一个'动作队列',一个int->int
函数列表。但是每个人都可以产生一些“后缀动作”,这些动作在他之后立即运行(但是在剩余的工作在未来的队列之前),并且尝试用纯粹的功能数据结构维护排序可能是低效的(没有正确的树/手头的队列库)。知道如何使用/消费这将是有趣的,因为可能会有一个小的变化可能允许完全不同的策略。
答案 1 :(得分:5)
忘记C#,回到设计文档(或其他)并重新实现。我的意思是,从字面上看,忘了C#。你在F#中可以做的最糟糕的事情就是编写C#。当然,这是一个通用规则的实例:您在语言X中可以做的最糟糕的事情是用Y语言编写程序。根据需要绑定X和Y.
答案 2 :(得分:1)
我假设IRunner和Result是预定义的,因为如果不是,你应该重新设计系统,以便更加专注于FP概念,而不需要所有这些继承。
无论如何,这是给定示例的二进制(我相信)对位
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))
答案 3 :(得分:1)
您可以考虑使用F#中的内置序列支持(以及非常好的sequence expressions)。这个例子有点人为,但你可以把程序想象成一系列序列,Brian正在暗示的“行动队列”。通过添加外部序列,它允许内部序列完全控制它的生命周期。
// 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