DEV Community

Cover image for Odin's Elixir
Camilo
Camilo

Posted on

Odin's Elixir

Also available at: https://ninjas.cl/blog/odin-s-elixir/

What is Odin?

Odin is a system level language that is a direct alternative to Rust, Zig or C. Creating an alternative to C language is nearly an impossible task, because more than 40 years of history of C. But at least a new programming language can be an alternative to C in modern systems and architectures, as the website says "Odin is the C alternative for the Joy of Programming". The official implementation of Odin currently supports: amd64, arm64/aarch64 (Rapsberry Pi), and wasm32/wasm64p32. The language borrows heavily from: Pascal, C, Go, Oberon-2, Newsqueak, GLSL.

What are the guiding principles behind the design of Odin?

Odin is a general-purpose programming language with distinct typing built for high performance, modern systems and data-oriented programming.

  • Simplicity and readability.
  • Minimal: there ought to be one way to write something.
  • Striving for orthogonality.
  • Programs are about transforming data into other forms of data.
  • Code is about expressing algorithms—not the type system.
  • There is embedded knowledge and wisdom in older programming languages.
  • The entire language specification should be possible to be memorized by a mere mortal.

Hello World in Odin

package main

import "core:fmt"

main :: proc() {
    fmt.println("Hellope!")
}
Enter fullscreen mode Exit fullscreen mode

Fibonacci

fibonacci :: proc(n: int) -> int {
    switch {
        case n < 1:
            return 0
        case n == 1:
            return 1
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

fmt.println(fibonacci(3)) // 2
Enter fullscreen mode Exit fullscreen mode

Odin's Elixir?

Odin and Elixir is a match made in Valhalla. With Elixir we can create robust and reliable systems and with Odin we can create powerful and secure low level extensions with FFI, or self contained terminal applications and libraries that can be shared with other programming languages, instead of using alternatives such as C, Rust, Zig or Go.

This is a video how to integrate Odin and Elixir:

Elixir (2011) and Odin (2016), despite the fact that they are very modern languages, are quite small. You can read through the language guide in a few hours and have a pretty good grasp on the core concepts. This leads to much quicker mastery.

Treasure Hunt

Let's do a simple exercise with CRC (Constructors, Reducers and Converters) a principle of code organization made by Bruce Tate.

  • Constructors: Are procedures (or functions) that creates and sets data structures that will be passed down to Reducers and Converters. This data structure is known as the accumulator or token.

  • Reducers: These procedures take the accumulator and "reduce it", applying a pipeline of different operations and procedures until it reaches a state ready for the Converter.

  • Converter: Takes the accumulator and "convert it" to a final format, that is ready for displaying to the user or pass it to another pipeline of CRC. Example would be a procedure that takes a json data structure and convert it to string, this string will be passed down to the pipeline for saving the contents to a file in disk.

Thanks to this form of organization, we can see our code in a coherent and unified way. By having a common data structure with several procedures (or functions), we can perform operations and express ourselves with greater readability. Our systems will be easier to understand and maintain in the future. Consistency is an important quality factor in our systems and using CRC is a great tool to achieve that. The really valuable idea in this principle is that it's extremely handy if you can clearly separate procedures that change state from those that don't. This is because you can use creators procedures in many situations with much more confidence, introducing them anywhere, changing their order. You have to be more careful with reducers and converters.

Elixir Version

defmodule Treasure do
  # Creator
  def new(x, y), do: {x, y}
  def new, do: new(0, 0)

  # Reducers
  def north({x, y}), do: {x, y - 1}
  def south({x, y}), do: {x, y + 1}

  def east({x, y}), do: {x - 1, y}
  def west({x, y}), do: {x + 1, y}

  # Converter
  def show({x, y}), do: "The treasure is located at (#{x},#{y})"
end


Treasure.new()
|> north()
|> north()
|> north()
|> north()
|> west()
|> west()
|> west()
|> south()
|> east()
|> east()
|> east()
|> show() # The treasure is located at (0,-3)
Enter fullscreen mode Exit fullscreen mode

Odin Version 1

package CRC

import "core:fmt"

Treasure :: struct {
    x: int,
    y: int
}

// Constructors
new :: proc(x: int, y: int) -> Treasure {
    treasure : Treasure
    treasure.x = x
    treasure.y = y
    return treasure
}

empty :: proc() -> Treasure {
    return new(0, 0)
}

// Reducers
north :: proc(treasure : Treasure) -> Treasure {
    return new(treasure.x, treasure.y - 1)
}

south :: proc(treasure : Treasure) -> Treasure {
    return new(treasure.x, treasure.y + 1)
}

east :: proc(treasure : Treasure) -> Treasure {
    return new(treasure.x - 1, treasure.y)
}

west :: proc(treasure : Treasure) -> Treasure {
    return new(treasure.x + 1, treasure.y)
}

// Converter
show :: proc (treasure : Treasure) -> (string, int, int) {
    return "The treasure is located at (%d, %d)", treasure.x, treasure.y
}

// Pipeline
main :: proc() {
    treasure := empty() // (0, 0)
    treasure = north(treasure) // (0, -1)
    treasure = north(treasure) // (0, -2)
    treasure = north(treasure) // (0, -3)
    treasure = north(treasure) // (0, -4)
    treasure = west(treasure)  // (1, -4)
    treasure = west(treasure)  // (2, -4)
    treasure = west(treasure)  // (3, -4)
    treasure = south(treasure) // (3, -3)
    treasure = east(treasure)  // (2, -3)
    treasure = east(treasure)  // (1, -3)
    treasure = east(treasure)  // (0, -3)
    out, x, y := show(treasure)
    fmt.printfln(out, x, y) // The treasure is at (0, -3)
}
Enter fullscreen mode Exit fullscreen mode

Odin Version 2

Odin provides another interesting way of approaching this challenge. This is using Array programming techniques where basic operations (+, -, *, etc.) on two fixed-size arrays of the same length are done element-wise. One of the goodies of Odin.

Treasure :: [2]int

NORTH :: Treasure{ 0, -1}
SOUTH :: Treasure{ 0, +1}
EAST  :: Treasure{-1,  0}
WEST  :: Treasure{+1,  0}

treasure := \
    NORTH + // (0, -1)
    NORTH + // (0, -2)
    NORTH + // (0, -3)
    NORTH + // (0, -4)
    WEST +  // (1, -4)
    WEST +  // (2, -4)
    WEST +  // (3, -4)
    SOUTH + // (3, -3)
    EAST +  // (2, -3)
    EAST +  // (1, -3)
    EAST    // (0, -3)
Enter fullscreen mode Exit fullscreen mode

This have the strong advantage that if something like a string is used instead of numbers to generate the treasure map, it won't compile. The validation is then at compiler level instead of at runtime.

Final Thoughts

Odin and Elixir provides powerful tools to create robust systems, low level, bindings and perfomant code. Any Elixir developer would benefit learning Odin and any Odin developer would benefit learning Elixir.

Odin and Elixir is truly a match made in Valhalla.

Top comments (0)