Pour frayer ou ne pas frayer?

Telle est la question! Est-il préférable de tout conserver dans un seul processus ou de créer un processus distinct pour chaque élément d'état que nous devons gérer? Dans cet article, je parlerai un peu de l'utilisation ou non des processus. Je vais également vous montrer comment découpler la logique à états complexes des problèmes tels que le comportement temporel et la communication interprocessus.





Mais avant de commencer, puisque l'article sera long, j'aimerais en esquisser les principaux points:





  • Utilisez des fonctions et des modules pour séparer les entités de pensée.





  • Utilisez des processus pour séparer les entités d'exécution.





  • N'utilisez pas de processus (même d'agents) pour séparer les entités de pensée.





Le concept «entités pensantes» fait ici référence aux idées qui sont dans notre esprit, telles que «ordre», «position dans l'ordre», «produit», etc. Si ces concepts sont trop complexes, il vaut la peine de les mettre en œuvre dans modules et fonctions séparés pour séparer les différentes entités et garder chaque partie de notre code focalisée et cohérente.





Utiliser des processus (par exemple des agents) pour cela est une erreur que les gens font souvent. Cette approche manque considérablement la fonctionnalité d'Elixir et tente plutôt d'imiter les objets par des processus. L'implémentation sera probablement pire qu'une simple approche fonctionnelle (ou même un équivalent en langage de programmation orienté objet). Par conséquent, il ne vaut la peine de se tourner vers les processus que lorsqu'il y a des avantages tangibles. L'organisation du code ne fait pas partie de ces avantages, ce n'est donc pas une bonne raison d'utiliser des processus.





- , . , , , . - , . . , , - .





, ? . , ( ), .





- , , , . . , : () (). . 21, . ( ).





- , (2-10) , , 10. 1 11, , ( ) .





, . , . , , .





, , , , (), , , .





, , : , . - . , «» , . , , , . , . : , , , . .





, , . , . . , . : , , ( ). , , , , , .





, . . , , . , . ( ) , , ( ) ( ). , , :-) (. : , ?)





, , . , , .





, , , - . , , , , . , , , :-)





, ? , . , , , , .





, , , .





, - . 52 . , .





, , . , , . , .





. , . :





@cards (
  for suit <- [:spades, :hearts, :diamonds, :clubs],
      rank <- [2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace],
    do: %{suit: suit, rank: rank}
)

      
      



shuffle/0



:





def shuffled(), do:
  Enum.shuffle(@cards)

      
      



, take/1



, :





def take([card | rest]), do:
  {:ok, card, rest}
def take([]), do:
  {:error, :empty}

      
      



take/1



{:ok, card_taken, rest_of_the_deck}



, {:error, :empty}



. ( ) , .





:





deck = Blackjack.Deck.shuffled()

case Blackjack.Deck.take(deck) do
  {:ok, card, transformed_deck} ->
    # do something with the card and the transform deck
  {:error, :empty} ->
    # deck is empty -> do something else
end

      
      



, « », :





  • ,





  • ,





  • ,









, - . - Deck



, Deck



. ( ), , ( , , , - . .)





, . . shuffled_deck/0



take_card/1



. , , , . , - . (. : , )





, . , .





.





. . (:ok



:busted



). Blackjack.Hand.





. new/0



, deal/2



, . :





# create a deck
deck = Blackjack.Deck.shuffled()

# create a hand
hand = Blackjack.Hand.new()

# draw one card from the deck
{:ok, card, deck} = Blackjack.Deck.take(deck)

# give the card to the hand
result = Blackjack.Hand.deal(hand, card)

      
      



deal/2



{hand_status, transformed_hand}



, hand_status



:ok



:busted



.





, Blackjack.Round, . :













  • ,





  • ( / )









  • ,





, . , . . , , , , , . , , .





, , /, GenServer



:gen_statem



. (, ) .





, , . , , , , . , (netsplits), , . , , , event sourcing - .





, , . .





, . .





. , start/1



:





{instructions, round} = Blackjack.Round.start([:player_1, :player_2])
      
      



, , - . , :

















  • . - . :





    [
    {:notify_player, :player_1, {:deal_card, %{rank: 4, suit: :hearts}}},
    {:notify_player, :player_1, {:deal_card, %{rank: 8, suit: :diamonds}}},
    {:notify_player, :player_1, :move}
    ]
    
          
          



    - , , . , , . , :





  • 1,





  • 1,





  • 1,





    . , , GenServer



    , . , , . () , Round



    .





, round



, . , . , round



. , , instruction



.





, 1:





{instructions, round} = Blackjack.Round.move(round, :player_1, :hit)
      
      



