4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HaxeAdvent Calendar 2014

Day 7

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

Last updated at Posted at 2014-12-07

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

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

例えば、

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

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

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

TypePacker - Github

使い方

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

haxelib install typepacker

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

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クラスのコードを見ながら読んでください。

import haxe.macro.Type in MacroType

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

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について)

Haxeの標準ライブラリには、トップレベルにも別のType型があるので、普通にインポートするとこちらが優先されてしまします。(型の優先順位について)

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

#if macro #end

マクロからのみで使用されるフィールドやimportは#if macro #end条件付きコンパイルを使うことで、実行時のプログラムに含まれないようにすることができます。

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

式マクロ(Expression Macro)を知る

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

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

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

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

型の具体化(Type Reification)

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

で使用しているmacro : package.Type文法は、ComplexType型のオブジェクトを生成する特殊な記法です。(Type Reification)

式の具体化(Expression Reification)

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

macro TypePacker.resolveType($v{name})

も上記の型の具体化に近い文法です。macro expression の文法で、Expr型のオブジェクトを生成しています。(Expression Reification)

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

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

var infoType:ComplexType = ComplexType.TPath({
    pack : ["shohei909", "typepacker"],
    name : "PortableTypeInfomation",
    params : [TPType(complexType)],
});
{
    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クラス)

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

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

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

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

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

この関数の埋め込みはコンパイルオプションの-resourceコンパイルオプションのファイル埋め込みと同じです。(リソースについて)

実行時に、haxe.ResourceクラスgetBytes 関数または getString 関数から利用できます。

haxe.Serializerを使う

マクロ側でhaxe.Serializerでシリアル化したオブジェクトをResourceとして埋め込んでおけば、実行時に逆シリアル化することで、そのオブジェクトを利用することができます。

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

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

Haxeのマクロの資料は少なく学びづらいですが、新しいHaxeマニュアルのマクロの節はかなり充実したの内容になったでマクロを使いたい人はこれを読んでみることが良いとおもいます。

ただ、Haxeの式マクロを使いこなす近道は、実際にHaxeのExprのソースコードを見ながら、以下のような関数でHaxeの式がどんなオブジェクトに変換されているのかを出力してみて、自分でその式を自分で生成してみることだと思います。

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

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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?