Ruby で矢印表記を使いたい

# ↑ で冪乗を計算できるようにする

四則演算の演算子は大体のプログラミング言語で共通して `+` `-` `*` `/` ですが、冪乗（累乗）演算子はプログラミング言語によって様々です。Ruby や Python などでは `**` ですが、BASIC では `^` ですし、そもそも C や ES2016 以前の JavaScript など冪乗演算子が無い言語もよくあります。

参考：[べき乗演算子 | 数値型 | プログラミング言語の比較 | hydroculのメモ](https://hydrocul.github.io/wiki/programming_languages_diff/number/pow.html)

さて、一部界隈では冪乗を「↑」で表します。つまり $2 \uparrow 4=2^4=16$ です。というわけで Ruby でも「↑」で冪乗を計算できるようにしましょう。

とはいえ、いくら Ruby が自由度の高いプログラミング言語だと言っても、残念ながら自分で新たな演算子を作ることはできません。[^1] というわけで、「2 ↑ 4」と書きたいところを `2.↑ 4` のようにドットが入る点はご容赦ください。

[^1]: できません…よね？やり方があれば是非ともコメントください。

```ruby
class Integer
  def ↑(n)
    self ** n
  end
end

puts 2.↑ 4 # => 16 (2 の 4 乗)
```

Ruby は 非 Ascii 文字もメソッド名に使えるので、こういうことができますね。[^2]

[^2]: このように組み込みクラスを拡張する場合は Refinement を使うのがマナーですが、このあと使うことになる `method_missing` は Refinement では使えないので、直接 Integer を拡張してしまいます。

Ruby 3.0 では endless メソッド定義を使って以下のようにも書けます。

```ruby
  def ↑(n) = self ** n
```

# ↑↑ でテトレーション

さて、冪乗とは乗算の繰り返しでした。

```math
2 \uparrow 4 = 2^4 = \underbrace{2 \times 2 \times 2 \times 2}_{4つの2}
```

そうすると、「冪乗の繰り返し」という演算を考えたくなってきます。一部界隈ではそれを「テトレーション」と呼び、「↑↑」で表します。

```math
2 \uparrow\uparrow 4 = \underbrace{2 \uparrow 2 \uparrow 2 \uparrow 2}_{4つの2} = 2^{2^{2^2}} = 2^{2^4} = 2^{16} = 65536
```

というわけで `2.↑↑ 4` でテトレーションを計算できるようにしたいと思います。それだけが目的なら、このように書けばいいでしょう。

```ruby
  def ↑↑(n)
    Array.new(n, self).inject{ |result, item| item.↑ result }
  end
```

なお、`Array.new(n, self).inject(:↑)` と書きたいところですが、これではうまく行きません。冪乗は右結合なので $3 \uparrow 3 \uparrow 3 = 3 \uparrow (3 \uparrow 3) = 3 \uparrow 27 = 7625597484987$ となるはずなのですが、`Array.new(n, self).inject(:↑)` だと計算順序上 $(3 \uparrow 3) \uparrow 3 = 27 \uparrow 3 = 19683$ の意味になってしまいます。

# テトレーションのその先へ

さて、冪乗の繰り返しを行うテトレーションという演算を定義しました。そうすると、テトレーションの繰り返しという演算も定義したくなります。それは「ペンテーション」と呼ばれ、「↑↑↑」で表します。つまり、

```math
2 \uparrow\uparrow\uparrow 4 = \underbrace{2 \uparrow\uparrow 2 \uparrow\uparrow 2 \uparrow\uparrow 2}_{4つの2}
```

ということですね。

ペンテーションをただ行うプログラムを書くなら、上のコードをコピペして少しだけいじって

```ruby
  def ↑↑↑(n)
    Array.new(n, self).inject{ |result, item| item.↑↑ result }
  end
```

とすれば良いでしょう。

ですが恐らくご想像のとおり、ペンテーションのさらに先「↑↑↑↑」（ヘキテーション）があります。さらに「↑↑↑↑↑」「↑↑↑↑↑↑」…と無限に矢印を伸ばしていくことができます。

先の方法で一つ上の演算をほぼコピペで定義していくことはできますが、「↑↑…（100個の↑）…↑↑」まで定義するのはさすがに手間です。こうした場合に考えられる方法として、`define_method` を利用した動的なメソッド定義があります。

```ruby
class Integer
  2.upto(100) do |i|
    define_method("↑" * i) do |n|
      operator = "↑" * (i - 1)
      Array.new(n, self).inject{ |result, item| item.send(operator, result) }
    end
  end
end
```

こうすれば簡単に「↑↑」から「↑↑…（100個の↑）…↑↑」まで定義できます。

しかし、こうして「↑↑…（100個の↑）…↑↑」までのメソッドを定義したところで、「↑↑…（101個の↑）…↑↑」が必要な機会があるかもしれません。[^3] たとえいくつの「↑↑…↑↑」が必要になっても、プログラムを書き換えることなく対応できるようにしたいものです。

[^3]: たぶんありません。

そこで、Ruby 黒魔術の王道 `method_missing` の出番です。[^4]

[^4]: [公式ドキュメント](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html)にある通り、`method_missing` を定義する際は `respond_to_missing?` も定義しましょう。

```ruby
class Integer
  def ↑(n)
    self ** n
  end

  def method_missing(name, *args)
    return super if name.match?(/[^↑]/)
    unless args.size == 1
      raise ArgumentError,
            "wrong number of arguments (given #{args.size}, expected 1)"
    end

    operator = name[0..-2]
    n = args[0]
    Array.new(n, self).inject do |result, item|
      item.send(operator, result)
    end
  end

  def respond_to_missing?(symbol, include_private)
    symbol.match?(/[^↑]/) ? super : true
  end
end
```

これでテトレーションだろうがペンテーションだろうがヘキテーションだろうが「↑↑…（1000個の↑）…↑↑」だろうが対応できます！

じゃあ、まずは軽く 3↑↑↑3 でも計算してみますかね！

```
> puts 3.↑↑↑ 3

bignum too big to convert into `long' (RangeError)
```

Oh...[^5]

[^5]: この数はトリトリと呼ばれ、全宇宙の粒子をメモリとして使ったとしても到底表せないような数です。参考：[仮面ライダービルドの数式 第16話 テトレーション](https://qiita.com/izmktr/items/8b0ff6234b6e5c9128f0)

# グラハム数を求めるプログラムを書く

さてせっかく矢印表記を定義したので、ギネスに載った巨大数として有名な[グラハム数](https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%A9%E3%83%8F%E3%83%A0%E6%95%B0)を求めるプログラムを書いてみましょう。もちろん 3↑↑↑3 よりさらに大きな数なので実際に求めることはできませんが、計算手順をプログラムとして表すだけなら可能です。

まず、俗にグラハム関数と呼ばれる関数 $g(n) = 3 \underbrace{\uparrow\uparrow\cdots\uparrow\uparrow}_{n個の\uparrow} 3$ を定義します。

```ruby
def g(n)
  3.send("↑" * n, 3)
end
```

method_missing を使って矢印表記を定義したことで、このようにグラハム関数も簡単に表せます。

グラハム数はグラハム関数を使って $G = \underbrace{g(g( \cdots g(g(4)) \cdots ))}_{64個の g( )}$ と表されますが、このような同じ関数の合成を $g^{64}(4)$ と冪乗みたいに書く場合があります。

だとすれば、Ruby においては同じ関数の合成を冪乗演算子 `**` を使って `method ** 64` みたいに書けるべきではないでしょうか。

```ruby
class Method
  def **(times)
    Array.new(times, self).inject(:>>)
  end
end
```

これでグラハム数を求めるプログラムの準備ができました。

```ruby
g64 = method(:g) ** 64
puts g64.call(4)
```

実に簡潔にグラハム数を求めるプログラムが書けました！

# 最後に

Ruby の柔軟さは数学と相性が良いと思うので、数学屋の皆様は Ruby を使ってみてはいかがでしょうか。