如何使F#区别联盟脱离另一种类型的联合案例?

时间:2010-11-16 06:53:52

标签: f#

想象一下这个受歧视的联盟:

type Direction = 
    | North
    | South
    | East
    | West

现在假设我想要一个只接受(北,南)或(东,西)元组的类型。也许这将描述仅从北向南或从东向西的火车路线。 (北,东)和(南,西)应该是禁止的,也许是因为火车不能像那样运行。

这不起作用:

type TrainLines = 
    | North, South
    | East, West

即使这不起作用,也许你可以看到我正在尝试做的事情。

这有效,但并不仅限于(北,南)和(东,西)的可能性:

type TrainLines = Direction * Direction

欢迎任何指导。

4 个答案:

答案 0 :(得分:10)

这不是你要求的,但我认为这可能是

type TrainLines =
    | NorthSouth
    | EastWest
你会好的吗?如果需要,你可以添加例如。

    with member this.Directions =
           match this with
           | NorthSouth -> [North; South]
           | EastWest -> [East; West]

答案 1 :(得分:2)

您不能完全按照自己的意愿行事,因为NorthSouthEastWest不是他们自己的类型。所以你不能拥有North * South之类的东西; North, SouthDirection * Direction类型的值,但不是唯一的值。就像你无法定义类型

一样
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

答案 2 :(得分:2)

  

现在想象我想要一个类型   只接受(北,南)的元组   或(东,西)。

有趣的功能请求:听起来像你想要的“静态范围限制”,例如

//fictional syntax for static range constraints
type TrainLine = (a,b) where (a=North and b=South) or (a=East and b=West)

let northSouth = TrainLine(North,South) // compiles
let northEast = TrainLine(North,East) // does not compile

这种特征似乎在只有文字的语言中是合理的,但是当我们考虑仅在运行时已知的值时,我们遇到麻烦:

let parseDirection userInputString = 
    match userInputString with
    | "North" -> North
    | "South" -> South
    | "East" -> East
    | "West" -> West
    | _ -> failwith "invalid direction"

let directionA = parseDirection(System.Console.ReadLine())
let directionB = parseDirection(System.Console.ReadLine())

//compiler can't enforce constraint because direction values unknown until runtime
let trainLine = TrainLine(directionA,directionB) 

然而,F#在Active Patterns中确实有一套很好的功能,可以帮助将运行时输入转换为一组已知的情况,然后继续静态置信:

let (|NorthSouth|EastWest|Invalid|) (a,b) = 
    match a,b with
    | North,South -> NorthSouth
    | East,West -> EastWest
    | _ -> Invalid

let trainLines = [(North,South); (South,North); (East,West); (North,East);(North,North); (South,East)]

let isValidTrainLine trainLine = 
    match trainLine with
    | NorthSouth -> true
    | EastWest -> true
    | Invalid -> false

let validTrainLines = trainLines |> List.filter isValidTrainLine
//val it : (Direction * Direction) list = [(North, South); (East, West)]

答案 3 :(得分:1)

你真的想要来自OCaml的多态变体:

[ `North | `South | `East | `West ]
[ `North | `South ] * [ `East | `West ]

但是F#目前无法表达这一点。我实际上发现我在工作中需要这么多......

您可以引入不必要的联合类型层:

type ns = North | South
type ew = East | West
type nsew = NorthSouth of ns | EastWest of ew

然后使用ns * ew

另一种有时可以正常工作的解决方案是使用接口在两种不同的联合类型之间提供一致性:

type IDir = abstract AsInt : int
type ns =
  | North
  | South
  interface IDir with
    method d.AsInt =
      match d with North -> 0 | South -> 1
type ew =
  | East
  | West
  interface IDir with
    method d.AsInt =
      match d with East -> 2 | West -> 3

可悲的是,这强加了OOP的所有缺点......