Nemerle の自動実装系のマクロ


Nemerle の自動実装系のマクロをいくつか紹介します。

Accessor マクロ

using Nemerle.Utility;

public class Foo
{
   [Accessor(DoubleValue)]
   private d : double;
   [Accessor(flags = WantSetter)]
   private mutable intValue : int;
}

def foo = Foo();
_ = foo.DoubleValue;
_ = foo.IntValue;
foo.I = 10;

自動プロパティがプロパティに対応するフィールド (バッキングフィールド) をコンパイラーが生成するのに対して, Accessor マクロは逆にフィールドから対応するプロパティを生成します。プロパティ名は指定することもできるし,コンパイラーに任せることもできます[A]。アクセシビリティを指定したりカスタム属性を指定したりもできます。

自動プロパティは setter が必須なので,プロパティの値は変更される可能性があります。そのため通常は setter のアクセシビリティを private に設定しますが,依然として自クラス内からの変更を避けられません。

一方 Accessor マクロを用いれば,フィールドを mutable に指定しない限り,自クラス内からも変更されないことが保障されます[B]

FlagAccessor マクロ

Accessor マクロでフラグ列挙体に特化したものが FlagAccessor マクロです。このマクロはフラグが立っているかどうかを bool 値で判別するようなプロパティを生成します。

using System;
using Nemerle.Extensions;
using Nemerle.Utility;

public class Student
{
   [Flags]
   private enum Status
   {
      | MidTermExamPassed = 0b1
      | ReportSubmitted = 0b10
      | FinalTermExamPassed = 0b100
   }

   [FlagAccessor(MidTermExamPassed, ReportSubmitted, FinalTermExamPassed, flags = WantSetter)]
   private mutable status : Status;
}

def giveCredit(student)
{
   student.FinalTermExamPassed && (student.MidTermExamPassed || student.ReportSubmitted);
}
def student = Student() <- {
   MidTermExamPassed = false;
   ReportSubmitted = true;
   FinalTermExamPassed = true;
};
def credit = giveCredit(student);  // True

Alias マクロ

メソッドのエイリアスを作成します。

using System.Globalization;
using Nemerle;

[Record]
public struct JapaneseCurrency
{
   private value : int;

   [Alias(Yen, AsString(), GetStringAlias(c))]
   public static GetString(c : JapaneseCurrency) : string
   {
      c.value.ToString("C", CultureInfo.GetCultureInfo("ja-JP"));
   }
}

def c = JapaneseCurrency(1000);
c.Yen |> System.Console.WriteLine;  // \1000
c.AsString() |> System.Console.WriteLine;  // \1000
JapaneseCurrency.GetStringAlias(c) |> System.Console.WriteLine;  // \1000

ここで AsString() の代わりに ToString() と書いて ToString のオーバーライドができたら便利な気がしますが,オーバーライドは解決できないみたいです[C]。また,アクセシビリティも元のメソッドのアクセシビリティをそのまま引き継ぎます。

InheritConstructors マクロ

基底クラスのコンストラクターと同じ引数を取るようなコンストラクターを生成します。

using System;
using Nemerle;

[InheritConstructors]
public class InsaneArgumentException : ArgumentException
{
}

これは次のように展開されます。

public class InsaneArgumentException : ArgumentException
{
   public this()
   {
   }

   public this(message : string)
   {
      base(message);
   }

   public this(message : string, innerException : Exception)
   {
      base(message, innerException);
   }

   public this(message : string, paramName : string, innerException : Exception)
   {
      base(message, paramName, innerException);
   }

   public this(message : string, paramName : string)
   {
      base(message, paramName);
   }

   public this(info : SerializationInfo, context : StreamingContext)
   {
      base(info, context);
   }
}

最後のシリアル化関連のコンストラクターを見れば気づくように,基底クラスでは protected であるコンストラクターのアクセシビリティが public になっています。このマクロは private を除くすべてのコンストラクターのアクセシビリティを public にしてしまいます。

デザインパターンマクロ

デザインパターンをマクロで実装します。 Wiki の情報が古いのでそのまま使えないものもあります。

単純なので Singleton パターンを紹介します。

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Nemerle.DesignPatterns;
using Nemerle.Utility;

[Singleton(Public)]
public class Counter
{
   [Accessor(Value)]
   private mutable count : long;

   private this()
   {
      this.count = 0L;
   }

   public CountUp() : long
   {
      Interlocked.Increment(ref this.count);
   }
}

def countMany()
{
   repeat (100000)
   {
      _ = Counter.Instance.CountUp();
   }
}
def factory = TaskFactory();
def tasks = $[1..100].Select(_ => factory.StartNew(countMany)).ToArray();
Task.WaitAll(tasks);

Counter.Instance.Value |> System.Console.WriteLine;  // 10000000

単純に Instance という名前の,指定されたアクセシビリティの static プロパティを生成します。 Instance の返すインスタンスは double-checked locking により,初めて呼び出されるまで作成されません[D]。このマクロを使うメリットは,コンストラクターのアクセシビリティを private にしていないとコンパイル時に警告が出ることでしょう。

まとめ

Nemerle には手間を省いてくれる便利な自動実装のマクロが用意されています。ただし,必ずしも最善であるとは限らないので,必要に応じて自分でマクロを書いたり,あるいはマクロに頼らない実装も視野に入れるべきです。

脚注

  1. コンパイラーが自動生成するプロパティ名は,フィールド名の先頭の文字および _ の次の文字を大文字にして _ を取り除いたものです。例えば some_field は SomeField になり, otherField は OtherField になります。 []
  2. ただしリフレクションを用いれば外部からも変更可能。 []
  3. Accessor マクロはプロパティのオーバーライドができるので,技術的にはやろうと思えばできるのでしょう。 []
  4. Double-checked locking については多少の問題があるようなので,気になる場合は『Implementing the Singleton Pattern in C#』を参考にすると良いでしょう。 []