, , . , , .





, :





[
  {:notify_player, :player_1, {:deal_card, %{rank: 10, suit: :spades}}},
  {:notify_player, :player_1, :busted},
  {:notify_player, :player_2, {:deal_card, %{rank: :ace, suit: :spades}}},
  {:notify_player, :player_2, {:deal_card, %{rank: :jack, suit: :spades}}},
  {:notify_player, :player_2, :move}
]
      
      



, 1 . 4 8 , , . 2 , , .





2:





{instructions, round} = Blackjack.Round.move(round, :player_2, :stand)

# instructions:
[
  {:notify_player, :player_1, {:winners, [:player_2]}}
  {:notify_player, :player_2, {:winners, [:player_2]}}
]

      
      



2 , . .





, Round



Deck



Hand



. Round



:





defp deal(round) do
  {:ok, card, deck} =
    with {:error, :empty} <- Blackjack.Deck.take(round.deck), do:
      Blackjack.Deck.take(Blackjack.Deck.shuffled())

  {hand_status, hand} = Hand.deal(round.current_hand, card)

  round =
    %Round{round | deck: deck, current_hand: hand}
    |> notify_player(round.current_player_id, {:deal_card, card})

  {hand_status, round}
end

      
      



, , . , , (:ok



:busted



) . :-)





notify_player



- , . (, GenServer Phoenix). - , . , , .





, , Round



. notify_player



. , , take_instructions



Round



, , .





, . , . - . , .





. , . , , . , , .





Blackjack.RoundServer, GenServer



. Agent



, , GenServer



. , , :-)





, start_playing/2



. start_link



, start_link



. , start_playing



- , .





: . - , . .





, :





@type player :: %{id: Round.player_id, callback_mod: module, callback_arg: any}
      
      



, . . , , callback_mod.some_function (some_arguments)



, some_arguments



, , callback_arg



, .





callback_mod



, :





  • , HTTP





  • , TCP





  • iex





  • ()





    . , .





, , :





@callback deal_card(RoundServer.callback_arg, Round.player_id,
  Blackjack.Deck.card) :: any
@callback move(RoundServer.callback_arg, Round.player_id) :: any
@callback busted(RoundServer.callback_arg, Round.player_id) :: any
@callback winners(RoundServer.callback_arg, Round.player_id, [Round.player_id])
  :: any
@callback unauthorized_move(RoundServer.callback_arg, Round.player_id) :: any
      
      



, . , . . , , , , .





- , . . asserting/refuting , RoundServer.move/3



, .





Round



, , .





. , . - , . , , . , , . , .





Blackjack.PlayerNotifier, GenServer



, - . start_playing/2



, .





, . , //(M/F/A) .





, , (, , ). , . :





[
  {:notify_player, :player_1, {:deal_card, %{rank: 10, suit: :spades}}},
  {:notify_player, :player_1, :busted},
  {:notify_player, :player_2, {:deal_card, %{rank: :ace, suit: :spades}}},
  {:notify_player, :player_2, {:deal_card, %{rank: :jack, suit: :spades}}},
  {:notify_player, :player_2, :move}
]
      
      



, player_2



, player_1



, . , . , , , , .





, : Round



, . .





OTP :blackjack



( Blackjack). , : Registry



( ) :simple_one_for_one



, .





, . . Phoenix, Cowboy, Ranch ( TCP), elli , . , .





Demo, , , GenServer, , :





$ iex -S mix
iex(1)> Demo.run

player_1: 4 of spades
player_1: 3 of hearts
player_1: thinking ...
player_1: hit
player_1: 8 of spades
player_1: thinking ...
player_1: stand

player_2: 10 of diamonds
player_2: 3 of spades
player_2: thinking ...
player_2: hit
player_2: 3 of diamonds
player_2: thinking ...
player_2: hit
player_2: king of spades
player_2: busted

...

      
      



, , :





, ? , ! , Deck



and Hand



, .





, . , . , . . , .





/ , . , , ( ), - . , . - , , . (netsplits), , - .





Enfin, il vaut la peine de se souvenir du but ultime. Bien que je n'y sois pas allé (encore), j'ai toujours prévu que ce code soit hébergé sur une sorte de serveur Web. Certaines décisions sont donc prises pour soutenir ce scénario. En particulier, une implémentation RoundServer



qui accepte un module de rappel pour chaque joueur me permet de me connecter à différents types de clients utilisant différentes technologies. Cela rend le service de blackjack indépendant des bibliothèques et des frameworks spécifiques (à l'exception des bibliothèques standard et OTP, bien sûr) et le rend complètement flexible.








All Articles