型付きのJSONシリアライザでみる、マクロの使い方

オブジェクトをJSONに変換するとき、そのオブジェクトのいくつかの変数をJSONに含めたくない場合があります。逆にJSONをオブジェクトに変換するとき、余計な変数をオブジェクトに含めたくない場合があります。

このような場合に、Typedefで読み書きを行う変数を指定できたら、とても便利だと思いませんか？

例えば、

```hx
typedef User = {
    id : Int,
    name : String,
}
```

と宣言すれば、整数型のidと文字列型のnameのみを変数にもつJSONをあつかえるということです。

そういうライブラリを作ってみました。

[TypePacker - Github](https://github.com/shohei909/typepacker)

# 使い方

ライブラリは以下のコマンドで入手できます。

```
haxelib install typepacker
```

そして、以下のように利用します。

```hx
import shohei909.typepacker.JsonPacker;
import shohei909.typepacker.TypePacker;

class Main 
{
	static function main() 
	{
        var data = new SampleClass();
        var jsonString = JsonPacker.print(TypePacker.toTypeInfomation(SampleDef), data);
		trace(jsonString); // {"i":10,"str":"test"}
        
        var string = '{"i":10,"str":"test", "f":0.1}';
        var object = JsonPacker.parse(TypePacker.toTypeInfomation(SampleDef), string);
		trace(object); // { i => 10, str => test }
	}
}

class SampleClass {
    public var i = 10;
    public var str = "test";
    public var f = 0.1;
}

typedef SampleDef = {
    i : Int,
    str : String,
}
```

parseとprintをする際にtypedefを指定すると、そのtypedefの型情報通りにjsonのparseとprintをやってみます。

# 型付きのJSONシリアライザで見る、マクロの使い方

このライブラリでは、型情報をマクロで読み込んでおいて実行時に利用できる形で保存しておくことで型付きのシリアライザを実現しています。

このクラスでは、マクロを使う上とき特有の手法をいくつか使っているので、この記事ではこれらの手法を紹介していきます。[TypePackerクラス](https://github.com/shohei909/TypePacker/blob/master/lib/shohei909/typepacker/TypePacker.hx)のコードを見ながら読んでください。

## ```import haxe.macro.Type in MacroType```

まず、import文に注目してみます

```hx
import haxe.crypto.Base64;
import haxe.Resource;
import haxe.Unserializer;
import haxe.io.Bytes;
#if macro
import haxe.Serializer;
import haxe.macro.Compiler;
import haxe.macro.TypeTools;
import haxe.macro.ComplexTypeTools;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type in MacroType;
#end
```

ほとんどはただのimport文ですが、1つだけあまり見慣れないインポート文があります。```import A in B```は、AをBという名前でインポートするという文法です。([Import Aliasについて](http://old.haxe.org/manual/modules#import-alias))

Haxeの標準ライブラリには、トップレベルにも別のType型があるので、普通にインポートするとこちらが優先されてしまします。([型の優先順位について](http://old.haxe.org/ref/packages#type-lookup))

なので、これを避けるため別名でのインポートを使用しています。

## ```#if macro``` ```#end```

マクロからのみで使用されるフィールドやimportは```#if macro``` ```#end```の[条件付きコンパイル](http://haxe.org/manual/lf-condition-compilation.html)を使うことで、実行時のプログラムに含まれないようにすることができます。

これによりマクロのみで利用可能な関数を呼び出したり、プログラムのサイズを減らしたりできます。

## 式マクロ(Expression Macro)を知る

Haxeのマクロには、コンパイルオプションから呼び出す初期化マクロ(Initialize Macro)、クラスフィールドを生成するビルドマクロ(Build Macro)、式を生成する式マクロ(Expression Macro)の3種類があります。([マクロについて](http://haxe.org/manual/macro.html))


``` hx
macro static function toTypeInfomation(e:Expr) : Expr {
```

の関数のマクロが、式マクロです。

式をインスタンスとして受け取って、式のインスタンスを返すことでマクロを呼び出した位置に、任意のHaxeの式を挿入することを可能にします。

## 型の具体化(Type Reification)

```hx
var infoType:ComplexType = macro : shohei909.typepacker.PortableTypeInfomation<$complexType>;
```

で使用している```macro : package.Type```文法は、ComplexType型のオブジェクトを生成する特殊な記法です。([Type Reification](http://haxe.org/manual/macro-reification-type.html))


## 式の具体化(Expression Reification)

toTypeInfomationの戻り値で使用している

```hx
macro TypePacker.resolveType($v{name})
```

も上記の型の具体化に近い文法です。```macro expression``` の文法で、Expr型のオブジェクトを生成しています。([Expression Reification](http://haxe.org/manual/macro-reification-expression.html))


これらのReificationですが、文法を覚えるのはちょっと難しいです。ですが、Reificationはただの省略記法なので、面倒であれば使わなくて良い機能です。Reificationを使って
かける内容は、基本的にReificationを使わなくても書くことができます。

たとえば、上記の2つは以下のように書き換えることができます。

```hx
var infoType:ComplexType = ComplexType.TPath({
    pack : ["shohei909", "typepacker"],
    name : "PortableTypeInfomation",
    params : [TPType(complexType)],
});
```

```hx
{
    expr : ECall(
        {
            expr : EField(
                {
                    expr : EConst(CIdent("TypePacker")),
                    pos : Context.currentPos()
                },
                "resolveType"
            ),
            pos : Context.currentPos()
        },
        [
            Context.makeExpr(
                name,
                Context.currentPos()
            )
        ]
    ),
    pos : Context.currentPos()
}
```

Reificationの使い方がわからなければ、それを避けてることもできます。

## ```Context.follow(t:Type, once:Bool = null)```

tがtypedefだった場合に参照している型をたどります。onceがtrueだと1つ、falseだったら最後までたどります。([Contextクラス](http://api.haxe.org//haxe/macro/Context.html))

## ```Context.error(msg:String, pos:Position)```

コンパイルエラーを発生させます。errorの他に、warnningという関数もあります。

## ```Context.addResource(name:String, data:Bytes)```

```hx
Context.addResource(getResourceIdentifer(name), bytes);
```

の関数でバイナリデータをプログラムに埋め込んでいます。

この関数の埋め込みはコンパイルオプションの```-resource```コンパイルオプションのファイル埋め込みと同じです。([リソースについて](http://haxe.org/manual/cr-resources.html))

実行時に、[haxe.Resourceクラス](http://api.haxe.org//haxe/Resource.html)の`getBytes` 関数または `getString` 関数から利用できます。

## haxe.Serializerを使う
マクロ側で[haxe.Serializer](http://haxe.org/manual/std-serialization.html)でシリアル化したオブジェクトをResourceとして埋め込んでおけば、実行時に逆シリアル化することで、そのオブジェクトを利用することができます。

# マクロを学ぶ方法について

いくつかのマクロを扱う上での便利な文法や手法を紹介しましたが、マクロでできることはこれだけではありません。Haxeのマクロについて学習すれば、もっとたくさんのことができるようになります。

Haxeのマクロの資料は少なく学びづらいですが、[新しいHaxeマニュアルのマクロの節](http://haxe.org/manual/std-serialization.html)はかなり充実したの内容になったでマクロを使いたい人はこれを読んでみることが良いとおもいます。

ただ、Haxeの式マクロを使いこなす近道は、実際に[HaxeのExprのソースコード](https://github.com/HaxeFoundation/haxe/blob/development/std/haxe/macro/Expr.hx)を見ながら、以下のような関数でHaxeの式がどんなオブジェクトに変換されているのかを出力してみて、自分でその式を自分で生成してみることだと思います。

```hx
macro public static function test(e:Expr):Expr {
	trace(e);
	return macro null;
}
```


今回はここまで。明日のHaxe Advent Calendarは、@tksyさんです。