2009-12-21 8 views
4

총알 지옥 게임을위한 작은 스크립트 엔진을 작성하려고하는데 F #에서 그 작업을하고 싶습니다. 개념적으로 C# 코드를 작성했지만 F #으로 이식하는 데 문제가 있습니다. C# 코드는 아래에 게시되어 있으며 F #으로 이식하는 데 도움이 필요합니다. 일치하는 F # 코드가 상당히 작아지는 느낌이 들었습니다. 나는 어떤 종류의 창의적인 솔루션에 대해서도 개방적입니다.F # : C#에서 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

나는 F #이 파이썬과 C#보다 더 비슷하다는 것을 알게되었다. 나는 먼저 파이썬으로 번역 할 것이고, 그 언어로 (읽기 쉽기 때문에) 단단히하고 계속 진행할 것이다. –

+8

필자는 F #에 익숙하지 않지만 기능 코드를 개념화하기 위해 OO 코드를 작성하는 것은 나에게 좋은 생각처럼 들리지 않습니다. – pmr

+0

음, 적어도 FP를 염두에두고 작성했습니다. OO를 사용하는 유일한 방법은 물건을 멋지게 컴파일하는 것입니다. 누구나 코드를 스크롤 영역에 넣는 방법을 알고 있으므로 그렇게 크지는 않습니까? – rysama

답변

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)))) 

편집이

여기에 조금 더 세련된의 또 다른 방법 ... 여전히 "목록"에서 작동 할 수있는 좋은 방법이 있다면 결과는 단점 세포에 동형 것 때문에, 참조하려합니다. ..

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 기능 목록입니다. 그러나 각자는 그 직후에 (그러나 대기열에 남아있는 작업 전에) 실행되는 '접미어 동작'을 생성 할 수 있으며 순전히 기능적 데이터 구조로 정렬을 유지하려고하면 잠재적으로 비효율적입니다 (올바른 트리/대기열 라이브러리). 아마도 이것이 완전히 다른 전략을 허용 할 수도 있기 때문에 이것이 어떻게 사용되거나 소비되는지를 아는 것은 흥미로울 것입니다.

+0

이런 젠장, C#보다 훨씬 작아. "시퀀스"를 포팅하는 것을 잊어 버린 것 같습니까? – rysama

+0

러너/결과의 유형에 구속되지 않는 경우 시퀀스의 요소와 일치하는 h :: t 구문에 대한 목록을 사용해보십시오. – Guvante

+0

(그래, 나는 처음 4 개가 물건의 '본질'을 떠올리기에 충분했기 때문에 순서를 고심하지 않았다.) – Brian

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)) 
+1

원래의 포스터와 OO에 대한 개인적인 관계를 과대 평가 한 것 같습니다 프로그램 작성. F #은 확실히 볼 수 있듯이 내장되어 있지 않습니다. – Guvante

+0

IRunner 유형은 컬렉션에 저장하고 "실행"메소드를 호출 할 수 있도록 사용되었습니다. "Result"타입은 튜플을 수행하는 나의 솜씨 좋은 방식이었습니다. – rysama

+0

@Guvante : 공정하기 위해 F # 코드는 게시 한 원래 C# 코드보다 작습니다. – rysama

1

당신은 (아주 좋은 sequence expressions과 함께) F 번호에 내장 된 시퀀스 지원 사용을 고려할 수에 바이너리 (내가 믿는) 대위법이다. 이 예제는 약간 고안되었지만 프로그램을 시퀀스 시퀀스로 생각할 수 있습니다. 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 
5

C#을 잊어 버려도 디자인 문서 (또는 기타)로 돌아가서 다시 구현하십시오. 나는 말 그대로, C#을 잊어 버린다. F #에서 할 수있는 최악의 경우는 C#을 작성하는 것입니다. 이것은 물론 일반 규칙의 인스턴스입니다. 언어 X에서 할 수있는 가장 최악은 언어 Y으로 프로그램을 작성하는 것입니다. X와 Y를 원하는대로 바인딩하십시오.

+1

충분히 upvote 할 수 없습니다. –

+0

내가 말하는 것에 동의하지 않지만 C# 구현에서 객체를 사용하더라도 기능적 스타일로 프로그래밍하는 것은 여전히 ​​매우 관용적입니다. 돌연변이가 없으며 ctor는 단순히 인수를 전달하거나 클로저를 캡처하는 방법 일 뿐이며 "실행"메서드는 기본적으로 고차 함수입니다. – rysama