(defprofile lagénorhynque
:id @lagenorhynque
:reading "/laʒenɔʁɛ̃k/"
:aliases ["カマイルカ🐬"]
:languages [Clojure Haskell English français]
:interests [programming language-learning law mathematics]
:commits ["github.com/lagenorhynque/duct.module.pedestal"
"github.com/lagenorhynque/duct.module.cambium"]
:contributes ["github.com/japan-clojurians/clojure-site-ja"])
-
メタプログラミングという「魔術」
-
「魔術」を使うべきか
-
主な「魔術」の紹介
- 🔥 未定/不存在のものを呼び出す
- 🌿 既存の定義を変更する
- 💧 構文を操作する
メタプログラミング(metaprogramming)の定義(例)
メタプログラミングとは、
コードを記述するコードを記述することである。
※ ただし、(メタプログラミング的でない)プログラミングとメタプログラミングの境界はそもそも曖昧
- 実行時メタプログラミング(or 動的メタプログラミング)
- リフレクション(reflection)
- メタクラス(metaclass)の操作
- etc.
- コンパイル時メタプログラミング(or 静的メタプログラミング)
※ ただし、複数の処理フェーズに関わるメタプログラミング手法もある
- 利用側のコードの読み書きが簡単になる
- 困難/不可能を可能にする
- 動的な定義の生成/変更
- 評価の制御
- etc.
- DSL (domain-specific language)の表現力が高まる
- パフォーマンスが改善する
- コンパイル時に処理を済ませられる
- etc.
- 定義側のコードの読み書きが難しくなる
- 機能制限が生じる
- 静的解析との相性が悪くなる
- ファーストクラスの構成要素と組み合わせづらくなる
- etc.
- パフォーマンスが劣化する
- 実行時のオーバーヘッドが増える
- コンパイル時間が長くなる
- etc.
Macro Club has two rules, plus one exception. The first rule of Macro Club is Don’t Write Macros. (...) The second rule of Macro Club is Write Macros If That Is the Only Way to Encapsulate a Pattern. (...) The exception to the rule is that you can write any macro that makes life easier for your callers when compared with an equivalent function.
Programming Clojure, Third Edition
jshell> class Greeting {
...> private String name;
...> Greeting(String name) {
...> this.name = name;
...> }
...> String hello() {
...> return "Hello, " + name + "!";
...> }
...> private String bonjour() {
...> return "Bonjour, " + name + " !";
...> }
...> }
| created class Greeting
jshell> var g = new Greeting("lagénorhynque")
g ==> Greeting@377dca04
jshell> g.hello()
$3 ==> "Hello, lagénorhynque!"
jshell> g.bonjour()
| Error:
| bonjour() has private access in Greeting
| g.bonjour()
| ^-------^
jshell> Greeting.class.getDeclaredMethods()
$4 ==> Method[2] { java.lang.String Greeting.hello(), private ja
va.lang.String Greeting.bonjour() }
jshell> var mHello = Greeting.class.getDeclaredMethod("hello")
mHello ==> java.lang.String Greeting.hello()
jshell> mHello.invoke(g)
$6 ==> "Hello, lagénorhynque!"
jshell> var mBonjour = Greeting.class.getDeclaredMethod("bonjour
")
mBonjour ==> private java.lang.String Greeting.bonjour()
jshell> mBonjour.invoke(g)
| Exception java.lang.IllegalAccessException: class REPL.$JShell$23 cannot access a member of class REPL.$JShell$11$Greeting with modifiers "private"
| at Reflection.newIllegalAccessException (Reflection.java:385)
| at AccessibleObject.checkAccess (AccessibleObject.java:693)
| at Method.invoke (Method.java:556)
| at (#10:1)
jshell> mBonjour.setAccessible(true)
jshell> mBonjour.invoke(g)
$12 ==> "Bonjour, lagénorhynque !"
method_missingメソッド(Ruby)
irb(main):001:1* class Greeting
irb(main):002:2* def initialize(name)
irb(main):003:2* @name = name
irb(main):004:1* end
irb(main):005:2* def method_missing(m, *args)
irb(main):006:2* "#{m.capitalize}, #{@name}!"
irb(main):007:1* end
irb(main):008:1* end
=> :method_missing
irb(main):009:0> g = Greeting.new("lagénorhynque")
=> #<Greeting:0x00007fcb472e0198 @name="lagénorhynque">
irb(main):010:0> g.hello
=> "Hello, lagénorhynque!"
irb(main):011:0> g.bonjour
=> "Bonjour, lagénorhynque!"
- 他言語での例
- __getattr__メソッド(Python)
irb(main):001:1* class String
irb(main):002:2* def hello
irb(main):003:2* "Hello, #{self}!"
irb(main):004:1* end
irb(main):005:1* end
=> :hello
irb(main):006:0> "lagénorhynque".hello
=> "Hello, lagénorhynque!"
refinements (Ruby)
irb(main):001:1* module Greeting
irb(main):002:2* refine String do
irb(main):003:3* def hello
irb(main):004:3* "Hello, #{self}!"
irb(main):005:2* end
irb(main):006:1* end
irb(main):007:0> end
=> #<refinement:String@Greeting>
irb(main):008:1* module M
irb(main):009:1* using Greeting
irb(main):010:2* class << self
irb(main):011:3* def f(name)
irb(main):012:3* name.hello
irb(main):013:2* end
irb(main):014:1* end
irb(main):015:0> end
=> :f
irb(main):016:0> M.f("lagénorhynque")
=> "Hello, lagénorhynque!"
irb(main):017:0> "lagénorhynque".hello
Traceback (most recent call last):
4: from /Users/lagenorhynque/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
3: from /Users/lagenorhynque/.rbenv/versions/3.0.0/bin/irb:23:in `load'
2: from /Users/lagenorhynque/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
1: from (irb):17:in `<main>'
NoMethodError (undefined method `hello' for "lagénorhynque":String)
- 他言語での例
-
prototype (JavaScript)
-
implicit class (Scala)
-
特異メソッド(Ruby)
irb(main):001:0> name = "lagénorhynque"
=> "lagénorhynque"
irb(main):002:1* def name.hello
irb(main):003:1* "Hello, #{self}!"
irb(main):004:1* end
=> :hello
irb(main):005:0> name.hello
=> "Hello, lagénorhynque!"
irb(main):006:0> "lagénorhynque".hello
Traceback (most recent call last):
4: from /Users/lagenorhynque/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
3: from /Users/lagenorhynque/.rbenv/versions/3.0.0/bin/irb:23:in `load'
2: from /Users/lagenorhynque/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
1: from (irb):6:in `<main>'
NoMethodError (undefined method `hello' for "lagénorhynque":String)
- 他言語での例
-
MethodTypeでのメソッド追加(Python)
-
プロパティの操作(JavaScript)
-
メタデータによるプロトコル拡張 (Clojure)
-
マクロ(Clojure)
user=> (defmacro unless [test & body]
`(when (not ~test)
~@body))
#'user/unless
user=> (unless (= 1 2)
(println "not equal"))
not equal
nil
user=> (unless (= 1 1)
(println "not equal"))
nil
user=> (macroexpand-1
'(unless (= 1 2)
(println "not equal")))
(clojure.core/when (clojure.core/not (= 1 2)) (println "not equa
l"))
マクロ(Elixir)
iex(1)> defmodule M do
...(1)> defmacro unless(test, do: body) do
...(1)> quote do
...(1)> if !unquote(test) do
...(1)> unquote(body)
...(1)> end
...(1)> end
...(1)> end
...(1)> end
{:module, M,
<<70, 79, 82, 49, 0, 0, 5, 120, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 148,
0, 0, 0, 17, 8, 69, 108, 105, 120, 105, 114, 46, 77, 8, 95, 95, 105, 110,
102, 111, 95, 95, 10, 97, 116, 116, 114, ...>>, {:unless, 2}}
iex(2)> require M
M
iex(3)> M.unless 1 == 2 do
...(3)> IO.puts "not equal"
...(3)> end
not equal
:ok
iex(4)> M.unless 1 == 1 do
...(4)> IO.puts "not equal"
...(4)> end
nil
iex(5)> quote do
...(5)> M.unless 1 == 1 do
...(5)> IO.puts "not equal"
...(5)> end
...(5)> end |> Macro.expand_once(__ENV__) |>
...(5)> Macro.to_string |> IO.puts
if(!(1 == 1)) do
IO.puts("not equal")
end
:ok
-
メタプログラミングを「魔術」のままにせず
-
仕組みに対する理解を深めよう
-
適切に恐れ、適切に活用しよう
-