メタプログラミングとは

A1c506f412e3ff8198661e0988d148de?s=47 sunnyone
April 09, 2016

 メタプログラミングとは

2016.04.09

A1c506f412e3ff8198661e0988d148de?s=128

sunnyone

April 09, 2016
Tweet

Transcript

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

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

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

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

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

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

  7. 言語別の典型的な手法

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

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

  10. 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)
  11. C++ • template • constexpr(コンパイル時関数展開/C++11以降) C++文法を解釈するため、コンパイラの恩恵を受けやすい。 C++は難しいので例は割愛

  12. Rust • マクロ – パターンに一致させ、得たトークンを展開する A Practical Intro to Macros

    in Rust 1.0 https://danielkeep.github.io/practical-intro-to-macros.html
  13. 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
  14. インタプリタ型言語 実行時にソースコードから翻訳される 実行時の処理として型の作成等を行っている プログラムからも型情報を作成/変更するための枠組 みを自然に備えていることが多い

  15. 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
  16. 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")
  17. Python • Rubyとほぼ同様だが型定義するタイミングかどうかを 意識する印象 – type関数によるクラス定義 – metaclassによるクラス定義時の処理 – hasattr

    / setattr / getattr http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Metaprogramming.html
  18. 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')
  19. コンパイラ型言語(中間コードあり) コンパイラが吐いた中間コードをランタイムが実行する ネイティブコードと比較して高レベルな情報を持ってい る 言語を直接解釈するインタプリタ言語よりは柔軟性はな いものの、何らかの枠組みがある

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

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

  22. 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するように継承したクラスを生成
  23. C# • リフレクションによる型情報読み取り • 実行時IL生成 • Expression Treeによる構文木解析+生成 • Roslynによるソースコード分析・コンパイル

  24. C#(Expression Tree)の例(LightNode) // (object[] args) => { new X().M((T1)args[0], (T2)args[1])...

    } var lambda = Expression.Lambda<Action<IDictionary<string, object>, 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);
  25. Java / C# ゴミ残る問題 クラス情報は捨てにくいので、うまくしないとゴミが 残る 生成頻度を下げる・捨てやすくする工夫が必要 (ClassLoader(Java) / AppDomain(C#)

    を意識する必 要がある)