Generics in Elixir

Elixir introduced protocols as a way to achieve polymorphism, which can be used to implement behavior similar to generics in some cases. However, Elixir doesn’t have a direct equivalent to Go’s generics. We’ll use protocols and other Elixir features to demonstrate similar concepts.

defmodule Generics do
  # Elixir doesn't have a direct equivalent to Go's generics,
  # but we can use protocols to achieve similar functionality.
  # Here's an example of a protocol that can work with any
  # enumerable type.

  defprotocol Findable do
    @doc "Finds the index of an element in an enumerable"
    def find_index(enumerable, element)
  end

  # Implement the protocol for List
  defimpl Findable, for: List do
    def find_index(list, element) do
      Enum.find_index(list, &(&1 == element))
    end
  end

  # Implement the protocol for other enumerables
  defimpl Findable, for: Enumerable do
    def find_index(enumerable, element) do
      enumerable
      |> Enum.with_index()
      |> Enum.find(fn {e, _} -> e == element end)
      |> case do
        {_, index} -> index
        nil -> -1
      end
    end
  end

  # Define a custom linked list
  defmodule LinkedList do
    defstruct [:head, :tail]

    def new, do: %LinkedList{}

    def push(%LinkedList{head: nil, tail: nil} = list, value) do
      new_node = %{value: value, next: nil}
      %{list | head: new_node, tail: new_node}
    end

    def push(%LinkedList{tail: tail} = list, value) do
      new_node = %{value: value, next: nil}
      %{list | tail: %{tail | next: new_node}, tail: new_node}
    end

    def all_elements(%LinkedList{head: head}) do
      all_elements(head, [])
    end

    defp all_elements(nil, acc), do: Enum.reverse(acc)
    defp all_elements(node, acc), do: all_elements(node.next, [node.value | acc])
  end

  def main do
    s = ["foo", "bar", "zoo"]

    # Using our Findable protocol
    IO.puts("index of zoo: #{Findable.find_index(s, "zoo")}")

    # Create and use our custom LinkedList
    list = LinkedList.new()
           |> LinkedList.push(10)
           |> LinkedList.push(13)
           |> LinkedList.push(23)

    IO.puts("list: #{inspect(LinkedList.all_elements(list))}")
  end
end

Generics.main()

In this Elixir example:

  1. We define a Findable protocol that’s similar to the SlicesIndex function in the Go example. This protocol can be implemented for different types.

  2. We implement the Findable protocol for List and other Enumerable types.

  3. We create a custom LinkedList struct to demonstrate a generic data structure. In Elixir, we don’t need to specify type parameters as it’s a dynamically typed language.

  4. The main function demonstrates the usage of our Findable protocol and LinkedList struct.

To run this program, save it as generics.exs and use:

$ elixir generics.exs
index of zoo: 2
list: [10, 13, 23]

Note that Elixir’s approach to polymorphism and data structures is quite different from Go’s. Elixir uses protocols and pattern matching to achieve similar results to generics in statically typed languages.