Elixir + Odin: エリクサーとオーディンでの宝探し

🇨🇱 こんにちは、チリ出身のカミロです。自動翻訳を使用しています。読んでくださってありがとうございます。それでも良い読み物になれば幸いです。

## Odinとは何ですか？

Odinは、_Rust_、_Zig_、または _C_ の直接的な代替となるシステムレベルのプログラミング言語です。 _C_ 言語の代替を作ることはほぼ不可能な課題ですが、 _C_ 言語の40年以上の歴史を考慮しても、少なくとも新しいプログラミング言語が現代のシステムやアーキテクチャにおいて_C_の代替となることは可能です。公式ウェブサイトでは、「Odinはプログラミングの喜びのためのCの代替」と述べています。現在、Odinの公式実装は、amd64、arm64/aarch64（Raspberry Pi）、およびwasm32/wasm64p32をサポートしています。この言語は、_Pascal_、_C_、_Go_、_Oberon-2_、_Newsqueak_、_GLSL_ から多くのアイデアを借りています。

## Odinのデザインの指針とは？

Odinは、高性能で現代的なシステムとデータ指向プログラミングのために設計された一般的な目的のプログラミング言語で、独自の型システムを持っています。

- シンプルさと可読性。
- 最小限：物事を書く方法は一つであるべき。
- 直交性を追求。
- プログラムはデータを別のデータ形式に変換すること。
- コードはアルゴリズムを表現するものであり、型システムではない。
- 古いプログラミング言語には埋め込まれた知識と知恵がある。
- 言語仕様全体は、普通の人間でも記憶できる程度であるべき。

**OdinでのHello World**

```go
package main

import "core:fmt"

main :: proc() {
    fmt.println("Hellope!")
}
```

**Fibonacci**

```go
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
```

## OdinとElixirの相性

_Odin_ と _Elixir_ は、_ヴァルハラ_ で生まれた相性抜群のコンビです。_Elixir_ で堅牢で信頼性の高いシステムを作り、OdinでFFIを使って強力で安全な低レベル拡張を作成したり、自己完結型のターミナルアプリケーションやライブラリを作成し、それらを他のプログラミング言語と共有することができます。C、Rust、Zig、_Go_ などの代替を使う代わりにです。

以下の動画では、_Odin_ と _Elixir_ の統合方法を紹介しています：

<iframe width="560" height="315" src="https://www.youtube.com/embed/Gyt0kzLhtgs?si=zCMhJpoZViMdGq8W" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

Elixir（2011年）と _Odin_（2016年）は非常にモダンな言語でありながら、かなり小さいです。言語ガイドを数時間で読み終えることができ、主要な概念についてかなり理解することができます。これにより、習得が非常に速くなります。

## Treasure Hunt（宝探し）

Bruce Tate_によって提案されたコードの組織原則である CRC（Constructors、Reducers、Converters）を使ったシンプルな練習をしてみましょう。

<iframe width="560" height="315" src="https://www.youtube.com/embed/69wZzHYa0aM?si=VrJrSCOdRYm3XesI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

- `Constructors`（生成子）：データ構造を作成して設定する手続き（または関数）。このデータ構造はReducersやConvertersに渡されます。このデータ構造はaccumulator（累積器）やtoken（トークン）と呼ばれます。

- `Reducers`（還元子）：accumulatorを受け取り、それを「還元」して、いくつかの操作や手続きを適用し、最終的にConverterに渡せる状態にします。

- `Converters`（変換子）：accumulatorを受け取り、それを最終的なフォーマットに変換して、ユーザーに表示するためや他のパイプラインに渡すための準備をします。例えば、jsonデータ構造をstringに変換する手続きなどが考えられます。この文字列はディスクにファイルを保存するためのパイプラインに渡されます。

このような組織化により、コードを一貫性と統一性を持って見ることができます。共通のデータ構造と複数の手続き（または関数）を使うことで、操作をより読みやすく表現できます。システムは将来的に理解しやすく、保守もしやすくなります。整合性はシステムの重要な品質要素であり、_CRC_を使用することはそれを達成するための優れたツールです。この原則で本当に価値があるのは、状態を変更する手続きとしない手続きを明確に分けることが非常に便利だという点です。なぜなら、生成子手続きを多くの状況で自信を持って使用でき、順番を変更しても問題がないからです。還元子や変換子はより慎重に扱う必要があります。

### Elixir版

```elixir
defmodule Treasure do
  # 生成子
  def new(x, y), do: {x, y}
  def new, do: new(0, 0)

  # 還元子
  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}

  # 変換子
  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)
```

### Odin版 1

```go
package CRC

import "core:fmt"

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

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

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

// 還元子
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)
}

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

// パイプライン
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)
}
```

### Odin版 2

Odinはこの課題に対して別の面白いアプローチを提供しています。これは配列プログラミング技法を使用し、同じ長さの2つの固定サイズ配列に対する基本的な演算（+、-、*など）を要素ごとに行います。これがOdinの一つの利点です。

- [_Array programming_](https://odin-lang.org/docs/overview/#array-programming)

```go
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)
```

このアプローチの大きな利点は、例えば文字列を使って宝の地図を生成する場合、コンパイル時にエラーが発生するため、実行時ではなくコンパイラレベルで検証が行われることです。

## 最後の考え

_Odin_ と _Elixir_ は、堅牢なシステム、低レベルのバインディング、そして高性能なコードを作成するための強力なツールを提供します。_Elixir_ の開発者は_Odin_を学び、_Odin_ の開発者は_Elixir_ を学ぶことで、双方にとって大きな利点があるでしょう。

_Odin_ と _Elixir_ は、まさにヴァルハラで生まれた相性抜群のコンビです。