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
kokolegorille

kokolegorille

… and techniques, which does not suit FP.

You don’t use inheritance, but composition in FP.

Laetitia

Laetitia

Very good video!
Thanks for sharing

Where Next?

Popular in Challenges Top

bismark
Took me a minute to remember my binary math :smile: :grimacing:… import Bitwise __DIR__ |> Path.join("puzzle.txt") |> File.stream...
New
bjorng
Note: This topic is to talk about Day 16 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
Aetherus
I tried to use combinatorial to solve today’s puzzles but failed (my brain burned out :exploding_head:). In the end I just used brute for...
New
bjorng
This topic is about Day 18 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
New
stevensonmt
Trying to get more facility with dynamic programming concepts on Leetcode and having an issue I can’t find a way around. It’s a chutes an...
New
rugyoga
Fairly straightforward Dijkstra’s algorithm import AOC aoc 2023, 17 do def compute(input, candidates) do {{max_row, max_col}, ite...
New
New
igorb
So… that’s it? Everyone is stuck on part 2? :slight_smile: I looked at Reddit hints and thought I probably wouldn’t have come up with the...
New
Aetherus
I spent 3 hours struggling in part 2, until I noticed a very basic mistake :joy: Here’s my code: By the way, the starting position in...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
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
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement