Update Documentation for GenServer

38 views
Skip to first unread message

Bruce Tate

unread,
Apr 10, 2023, 10:14:16 AM4/10/23
to elixir-l...@googlegroups.com
I would like to propose that we change the base example for GenServer. For those not familiar with it, here it is: 

    defmodule Stack do

      use GenServer

    

      # Callbacks

    

      @impl true

      def init(stack) do

        {:ok, stack}

      end

    

      @impl true

      def handle_call(:pop, _from, [head | tail]) do

        {:reply, head, tail}

      end

    

      def handle_cast({:push, element}, state) do

        {:noreply, [element | state]}

      end

    end


These are the problems as I teach new Elixir developers OTP. 

First, the purpose of init is not clear. It simply returns a stack, and does not indicate that this is an opportunity to transform the input in some way: 

      def init(stack) do

        {:ok, stack}

      end


As a result, it's not clear what to use init for.

Second, we don't label the state of the GenServer appropriately, and worse, we do work in the function head, making it harder for users to grasp the central ideas that GenServers carry state and offer the opportunity to transform it: 

      def handle_call(:pop, _from, [head | tail]) do

        {:reply, head, tail}

      end


As a result, it's too hard for users to understand what this does without already knowing. 

Third, the return tuples are not explicitly labeled, forcing the reader to work harder to understand what the tuples actually do. 

Summarizing the problems: 

- The example doesn't use init to transform the initial value to the genserver state.
- The example doesn't consistently label the last argument of the two handlers. 
- The example does work in the "pop" function head, limiting our opportunity to label concepts.
- The example doesn't explicitly label the elements of the reply and noreply tuples. 

I would like to propose these changes: 

    defmodule Stack do

      use GenServer

    

      # Callbacks

    

      @impl true

      def init(string) do

        {:ok, String,split(string, ",", trim: true)}

      end

    

      @impl true

      def handle_call(:pop, _from, state) do

        [to_client | to_server] = state

        {:reply, head, tail}

      end

    

      def handle_cast({:push, element}, state) do

        to_server = [element | state]

        {:noreply, to_server}

      end

    end


I don't really care whether we label the state with state or stackI have a slight preference for state because we're labeling the genserver concept, not the domain concept. 

Likewise, I don't care whether we use to_server or new_genserver_state on the server side, or the to_client or to_caller to discuss the results of the reply and noreply tuples. 

I should also point out that we don't handle the error state and it's easy to do so. 

I mainly care that this example communicates with more clarity. 

Feedback is welcome. 

-bt

 
Regards,
Bruce Tate
CEO


José Valim

unread,
Apr 10, 2023, 1:21:12 PM4/10/23
to elixir-l...@googlegroups.com
Agreed. I just wouldn't call it to_server but "state". Easier to bikeshed on code, so a PR is welcome. :)

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAFXvW-5%2B3ECDYi76eENLEEfHDCyRfkNjX-A%3DHCKtxqFy%2BCATSQ%40mail.gmail.com.

Bruce Tate

unread,
Apr 10, 2023, 2:02:46 PM4/10/23
to elixir-l...@googlegroups.com
Will do. 

-bt
--

Regards,
Bruce Tate
CEO

Reply all
Reply to author
Forward
0 new messages