DmitriyChernyavskiy

DmitriyChernyavskiy

How to implement extension of functionality as inheritance in object oriented language

Hello everyone,
I’m a new in elexir and functional language. I’m trying to implement Websocket interraction with server.
On first layer I implement module based on GenServer with functionality like WebSockex and use ninenines/gun as Websocket client. This module name egun, below are the code snippets:

defmodule Egun do
  use GenServer

  @callback handle_connect(state :: term) :: {:ok, new_state :: term}
  @callback handle_message(msg :: term, state :: term) :: {:ok, new_state} | ...
  @callback handle_send(msg :: term, state :: term) :: {:ok, new_state} | ...
...

  defmacro __using__(_opts) do
    quote location: :keep do
      @behaviour Egun
      @doc false
      def handle_connect(state) do
        {:ok, state}
      end

      @doc false
      def handle_message(message, _state) do
        raise "No handle_message/2 clause in #{__MODULE__} provided for #{inspect(message)}"
      end

      @doc false
      def handle_send(message, state) do
        {:reply, {:text, message}, state}
      end

      defoverridable  handle_connect: 1,
                      handle_message: 2,
                      handle_send: 2
   end

  def start_link(conn = %Conn{}) do
    GenServer.start_link(__MODULE__, %__MODULE__{
      conn: conn,
    })
  end

  def init(state) do
     ...
     send(self(), :ws_connected)
     {:ok, state}
  end

  def send_message(client, message) do
    GenServer.call(client, {:ws_syncsend, message})
  end

  def handle_call({:ws_syncsend, message}, sender, state) do
    message = compose_msg(message, state.conn)
   ...
    {:reply, message, state}
  end

  def handle_info(:ws_connected, state) do
    common_handle(:handle_connect, state)
  end

  def handle_info(
    {:gun_ws, _gun_pid, _ws_stream, {:text, message}}, state
  ) do
    case Jason.decode(message, keys: :atoms) do
      {:ok, msg} ->
        common_handle({:handle_message, msg}, state)

      error ->
        {:stop, error, state}
    end
  end

  defp common_handle({function, msg}, state) when is_atom(function)  do
    result = apply(state.module, function, [msg, state])
    common_handle_ret(result, {function, msg}, state)
  end

  defp common_handle(function, state) when is_atom(function)  do
    result = apply(state.module, function, [state])
    common_handle_ret(result, {function, nil}, state)
  end

  defp common_handle_ret(result, {function, msg}, state) do
    case result do
      {:ok, new_state} ->
        {:noreply, new_state}

      {:reply, frame, new_state} ->
        :gun.ws_send(new_state.gun_pid, frame_encode(frame))
        {:noreply, new_state}
      
      ....
    end
  end

This implementation provides messaging through an open Websocket. Next I need extend functionality egun. And create module session which adds business logic methods:

defmodule Session do
  @moduledoc nil

  use Egun

  def create(client) when is_pid(client) do
    tr = Egun.send_message(client, %{
      session: "create",
      transaction: "yes"
      })
    GenServer.call(client, {:push_transaction, tr, client})
  end

  @impl true
  def handle_call({:push_transaction, transaction, callback_pid}, _from, state) do
    trs = Map.put(state.transactions, transaction, callback_pid)
    {:noreply, %{state | transactions: trs}}
  end

  @impl Egun
  def handle_message(message, state) do
    IO.puts("Handle message: #{inspect(message)}")
    IO.puts("State: #{inspect(state)}")
    {:ok, state}
  end
end

After compile I get warning message:

warning: got "@impl Egun" for function handle_call/3 but this behaviour does not specify such callback. The known callbacks are:

  * Egun.handle_connect/1 (function)
  * Egun.handle_message/2 (function)
  * Egun.handle_send/2 (function)

How can I modify my modules so that the Session module can implement the behavior of the GenServer and Egan without using an additional GenServer. On object oriented programming language Session can get access to all methods from GenServer and Egan?

Most Liked

kokolegorille

kokolegorille

Yes I can.

This video (and all videos made by Scott Wlaschin)

I cannot tell You how much impact they had on me :slight_smile:

Exadra37

Exadra37

For me, the only thing that made the click to switch the brain from OOP to FP was the book:

and the video course:

@pragdave spends a great deal of time in both the book and in the video course to get you into the FP mode of thinking, thus I cannot recommend enough that you chose the book or the video to get you in the mindset of FP :slight_smile:

al2o3cr

al2o3cr

Couple things:

  • Elixir is not “OO”. Especially at first, it’s important to reorient your brain to the new idioms
  • I suspect you’ve ...ed something important, because @impl doesn’t appear anywhere in the posted code
  • I think you’re looking for defoverridable which sounds very OO, but is slightly different

Where Next?

Popular in Challenges Top

connorlay
Note by the Moderators: This topic is for general discussion about the Advent of Code 2018. To prevent people from being spoiled about s...
New
bjorng
Note: This topic is to talk about Day 3 of the Advent of Code 2019 . There is a private leaderboard for elixirforum members. You can jo...
New
mattbaker
I’m having so much fun working on the “Protohackers” challenges, I never got into Advent of Code much but this has been amazing. The chal...
New
Aetherus
Today’s challenge is quite interesting. I ended up using Zipper to solve this problem. Maybe I overengineered quite a bit. The data stru...
New
maennchen
Ok, that was a rough one today. I haven’t found a way to improve the algorithm further. Part 1 runs in .5 seconds, Part 2 in ~ 5 minutes...
New
New
lud
Hello everyone! This year is going to be shorter, but the difficulty will grow faster. Today I already feel that this is not standard “D...
New

Other popular topics Top

grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54260 488
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44778 311
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement