Slide 1

Slide 1 text

メタプログラミングとは @_sunnyone

Slide 2

Slide 2 text

目次 ● メタプログラミングとは ● 言語別の典型的な手法 – コンパイラ型言語(ネイティブコード直接生成タイプ) – インタプリタ言語 – コンパイラ型言語(中間コードあり)

Slide 3

Slide 3 text

メタプログラミングとは? 「ロジックを直接コーディングするのではなく、 あるパターンをもったロジックを生成する 高位ロジックによってプログラミングを行う方法」 (Wikipedia)

Slide 4

Slide 4 text

メタプログラミングとは? プログラミング コンピュータへの指示を決まった方法により記述する行為 メタプログラミング meta- 高次の コンピュータに指示する方法を記述する行為

Slide 5

Slide 5 text

メタプログラミングの目的 言語のもつ直接的な表現では繰り返しが多かったり、 記述者の意図が伝わりにくいケース 記述方法そのものを用意 作成者の意図する指示を直接的に記述可能

Slide 6

Slide 6 text

メタプログラミングが使われる箇所 ● DI コンテナ ● O/R マッパー など いわゆる「フレームワーク」と呼ばれるものには しばしば使われる。

Slide 7

Slide 7 text

言語別の典型的な手法

Slide 8

Slide 8 text

コンパイラ型言語(ネイティブコード直接生成タイプ) 中間コードを持たず、ソースコードの記述から直接ネ イティブコードを出力するタイプ 実行時の型情報の読み取り/書き換えや、 実行時のコード翻訳の仕組みが薄いことが多い コンパイル時にマクロ展開することが多い

Slide 9

Slide 9 text

C言語 ● Cプリプロセッサによるマクロ展開 – ほぼほぼ単純な文字列展開のため、名前の衝突など の間違いを起こしやすい。

Slide 10

Slide 10 text

C言語の例(glib) #define G_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init) { \ const GInterfaceInfo g_implement_interface_info = { \ (GInterfaceInitFunc) iface_init, NULL, NULL \ }; \ g_type_add_interface_static (g_define_type_id, TYPE_IFACE, &g_implement_interface_info); \ } G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_entry_editable_init) glib の GLib Object SystemはマクロとC言語の仕様を駆使して、 C言語上にクラス/インタフェース構造を表現できるようにしている。 https://developer.gnome.org/gobject/stable/gtype-instantiable-classed.html gtype.h (https://github.com/GNOME/glib/blob/master/gobject/gtype.h)

Slide 11

Slide 11 text

C++ ● template ● constexpr(コンパイル時関数展開/C++11以降) C++文法を解釈するため、コンパイラの恩恵を受けやすい。 C++は難しいので例は割愛

Slide 12

Slide 12 text

Rust ● マクロ – パターンに一致させ、得たトークンを展開する A Practical Intro to Macros in Rust 1.0 https://danielkeep.github.io/practical-intro-to-macros.html

Slide 13

Slide 13 text

