DRY with immutable variables

I am new to functional programming and I struggle a bit with immutability.

In this case, how would you write this code in a more elegant way to avoid repeating code?

def handle_event("guess", %{"number" => guess} = data, socket) do
    if String.to_integer(guess) == Enum.random(1..10) do
      {:noreply,
       assign(socket,
         message: "Your guess: #{guess} is correct. Congratulations!",
         score: socket.assigns.score + 1,
         time: time()
       )}
    else
      {:noreply,
       assign(socket,
         message: "Your guess: #{guess} is wrong. Guess again.",
         score: socket.assigns.score - 1,
         time: time()
       )}
    end
  end

This is how I would do it :

def handle_event("guess", %{"number" => guess}, socket) do
    guess =  String.to_integer(guess)

      {:noreply,
        assign(
         socket,
         message: define_message(guess),
         score: compute_new_score(socket.assigns.score, guess),
         time: time()
       )}
  end

defp define_message(guess) when guess <= 10 and guess >= 1 do
 "Your guess: #{guess} is correct. Congratulations!"
end

defp define_message(guess), do: "Your guess: #{guess} is wrong. Guess again."

defp compute_new_score(prev_score, guess) when guess <= 10 and guess >= 1 do
  prev_score + 1
end

defp compute_new_score(prev_score), do: prev_score - 1
1 Like

Alright, so bringing the random number back in, it would end up like this then.
(Passing random down to make sure it’s the same random number for message and score)

def handle_event("guess", %{"number" => guess}, socket) do
    guess = String.to_integer(guess)
    random = Enum.random(1..10)

    {:noreply,
     assign(socket,
       message: define_message(guess, random),
       score: compute_new_score(guess, random, socket.assigns.score),
       time: time()
     )}
  end

  defp define_message(guess, random) when guess == random,
    do: "Your guess: #{guess} is correct. Congratulations!"

  defp define_message(guess, _random), do: "Your guess: #{guess} is wrong. Guess again."

  defp compute_new_score(guess, random, prev_score) when guess == random, do: prev_score + 1

  defp compute_new_score(_guess, _random, prev_score), do: prev_score - 1

It is a nice version at least for learning purposes, and you are probably correct that this is a better general practice, but I am honestly not convinced it looks more elegant, at least not in this example, hehe.

Thank you though, I really appreciate it :slight_smile:

I would’ve started by simply extracting out the parts, which indeed change by the condition from the parts that don’t:

def handle_event("guess", %{"number" => guess} = data, socket) do
  {message, score_effect} = 
    if String.to_integer(guess) == Enum.random(1..10) do
      {"Your guess: #{guess} is wrong. Guess again.", fn score -> score - 1 end}
    else
      {"Your guess: #{guess} is correct. Congratulations!",  fn score -> score + 1 end}
    end
  
  socket = socket |> assign(message: message) |> update(:score, score_effect)
  {:noreply, socket}
end
4 Likes

Oh okay sorry I didn’t get the random thing !

Therefore I would do it that way instead :

def handle_event("guess", %{"number" => guess}, socket) do
    guess = String.to_integer(guess)
    random = Enum.random(1..10)

    {:noreply,
     assign(socket,
       message: define_message(guess, success: guess == random),
       score: compute_new_score(success: guess == random, socket.assigns.score),
       time: time()
     )}
  end

  defp define_message(guess, success: true), do: "Your guess: #{guess} is correct. Congratulations!"

  defp define_message(guess, success: false), do: "Your guess: #{guess} is wrong. Guess again."

  defp compute_new_score(success: true, prev_score) when guess == random, do: prev_score + 1

  defp compute_new_score(success: false, prev_score), do: prev_score - 1
1 Like

How would mutability help here?

This looks much sexier indeed. :smiling_face_with_three_hearts:

I would normally just define the wrong message first and overwrite the value if the guess was correct.

var message = "Your guess is wrong. Guess again"

if (guess == random_number) {
    message = "Your guess is correct. Congratulations!"
}

...

I see, I think this is one of the cases were immutability does not hurt at all (it can) but just forces you to write better code. Isn’t

message =
  if guess == random_number do
    "Your guess is correct. Congratulations!"
  else
    "Your guess is wrong. Guess again"
  end

more clear?

2 Likes

Yes, I agree. I have had so many problems with JavaScript that I immediately see will never be an issue with Phoenix, and I am completely in love with it.

I think immutability is fantastic, but the pattern matching takes a bit to get used to :grinning_face_with_smiling_eyes: