Desovar ou não desovar?

Essa é a questão! É melhor manter tudo em um processo ou criar um processo separado para cada parte do estado que precisamos gerenciar? Neste artigo, falarei um pouco sobre o uso ou não de processos. Também mostrarei como desacoplar a lógica complexa com estado de questões como comportamento temporal e comunicação entre processos.





Mas antes de começar, como o artigo será longo, gostaria de destacar os pontos principais:





  • Use funções e módulos para separar entidades de pensamento.





  • Use processos para separar entidades de tempo de execução.





  • Não use processos (mesmo agentes) para separar entidades de pensamento.





O construto "entidades pensantes" aqui se refere às ideias que estão em nossa mente, como "ordem", "posição na ordem", "produto", etc. Se esses conceitos forem muito complexos, vale a pena implementá-los em módulos e funções separados para separar entidades diferentes e manter cada parte de nosso código focada e coerente.





Usar processos (por exemplo, agentes) para isso é um erro que as pessoas costumam fazer. Essa abordagem perde significativamente a funcionalidade do Elixir e, em vez disso, tenta imitar objetos por processos. A implementação provavelmente será pior do que uma abordagem funcional simples (ou mesmo uma linguagem de programação orientada a objetos equivalente). Portanto, vale a pena recorrer aos processos apenas quando houver benefícios tangíveis deles. A organização do código não é um desses benefícios, portanto, não é um bom motivo para usar processos.





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





, ? . , ( ), .





- , , , . . , : () (). . 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), , - .





Finalmente, vale a pena lembrar o objetivo final. Embora eu não tenha ido lá (ainda), sempre planejei que esse código fosse hospedado em algum tipo de servidor web. Portanto, algumas decisões são tomadas para apoiar este cenário. Em particular, uma implementação RoundServer



que aceita um módulo de retorno de chamada para cada jogador permite que eu me conecte a diferentes tipos de clientes usando diferentes tecnologias. Isso torna o serviço de blackjack independente de bibliotecas e estruturas específicas (com exceção das bibliotecas padrão e OTP, é claro) e o torna completamente flexível.








All Articles