在 F# 中实现状态机很容易。它通常遵循三步过程,第三步是可选的:
- 为每个州定义一个带有案例的歧视联盟
- 为每种情况定义一个转换函数
- 可选:实现所有其余代码
第一步
在这种情况下,我觉得有两种状态:
这表明Deal 有区别的联合:
type Deal = Hand of Card * Card | Hit of Card
另外,定义 Game 是什么:
type Game = Game of Deal list
注意使用单例区分联合; there's a reason for that.
第 2 步
现在定义一个从每个状态转换到Game 的函数。
事实证明,您无法从任何游戏状态转换到Hand 情况,因为Hand 是开始新游戏的原因。另一方面(双关语)你需要提供进入手牌的牌:
let init c1 c2 = Game [Hand (c1, c2)]
另一种情况是游戏正在进行时,你应该只允许Hit,而不是Hand,所以定义这个过渡:
let hit (Game deals) card = Game (Hit card :: deals)
如您所见,hit 函数要求您传入现有的Game。
第三步
是什么阻止客户端创建无效的Game 值,例如[Hand; Hit; Hand; Hit; Hit]?
你可以用signature file封装上面的状态机:
BlackJack.fsi:
type Deal
type Game
val init : Card -> Card -> Game
val hit : Game -> Card -> Game
val card : Deal -> Card list
val cards : Game -> Card list
在这里,类型 Deal 和 Game 被声明,但它们的“构造函数”没有。这意味着您不能直接创建这些类型的值。例如,这不会编译:
let g = BlackJack.Game []
给出的错误是:
错误 FS0039:未定义值、构造函数、命名空间或类型“游戏”
创建Game 值的唯一方法是调用为您创建它的函数:
let g =
BlackJack.init
{ Face = Ace; Suit = Spades }
{ Face = King; Suit = Diamonds }
这也使您能够继续游戏:
let g' = BlackJack.hit g { Face = Two; Suit = Spades }
您可能已经注意到,上面的签名文件还定义了两个函数来将卡片从Game 和Deal 值中取出。以下是实现:
let card = function
| Hand (c1, c2) -> [c1; c2]
| Hit c -> [c]
let cards (Game deals) = List.collect card deals
客户可以这样使用它们:
> let cs = g' |> BlackJack.cards;;
>
val cs : Card list = [{Suit = Spades;
Face = Two;};
{Suit = Spades;
Face = Ace;};
{Suit = Diamonds;
Face = King;}]
请注意,这种方法主要是结构性的;活动部件很少。
附录
这些是上面使用的文件:
Cards.fs:
namespace Ploeh.StackOverflow.Q34042428.Cards
type Suit = Diamonds | Hearts | Clubs | Spades
type Face =
| Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
| Jack | Queen | King | Ace
type Card = { Suit: Suit; Face: Face }
BlackJack.fsi:
module Ploeh.StackOverflow.Q34042428.Cards.BlackJack
type Deal
type Game
val init : Card -> Card -> Game
val hit : Game -> Card -> Game
val card : Deal -> Card list
val cards : Game -> Card list
BlackJack.fs:
module Ploeh.StackOverflow.Q34042428.Cards.BlackJack
open Ploeh.StackOverflow.Q34042428.Cards
type Deal = Hand of Card * Card | Hit of Card
type Game = Game of Deal list
let init c1 c2 = Game [Hand (c1, c2)]
let hit (Game deals) card = Game (Hit card :: deals)
let card = function
| Hand (c1, c2) -> [c1; c2]
| Hit c -> [c]
let cards (Game deals) = List.collect card deals
Client.fs:
module Ploeh.StackOverflow.Q34042428.Cards.Client
open Ploeh.StackOverflow.Q34042428.Cards
let g =
BlackJack.init
{ Face = Ace; Suit = Spades }
{ Face = King; Suit = Diamonds }
let g' = BlackJack.hit g { Face = Two; Suit = Spades }
let cs = g' |> BlackJack.cards