2010-11-16 161 views
3

想象一下這樣的區分聯合:如何使F#歧視聯盟脫離其他類型的聯盟情況?

type Direction = 
    | North 
    | South 
    | East 
    | West 

現在想象一下,我想這樣一種類型,只接受(北,南)或(東,西)的元組。也許這將描述只能從北到南或從東到西的列車路線。 (北,東)和(南,西)應該被禁止,也許是因爲列車不這樣運行。

這不起作用:

type TrainLines = 
    | North, South 
    | East, West 

即使不工作,也許你可以看到我想要做的事。

這工作,但不限制possibilites只(北美,南美)和(東,西):

type TrainLines = Direction * Direction 

任何指導意見將受到歡迎。

回答

10

這不正是你問什麼,但我認爲這很可能是

type TrainLines = 
    | NorthSouth 
    | EastWest 

會對你有好處。如果需要,您可以添加例如

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

你不能做你想要什麼,因爲NorthSouthEastWest不是類型的自己。所以你不能有像North * South; North, South是類型Direction * Direction的值,但不是唯一的值。就像你不能定義類型

type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 
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#的確實有一個很好的集在活動模式功能,它可以幫助運行時輸入轉換成一組已知的情況下,然後用靜態的堅定信心:

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)] 
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的缺點...