blackened
blackened

Reputation: 903

Manipulating a struct in an existing variable

Complete Elixir beginner. I am sure I am missing some fundamental issues in the following. Can someone please explain, exactly why the second code not work as the first one?

defmodule Bulb do
  defstruct [:state]

  def turn_on(bulb = %Bulb{}) do
    %{bulb | state: :on}
  end

  def turn_off(bulb = %Bulb{}) do
    %{bulb | state: :off}
  end
end

bulb = %Bulb{state: :on} # --> %Bulb{state: :on}
bulb |> Bulb.turn_off() # --> %Bulb{state: :off}
bulb |> Bulb.turn_on() # --> %Bulb{state: :on}

versus

defmodule Bulb do
  defstruct [:state]

  def switch_bulb(bulb = %Bulb{}) do
    case bulb do
      %{state: :off} -> %{bulb | state: :on}
      %{state: :on} -> %{bulb | state: :off}
    end
  end
end

bulb = %Bulb{state: :on} # --> %Bulb{state: :on}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off} why?

(Yes, the second one also works if I do the following.)

defmodule Bulb do
  defstruct [:state]

  def switch_bulb(bulb = %Bulb{}) do
    case bulb do
      %{state: :off} -> %{bulb | state: :on}
      %{state: :on} -> %{bulb | state: :off}
    end
  end
end

bulb = %Bulb{state: :on} # --> %Bulb{state: :on}
bulb = bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off}
bulb = bulb |> Bulb.switch_bulb() # --> %Bulb{state: :on}

Upvotes: 0

Views: 73

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

In , everything is immutable. bulb |> Bulb.turn_on() from the first snippet does not do what you think it does. It does literally nothing, actually, “switching” the state from on (initial value) to on.

To understand, what happens, do print bulb every time.

bulb = %Bulb{state: :on} # %Bulb{state: :on}
bulb |> IO.inspect() |> Bulb.turn_off() |> IO.inspect() # on → off
bulb |> IO.inspect() |> Bulb.turn_on() |> IO.inspect() # on → on

bulb from inside switch_bulb/1 function has the same name, but a very different scope, it’s not the same variable. bulb stays what it was assigned to.

bulb = %Bulb{state: :on}   # --> %Bulb{state: :on}
IO.inspect(bulb)           # --> %Bulb{state: :on}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off}
bulb                       # --> %Bulb{state: :on}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off} why?

Even more: the example below won’t change the value of outermost bulb too

bulb = :on
if true, do: bulb = :off # different scope, outermost `bulb` is untouched
IO.inspect(bulb) #⇒ :on

That said unless you reassign the value of any expression, it simply gets lost. Nothing is mutated in place.


BTW, = is NOT an assignment, it’s a matching operator. In this case, it’s rebinding the variable.

Upvotes: 2

Related Questions