¿Engendrar o no engendrar?

¡Esa es la pregunta! ¿Es mejor mantener todo en un solo proceso o crear un proceso separado para cada parte del estado que necesitamos administrar? En este artículo, hablaré un poco sobre el uso o no de procesos. También le mostraré cómo desacoplar la lógica con estado compleja de cuestiones como el comportamiento temporal y la comunicación entre procesos.





Pero antes de comenzar, dado que el artículo será extenso, me gustaría esbozar los puntos principales:





  • Utilice funciones y módulos para separar entidades de pensamiento.





  • Utilice procesos para separar entidades en tiempo de ejecución.





  • No utilice procesos (ni siquiera agentes) para separar entidades de pensamiento.





El constructo "entidades pensantes" aquí se refiere a las ideas que están en nuestra mente, como "orden", "posición en el orden", "producto", etc. Si estos conceptos son demasiado complejos, entonces vale la pena implementarlos en módulos y funciones separados para separar diferentes entidades y mantener cada parte de nuestro código enfocada y coherente.





El uso de procesos (por ejemplo, agentes) para esto es un error que la gente suele cometer. Este enfoque pierde significativamente la funcionalidad de Elixir y, en cambio, intenta imitar objetos mediante procesos. Es probable que la implementación sea peor que un enfoque funcional simple (o incluso un lenguaje de programación orientado a objetos equivalente). Por lo tanto, vale la pena recurrir a los procesos solo cuando se obtienen beneficios tangibles. La organización del código no es uno de esos beneficios, por lo que no es una buena razón para utilizar procesos.





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





, ? . , ( ), .





- , , , . . , : () (). . 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 la pena recordar el objetivo final. Aunque no fui allí (todavía), siempre planeé que este código estaría alojado en algún tipo de servidor web. Entonces se toman algunas decisiones para respaldar este escenario. En particular, una implementación RoundServer



que acepta un módulo de devolución de llamada para cada jugador me permite conectarme a diferentes tipos de clientes utilizando diferentes tecnologías. Esto hace que el servicio de blackjack sea independiente de bibliotecas y marcos específicos (con la excepción de las bibliotecas estándar y OTP, por supuesto) y lo hace completamente flexible.








All Articles