defmodule FightingSpiritMacro do
defmacro shout(messages) do
quote do
unquote(messages)
|> Enum.each(& IO.puts("<b><font color=\"red\">$\\huge{#{&1}}$</font></b>"))
end
end
end
require FightingSpiritMacro
messages = [
"元氣ですかーーーッ!!!",
"元氣があればなんでもできる!",
"",
"闘魂とは己に打ち克つこと。",
"そして闘いを通じて己の魂を磨いていく",
"ことだと思います。"
]
FightingSpiritMacro.shout(messages)
$\huge{元氣ですかーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$
$\huge{}$
$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います。}$
はじめに
Elixir 1.17で「過激な」メタプログラミングに挑戦してみませんか?Elixirには、マクロを使ってコードを動的に生成するという強力な機能が備わっています。このマクロ、使い方次第でコードが極めてエレガントに、そして強力に変化します。ただし、慎重に使わないと「過激すぎる」コードになりがちです。まさに「過激な」プロレスです。ストロングスタイルです!
ここでは、Elixirにおけるメタプログラミングの基礎と、マクロでのコード生成方法をシンプルに紹介します。まさに「猪木イズム」で挑むべきテーマです!
メタプログラミングとは?
メタプログラミングとは、コードを生成するためのコードを書くことです。Elixirでは、この目的のためにマクロを利用します。マクロを用いると、コードの繰り返しや冗長性を一気に減らすことができ、柔軟なプログラムが実現します。しかし、その柔軟性ゆえに、プロジェクト全体の可読性が落ちる危険性も伴います。
マクロの基本
まず、シンプルなマクロの基本から見ていきましょう。マクロを定義するには、defmacro
を使います。また、マクロの定義にはquote
とunquote
が鍵となります。
defmodule FightingMacro do
defmacro shout do
quote do
IO.puts "元氣ですかーーーッ!!!"
end
end
end
上記の例では、shout
というマクロを定義しています。このマクロを呼び出すと、まさに「元氣ですかーーーッ!!!」と叫ぶコードが生成されます。
使用例
マクロは通常の関数と同じように使用できますが、呼び出すと「コード自体」が展開されます。
require FightingMacro
FightingMacro.shout() # => 元氣ですかーーーッ!!!
過激なマクロの活用例
まだちっともよさが伝わらないと思いますので、活用例を示します。
マクロの力を過激に発揮させる例として、複数のデータ型に対応した「共通処理」を動的に生成する方法を見てみましょう。例えば、ログを出力するマクロを使うと、どの型のデータでも一貫したログを出力できます。
defmodule LoggerMacro do
defmacro log(value) do
quote do
IO.puts "闘魂注入: #{inspect(unquote(value))}"
end
end
end
require LoggerMacro
LoggerMacro.log("123da-") # => 闘魂注入: "123da-"
LoggerMacro.log(123) # => 闘魂注入: 123
LoggerMacro.log(:"strong style") # => 闘魂注入: :"strong style"
LoggerMacro
を使わない場合のコードも示しておきます。闘魂注入
をタイプする回数に差がでます。この数が多くなればなるほど、コーディング効率の面で差がでるというわけです。マクロを使うとたったの1回ですみます。
IO.puts "闘魂注入: #{inspect("123da-")}"
IO.puts "闘魂注入: #{inspect(123)}"
IO.puts "闘魂注入: #{inspect(:"strong style")}"
マクロを使う際の注意点
マクロは強力なツールですが、「過激」に使いすぎるとコードが複雑になりがちです。以下のポイントに注意して活用しましょう:
- シンプルに保つ:メンテナンスが難しくなるため、過度なロジックをマクロに詰め込まない。
- 使いどころを見極める:繰り返し処理や共通ロジックに適した部分のみで利用し、細かい処理には通常の関数を使う。
- 可読性を重視:他の開発者が理解しやすい形でマクロを設計する。
上記は一般的なマクロを使う際の注意点をまとめています。
公式では以下のページにまとまっています。
Meta-programming anti-patterns
どうやって実行するんだー!? バカヤロー!って方へ
GitHubのアカウントをお持ちの方へお手軽な方法を示しておきます。
PhoenixアプリケーションをGitHub Codespaces上で開発する方法で紹介したGitHub Codespacesを使うという方法です。
記事中で紹介しているphx_devcontainerを使うと、ElixirがインストールされたUbuntuコンテナが立ち上がるので、Ubuntu上で直接開発をしているかのように操作ができます。iex
コマンドでREPL(Read-Eval-Print Loop)が立ち上がるのでこの記事のコード例をぜひ試してください。
そして気づきましたか。もっともらしく書いていますが、この記事で挙げたLoggerMacro
は過激すぎます。単なる関数で間に合います。
defmodule :闘魂ロガー do
def log(value) do
IO.puts "闘魂注入: #{inspect(value)}"
end
end
:闘魂ロガー.log("123da-")
:闘魂ロガー.log(123)
:闘魂ロガー.log(:"strong style")
ムフフ。
虚と実が、入り交じるのがプロレスです。
まとめ
Elixirのマクロを駆使することで、コードに「過激な」エレガンスと効率を注入できます。とはいえ、猪木イズムを忘れず、慎重かつ大胆に挑戦してください!この過激なツールを使いこなすことで、Elixirの可能性をさらに広げ、闘魂をこめたコードが生まれるでしょう。
$\huge{迷わず使えよ、使えばわかるさ!}$