Translating Swift Enum Type into Elixir

We explore how to adapt Swift's indirect enum feature, specifically a Result enum, into Elixir's functional paradigm. Perfect for developers looking to expand their understanding of both Swift and Elixir.

Introduction

The ability to understand and translate concepts between languages is a valuable skill. Today, we’re focusing on a specific feature from Swift - the indirect keyword in enums - and how to replicate its functionality in Elixir. The Swift code we’re working with is a Result enum, which showcases the versatility of enums in Swift.

enum Result {
    case player(score: Int)
    case cpu(score: Int)
    case tie
    indirect case previous(Result)
}

In Swift, the indirect keyword allows an enum case to be recursive. In our example, previous(Result) can hold a Result which might itself be a previous. This is particularly useful for representing state that has a notion of history or progression, common in many applications.

Translating to Elixir

Elixir, being a functional language, does not have a direct equivalent to Swift’s enums. However, we can use tuples and pattern matching to achieve a similar result. Here’s how we might represent our Result enum in Elixir:

defmodule Result do
  defstruct [:type, :score]

  @types ~w(player cpu tie previous)a

  def all_cases(), do: @types

  def score(type, score \\ 0)
  def score(type, score) when type in @types, do: put_score(type, score)
  def score(type, _score), do: raise(ArgumentError, message: "You cannot use #{inspect(type)}")

  defp put_score(:player, score), do: %__MODULE__{type: :player, score: score}
  defp put_score(:cpu, score), do: %__MODULE__{type: :cpu, score: score}
  defp put_score(:tie, score), do: %__MODULE__{type: :tie, score: score}
  defp put_score(:previous, previous = %__MODULE__{type: type}) when type in @types, do: %__MODULE__{type: :previous, score: previous}
  defp put_score(:previous, _previous), do: raise(ArgumentError, message: "You must pass in a previous Result that has a known type of :player, :cpu, :tie, :previous")

end

In this Elixir version, we define a Result module with a struct. The struct has three fields: :type, :score, and :previous. We then define functions for each case that would be an enum case in Swift. The previous function takes a Result struct and sets it as the :previous field, mimicking the recursive behavior of Swift’s indirect.

Unit Test Example

In this example you can see how this module would work

defmodule ResultTest do
  use ExUnit.Case

  describe "Result" do
    test "creating player result" do
      result = Result.score(:player, 10)
      assert result.type == :player
      assert result.score == 10
    end

    test "creating cpu result" do
      result = Result.score(:cpu, 20)
      assert result.type == :cpu
      assert result.score == 20
    end

    test "creating tie result" do
      result = Result.score(:tie)
      assert result.type == :tie
      assert result.score == 0
    end

    test "creating previous result" do
      previous_result = Result.score(:player, 10)
      result = Result.score(:previous, previous_result)
      assert result.type == :previous
      assert result.score == previous_result
    end

    test "raising error for invalid type" do
      assert_raise ArgumentError, fn -> Result.score(:foo) end
      assert_raise ArgumentError, fn -> Result.score(:previous, 100) end
    end
  end
end

Pattern Matching in Elixir

One of the strengths of Elixir is its pattern matching capabilities. When using our Result module, we can easily destructure and handle different cases:

def handle_result(%Result{type: :player, score: score}) do
  # handle player result
end

def handle_result(%Result{type: :previous, previous: previous_result}) do
  # handle previous result recursively
end

Conclusion

While Swift and Elixir have different paradigms and syntactical structures, the core concept of a recursive enum in Swift can be effectively translated to Elixir. By using structs and pattern matching, Elixir can mimic the behavior of Swift’s indirect enums, allowing for similar functionality in both languages.

This exercise not only shows the power and flexibility of these languages but also underscores the importance of understanding core programming concepts that transcend language boundaries. Whether you’re a Swift developer learning Elixir or vice versa, grasping these concepts can greatly enhance your programming skills and broaden your toolkit.