2009-08-26 18 views
6

¿Por qué no se permiten encuadernaciones en una unión discriminada? Supongo que tiene que ver con dejar que las consolidaciones se ejecuten en un constructor predeterminado.Discriminated Union & let binding?

En una nota secundaria, cualquier sugerencia sobre cómo podría reescribir AI_Choose sería apreciada. Quiero mantener la prioridad ponderada en una tupla con la IA. Mi idea es tener AI_Weighted_Priority heredar AI_Priority y anular Elegir. No quiero hacer frente a comprimir listas de diferentes longitudes (mala práctica de la OMI.)

open AI 

type Condition = 
    | Closest of float 
    | Min 
    | Max 
    | Average 
    member this.Select (aiListWeight : list<AI * float>) = 
     match this with 
     | Closest(x) -> 
      aiListWeight 
      |> List.minBy (fun (ai, priority) -> abs(x - priority)) 
     | Min -> aiListWeight |> List.minBy snd 
     | Max -> aiListWeight |> List.maxBy snd 
     | Average -> 
      let average = aiListWeight |> List.averageBy snd 
      aiListWeight 
      |> List.minBy (fun (ai, priority) -> abs(average - priority)) 

type AI_Choose = 
    | AI_Priority of list<AI> * Condition 
    | AI_Weighted_Priority of list<AI * float> * Condition 

    // I'm sad that I can't do this  
    let mutable chosen = Option<AI>.None 

    member this.Choose() = 
     match this with 
     | AI_Priority(aiList, condition) -> 
      aiList 
      |> List.map (fun ai -> ai, ai.Priority()) 
      |> condition.Select 
      |> fst 
     | AI_Weighted_Priority(aiList, condition) -> 
      aiList 
      |> List.map (fun (ai, weight) -> ai, weight * ai.Priority()) 
      |> condition.Select 
      |> fst 

    member this.Chosen 
     with get() = 
      if Option.isNone chosen then 
       chosen <- Some(this.Choose()) 
      chosen.Value 
     and set(x) = 
      if Option.isSome chosen then 
       chosen.Value.Stop() 
      chosen <- Some(x) 
      x.Start() 

    interface AI with 
     member this.Start() = 
      this.Chosen.Start() 
     member this.Stop() = 
      this.Chosen.Stop() 
     member this.Reset() = 
      this.Chosen <- this.Choose() 
     member this.Priority() = 
      this.Chosen.Priority() 
     member this.Update(gameTime) = 
      this.Chosen.Update(gameTime) 

Respuesta

2

tiene sentido permitir el enlace "let" dentro de las uniones discriminadas. Creo que la razón por la que no es posible es que las uniones discriminadas todavía se basan en el diseño OCaml, mientras que los objetos provienen del mundo .NET. F # intenta integrar estos dos tanto como sea posible, pero probablemente podría ir más allá.

De todos modos, me parece que está utilizando la unión discriminada solo para implementar algún comportamiento interno del tipo AI_Choose. En ese caso, podría declarar una unión discriminada por separado y usarla para implementar el tipo de objeto.

creo que podría escribir algo como esto:

type AiChooseOptions = 
    | AI_Priority of list<AI> * Condition 
    | AI_Weighted_Priority of list<AI * float> * Condition 

type AiChoose(aiOptions) = 
    let mutable chosen = Option<AI>.None 
    member this.Choose() = 
     match aiOptions with 
     | AI_Priority(aiList, condition) -> (...) 
     | AI_Weighted_Priority(aiList, condition) -> (...) 
    member this.Chosen (...) 
    interface AI with (...) 

La diferencia clave entre la jerarquía de clases y los sindicatos discriminados es cuando se trata de extensibilidad. Las clases hacen que sea más fácil agregar nuevos tipos, mientras que las uniones discriminadas hacen que sea más fácil agregar nuevas funciones que funcionen con el tipo (en su caso, AiChooseOptions), por lo que es probablemente lo primero que se debe tener en cuenta al diseñar la aplicación.

3

Para cualquier persona interesada que terminó derivando AI_Priority y AI_Weighted_Priority de una clase base abstracta.

[<AbstractClass>] 
type AI_Choose() = 
    let mutable chosen = Option<AI>.None 

    abstract member Choose : unit -> AI 

    member this.Chosen 
     with get() = 
      if Option.isNone chosen then 
       chosen <- Some(this.Choose()) 
      chosen.Value 
     and set(x) = 
      if Option.isSome chosen then 
       chosen.Value.Stop() 
      chosen <- Some(x) 
      x.Start() 

    interface AI with 
     member this.Start() = 
      this.Chosen.Start() 
     member this.Stop() = 
      this.Chosen.Stop() 
     member this.Reset() = 
      this.Chosen <- this.Choose() 
     member this.Priority() = 
      this.Chosen.Priority() 
     member this.Update(gameTime) = 
      this.Chosen.Update(gameTime) 

type AI_Priority(aiList : list<AI>, condition : Condition) = 
    inherit AI_Choose() 
    override this.Choose() = 
     aiList 
     |> List.map (fun ai -> ai, ai.Priority()) 
     |> condition.Select 
     |> fst 

type AI_Weighted_Priority(aiList : list<AI * float>, condition : Condition) = 
    inherit AI_Choose() 
    override this.Choose() = 
     aiList 
     |> List.map (fun (ai, weight) -> ai, weight * ai.Priority()) 
     |> condition.Select 
     |> fst 
3

Revisando este código Terminé tomando la sugerencia de Tomas, que resultó ser mucho más clara.

type AiChooseOptions = 
    | Priority of List<AI * Priority> 
    | WeightedPriority of List<AI * Priority * float> 
    member this.Choose(condition : Condition) = 
     match this with 
     | Priority(list) -> 
      list 
      |> List.map (fun (ai, priority) -> ai, priority.Priority()) 
      |> condition.Select 
     | WeightedPriority(list) -> 
      list 
      |> List.map (fun (ai, p, weight) -> ai, p.Priority() * weight) 
      |> condition.Select 

type AiChoose(condition, list : AiChooseOptions) = 
    let mutable chosen = Unchecked.defaultof<AI>, 0.0 

    interface AI with 
     member this.Update(gameTime) = 
      (fst chosen).Update(gameTime) 

    interface Priority with 
     member this.Priority() = 
      chosen <- list.Choose(condition) 
      (snd chosen)