Rustの例 macro_rules! try { ($expr:expr) => (match $expr { $crate::result::Result::Ok(val) => val, $crate::result::Result::Err(err) => { return $crate::result::Result::Err($crate::convert::From::from(err)) } }) } fn write_to_file_using_try() -> Result<(), io::Error> { let mut file = try!(File::create("my_best_friends.txt")); try!(file.write_all(b"This is a list of my best friends.")); println!("I wrote to the file"); Ok(()) } 標準のtry! マクロ (https://github.com/rust-lang/rust/blob/master/src/libcore/macros.rs) このマクロの意味はRust bookのエラーハンドリングを参照 https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/error-handling.html

Slide 14

Slide 14 text

インタプリタ型言語 実行時にソースコードから翻訳される 実行時の処理として型の作成等を行っている プログラムからも型情報を作成/変更するための枠組 みを自然に備えていることが多い

Slide 15

Slide 15 text

Ruby ● 普段のメソッドなどの定義そのものがmoduleやclassなどのス コープに入って定義を追加する処理 ● コードが動作時に行われることを明示的に実行する – NewClass = Class.new do ... end – define_method :new_method do ... end – send によるメソッド呼び出し – method_missing / respond_to – class_eval / instance_eval / module_eval

Slide 16

Slide 16 text

Rubyの例(ActiveRecord) def method_missing(name, *arguments, &block) match = Method.match(self, name) if match && match.valid? match.define send(name, *arguments, &block) else super end end dynamic_matchers.rb (https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/dynamic_matchers.rb) 定義する def define model.class_eval <<-CODE, __FILE__, __LINE__ + 1 def self.#{name}(#{signature}) #{body} end CODE end 呼び出す Client.find_by_first_name("HogeHoge")

Slide 17

Slide 17 text

Python ● Rubyとほぼ同様だが型定義するタイミングかどうかを 意識する印象 – type関数によるクラス定義 – metaclassによるクラス定義時の処理 – hasattr / setattr / getattr http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Metaprogramming.html

Slide 18

Slide 18 text

Pythonの例(boto3) def resource(self, service_name, region_name=None, api_version=None, (略) # Create the service resource class. cls = self.resource_factory.load_from_definition( resource_name=service_name, single_resource_json_definition=resource_model['service'], service_context=service_context ) return cls(client=client) session.py (https://github.com/boto/boto3/blob/develop/boto3/session.py) def load_from_definition(self, resource_name, single_resource_json_definition, service_context): (略) # Create the name based on the requested service and resource cls_name = resource_name if service_context.service_name == resource_name: cls_name = 'ServiceResource' cls_name = service_context.service_name + '.' + cls_name base_classes = [ServiceResource] (略) return type(str(cls_name), tuple(base_classes), attrs) s3 = boto3.resource('s3')

Slide 19

Slide 19 text

コンパイラ型言語(中間コードあり) コンパイラが吐いた中間コードをランタイムが実行する ネイティブコードと比較して高レベルな情報を持ってい る 言語を直接解釈するインタプリタ言語よりは柔軟性はな いものの、何らかの枠組みがある

Slide 20

Slide 20 text

Java ● リフレクションによる型情報読み取り ● 実行時バイトコード生成 ● バイトコード読み取りによる構文木解析 ● Annotation Processingによるコンパイル時コード生 成

Slide 21

Slide 21 text

Java ● 実行時コード生成については、定番のライブラリが存 在 – Javassist – CGLib

Slide 22

Slide 22 text

Javaの例(ModelMapper) static Class proxyClassFor(Class type, Errors errors) throws ErrorsException { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(type); enhancer.setUseFactory(true); enhancer.setUseCache(true); enhancer.setNamingPolicy(NAMING_POLICY); enhancer.setCallbackFilter(METHOD_FILTER); enhancer.setCallbackTypes(new Class[] { MethodInterceptor.class, NoOp.class }); try { return enhancer.createClass(); } catch (Throwable t) { throw errors.errorEnhancingClass(type, t).toException(); } } ProxyFactory.java (https://github.com/jhalterman/modelmapper/blob/master/core/src/main/java/org/modelmapper/internal/ProxyFactory.java) private static void setCallbacks(Object enhanced, MethodInterceptor interceptor) throws Exception { Field callback1 = enhanced.getClass().getDeclaredField("CGLIB$CALLBACK_0"); callback1.setAccessible(true); callback1.set(enhanced, interceptor); 呼び出し毎にinvoke()してくれる interceptするように継承したクラスを生成

Slide 23

Slide 23 text

C# ● リフレクションによる型情報読み取り ● 実行時IL生成 ● Expression Treeによる構文木解析+生成 ● Roslynによるソースコード分析・コンパイル

Slide 24

Slide 24 text

C#(Expression Tree)の例(LightNode) // (object[] args) => { new X().M((T1)args[0], (T2)args[1])... } var lambda = Expression.Lambda, object[]>>( Expression.Call( Expression.MemberInit(Expression.New(classType), envBind), methodInfo, parameters), envArg, args); this.handlerBodyType = HandlerBodyType.Action; this.methodActionBody = lambda.Compile(); OperationHandler.cs (https://github.com/neuecc/LightNode/blob/master/Source/LightNode.Server/OperationHandler.cs) handler.methodActionBody(environment, methodParameters);

Slide 25

Slide 25 text

Java / C# ゴミ残る問題 クラス情報は捨てにくいので、うまくしないとゴミが 残る 生成頻度を下げる・捨てやすくする工夫が必要 (ClassLoader(Java) / AppDomain(C#) を意識する必 要がある)