如何确保非法行为不可执行?

时间:2015-12-02 12:11:39

标签: f# functional-programming state-machine

如何使非法行为无法执行?

要点:

自从开始学习F#之后,我正在学习类型驱动设计和基于属性的测试。结果,我爱上了让非法国家无法代表的想法。

但我真正想做的是使非法行为无法执行。

我正在通过写一个BlackJack游戏来学习F#。因此,我想确保当经销商分发卡片时,经销商只能处理“初始手”或“击中”。所有其他卡片分发都是非法的。

在C#中,我将实现策略模式,从而创建一个DealHandCommand和一个DealHitCommand。然后我会硬编码一个常数整数值来表示要解决的卡数(按策略)。

DealHandCommand = 2张卡

DealHitCommand = 1张卡

基于这些策略,我会实现一个状态机来代表BlackJack游戏的一个会话。因此,在我处理了初始手牌(即DealHandCommand)之后,我执行了一个状态转换,其中未来的交易只能执行“DealHitCommand”。

具体而言,在混合功能语言中实现状态机是否有意义以实现非法行为?

3 个答案:

答案 0 :(得分:9)

在F#中实现状态机很容易。它通常遵循三个步骤,第三步是可选的:

  1. 使用每个州的案例定义一个被歧视的联盟
  2. 为每个案例定义过渡函数
  3. 可选:实现所有其余代码
  4. 第1步

    在这种情况下,我觉得有两种状态:

    • 带有两张卡的初始
    • 使用额外的卡点击

    这表明这个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启动的 a新游戏。另一方面(双关语)你需要提供进入手中的

    let init c1 c2 = Game [Hand (c1, c2)]
    

    另一种情况是游戏正在进行中,您应该只允许Hit,而不是Hand,所以要定义此转换:

    let hit (Game deals) card = Game (Hit card :: deals)
    

    如您所见,hit功能要求您传入现有的Game

    第3步

    什么阻止客户创建无效的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
    

    此处声明类型DealGame,但它们的“构造函数”不是。这意味着您无法直接创建这些类型的值。例如,这不会编译:

    let g = BlackJack.Game []
    

    给出的错误是:

      

    错误FS0039:未定义值,构造函数,命名空间或类型“Game”

    创建Game值的唯一方法是调用为您创建它的函数:

    let g =
        BlackJack.init
            { Face = Ace; Suit = Spades }
            { Face = King; Suit = Diamonds }
    

    这也使您可以继续游戏:

    let g' = BlackJack.hit g { Face = Two; Suit = Spades }
    

    您可能已经注意到,上述签名文件还定义了两个函数,用于从GameDeal值中获取卡片。以下是实施:

    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
    

答案 1 :(得分:2)

点击还有一张卡吗?

如果是这样,那么只需使用两个类型

  • type HandDealt = Dealt of Card * Card
  • type Playing = Playing of Cards
  • (可能更多 - 取决于你想要的)。

然后代替命令,你有简单的功能:

  • dealHand :: Card * Card -> HandDealt
  • start :: HandDealt -> Playing
  • dealAnother :: Playing -> Card -> Playing
通过这种方式,您只能遵循某种行为并对其进行静态检查。

你可能想把这些类型扩展到多个玩家,但我认为你会得到我要去的东西

PS:也许你甚至想跳过HandDealt / start阶段(如果你不需要中间阶段来进行投注/分裂/等等) - 但请注意我对二十一点没有任何线索):

  • dealHand :: Card * Card -> Playing
  • dealAnother :: Playing -> Card -> Playing

它取决于你

答案 2 :(得分:1)

其中一种可能性可能是使用歧视联盟:

type DealCommand =
    | Hand of Card * Card
    | Hit of Card

(假设您有类型Card

相关问题