5 votes

F#: Besoin d'aide pour convertir C# en F#

Je suis en train d'essayer d'écrire un petit moteur de script pour un jeu de type bullet hell et j'aimerais le faire en F#. J'ai écrit du code C# pour le conceptualiser, mais j'ai du mal à le porter en F#. Le code C# est affiché ci-dessous, et j'aimerais de l'aide pour le porter en F#. J'ai l'impression que le code F# correspondant sera significativement plus petit. Je suis ouvert à tout type de solutions créatives :)

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 _runner;

    public Sequence(IEnumerator 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));
    }
}

9voto

Brian Points 82719

Voici quelque chose qui est presque une traduction directe de la même stratégie de solution.

Cela dit, je pense qu'il peut y avoir un meilleur/plus simple choix de représentation, je suis encore en train d'y réfléchir.

type Runner = int -> Result
and Result = Result of int * option

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

EDIT

Voici une autre manière un peu plus raffinée... j'essaie toujours de voir s'il y a un bon moyen d'intégrer une "liste", car les résultats semblent isomorphes aux cellules cons...

type Runner = Runner of (int -> int * option)

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

EDIT

Encore une autre version, elle utilise des listes, mais maintenant j'ai l'impression que quelque chose d'étrange se passe ici...

type Runner = Runner of (int -> int * list)

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]) // inefficace

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

C'est presque comme une 'file d'actions', une liste de fonctions int->int. Mais chaque individu peut produire quelques 'actions suffixes' qui s'exécutent immédiatement après lui (mais avant le reste du travail dans la file d'attente potentielle), et essayer de maintenir l'ordre avec une structure de données purement fonctionnelle est potentiellement inefficace (sans la bonne bibliothèque d'arbres/file à disposition). Il serait intéressant de savoir comment cela sera utilisé/consommé, car peut-être un petit changement là-bas permettrait une stratégie complètement différente.

5voto

Oubliez le C#, retournez aux documents de conception (ou autres) et réimplémentez. Je veux dire, littéralement, oubliez le C#. Le pire que vous pouvez faire en F# est d'écrire du C#. Ceci est bien sûr une instance d'une règle générique : Le pire que vous pouvez faire dans le langage X est d'écrire un programme dans le langage Y. Liez X et Y comme vous le souhaitez.

1voto

Guvante Points 10338

Je suppose que IRunner et Result sont prédéfinis, car sinon vous devriez revoir le système pour le focaliser davantage sur les concepts de FP sans tout cet héritage.

Quoi qu'il en soit, voici un contre-exemple binaire à l'exemple donné

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

1voto

Nathan Howell Points 3488

Vous pouvez envisager d'utiliser le support de séquence intégré en F# (avec les très belles expressions de séquence). Cet exemple est un peu artificiel mais vous pourriez penser au programme comme une séquence de séquences, la "file d'actions" à laquelle Brian faisait allusion. En ajoutant la séquence externe, cela permet à la séquence interne de contrôler pleinement sa durée de vie.

// une séquence infinie, répétez le dernier élément de la séquence sous-jacente
// maintenant nous pouvons éviter d'utiliser des types d'option ou des vérifications nulles/booléennes dans notre 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 ]
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; ]

// utilisez Seq.scan si vous avez besoin d'un accès paresseux aux résultats intermédiaires
// ou peut-être Seq.map...
let result = Seq.concat program
             |> Seq.fold (fun state value -> value state) 0

printfn "resultat: %A" result

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X