Looking at this code:
bulb = %Bulb{state: :on} # --> %Bulb{state: :on}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off} why?
Others already answered the why.
Here is a snippet of code that might do what you want.
bulb = %Bulb{state: :on} # --> %Bulb{state: :on}
bulb = bulb |> Bulb.switch_bulb() # --> %Bulb{state: :off}
bulb |> Bulb.switch_bulb() # --> %Bulb{state: :on} because we started with a switched bulb
On the 2nd line we re-bind the variable to the newly returned switched_off_bulb
Another fun way of debugging this would be
%Bulb{state: :on}
|> IO.inspect(label: "Initial Bulb should be on đź’ˇ")
|> Bulb.switch_bulb()
|> IO.inspect(label: "After 1st switch, bulb should be off ❌💡")
|> Bulb.switch_bulb()
|> IO.inspect(label: "After 2nd switch, bulb should be onđź’ˇ")
It is important that you understand the immutable behavior at this level. So keep asking here and playing with this code until you do.
If this behavior means that you can not solve the problem you are trying to solve, please ask the higher-level question of how you would do X with immutable data structures.
I am VERY hesitant to keep writing at this point because it could steer you in the wrong direction. If you are stuck in thinking in the concept of Objects, what I am about to recommend could seem to you like an Object, and it is not. That said, saving the persistent state of an application is a solved problem in Elixir.
If this puzzle can’t be solved with pure functions, it might be better to solve simpler puzzles for a while until you feel comfortable with Elixir and then come back to this one.
If there was a real light bulb, you would have a single GenServer that holds that state for the light bulb and is in charge of communicating with the light bulb. The interface to the GenServer would have a toggle
or switch_bulb
function which will return different results based on what the previous state was.
There is a concept of a Singleton GenServer. If the GenServer is named, only one GenServer can hold that name.
However, I beseech you always try to solve problems with pure functions that operate on data structures. As your codebase grows, this is always the easiest to reason about. This is one of Elixir’s main competitive advantages, that after the codebase has grown, productivity is not slowed down by complexity as much as it would be with complex objects that represent behavior and state.