読者です 読者をやめる 読者になる 読者になる

かとじゅんの技術日誌

技術の話をするところ

地豆の関数ライブラリとobject-manipulator

先のエントリでGoogle Collectionsを使えば、Javaでも関数型ぽくできるのですが、地豆にも関数型に特化したfunctorライブラリがあります。
まぁ、かなりマニアックなネタなので、ブクマとかスターとはつかないだろうなw そんなの気にしませんw

地豆謹製の関数ライブラリ functor

http://svn.jiemamy.org/products/leto/functor/trunk/

関数はFunctorクラスで実現できます。このあたりにテストコードがあります。かなり萌えですよねw
http://svn.jiemamy.org/products/leto/functor/trunk/src/test/java/org/jiemamy/utils/functor/core/FunctorsTest.java

例えば、以下のAdd関数はapplyで渡した引数にサフィックスを付加できます。

// AbstactFunctorはFunctor<D,S>インターフェイスを実装している。Dが戻り値、Sが引数。
public class Add extends AbstractFunctor<String, String> {
	private String suffix;
	/**
	 * インスタンスを生成する。
	 * 
	 * @param suffix 追加する文字列
	 */
	public Add(String suffix) {
		this.suffix = suffix;
	}
	public String apply(String argument) {
		return argument + suffix;
	}
}

このAdd関数のインスタンスを二つ作り、合成します。

@Test
public void testCompose() {
	Functor<String, String> c = Functors.compose(new Add("g"), new Add("f"));
	assertThat(c.apply("a"), is("afg"));
}

Functors#composeは以下な処理になっているので、addG.apply(addF.apply("a"))と同じ意味になります。

/**
	 * 2つの{@link Functor}を合成した関数を生成して返す。
	 * 
	 * <p>合成した関数{@code composite}は、{@link Functor#apply(Object)}が実行された際に
	 * まず{@code f.apply()}を実行し、その結果にさらに{@code g.apply()}を
	 * 適用した結果を返す。
	 * つまり、
	 * {@code composite.apply(x) == g.apply(f.apply(x))}
	 * となる。</p>
	 * 
	 * @param <D> 最終的な変換後の値の型
	 * @param <I> 最初の変換後の値の型
	 * @param <S> 変換前の値の型
	 * @param g {@code f}の結果をさらに変換する関数
	 * @param f 最初に変換を行う関数
	 * @return 合成した関数
	 * @throws IllegalArgumentException 引数に{@code null}が含まれる場合
	 */
	public static <D, I, S>Functor<D, S> compose(Functor<? extends D, ? super I> g, Functor<? extends I, ? super S> f) {
		if (f == null) {
			throw new IllegalArgumentException("f is null"); //$NON-NLS-1$
		}
		if (g == null) {
			throw new IllegalArgumentException("g is null"); //$NON-NLS-1$
		}
		return new Composite<D, I, S>(f, g);
	}

Functorだけでなく、既存のインスタンスを編集できるEditorってのもあります。
http://svn.jiemamy.org/products/leto/functor/trunk/src/test/java/org/jiemamy/utils/functor/core/EditorsTest.java
さらに、値を生成するGeneratorというものあります。
http://svn.jiemamy.org/products/leto/functor/trunk/src/test/java/org/jiemamy/utils/functor/core/GeneratorsTest.java

functorを使ったライブラリがobject-manipulator

このfunctorを使ったライブラリがobject-manipulatorってのがあります。これもまたマニアックですが、いわゆるDXOを行うための変換器や編集器を生成するライブラリです。少人数で技術に明るい人たちで使う分にはよいライブラリだと思います。

たとえば、JPAPerson -> Personへの変換の場合は以下のようなコードになります。

@RequireKind
public class Person{
  private String name;
  private String old;
  private List<String> address;
// setter/getter省略
}
@RequireKind
public class JPAPerson{
  private String name;
  private int old;
  private List<Integer> address;
// setter/getter省略
}

// カインドはRequireKindアノテーションが付いているBeanのメタモデルです。APTによって自動生成されます。
PersonKind personKind = new PersonKind();
JPAPersonKind jpaPersonKind = new JPAPersonKind();

Manipulator<Person, JPAPerson> manipulator = Manipulators
  .edit(personKind).using(jpaPersonKind) // JPAPersonを使ってPersonを編集する
  .that(personKind.old).is(jpaPersonKind.old.andThen(Objects.asString())) // Personのoldは文字列に変換
  .that(personKind.address).is(jpaPersonKind.address.andThen(Lists.each(Objects.asString()))) // addressは文字列のリストに変換
  .otherProperties().areCopiedFromArgument() // それ以外のプロパティはそのままコピー
  .createManipulator(personKind.generator()); // する、Manipulatorを生成する。

Person person = new Person();
person.name = "xxxx";

JPAPerson jpaPerson = new JPAPerson();
jpaPerson.name = "yyyy";
jpaPerson.old = 2;
jpaPerson.address = new ArrayList<Integer>();
jpaPerson.address.add(1);
jpaPerson.address.add(2);
jpaPerson.address.add(3);

// 既存のpersonに対してjpaPersonを使って編集
Person person2 = manipulator.edit(person, jpaPerson);
System.out.println(person2.name);
System.out.println(person2.old);
for (String address : person2.address) {
  System.out.println(address);
}

// jpaPersonを使ってPersonに変換する
Person person3 = manipulator.apply(jpaPerson);
System.out.println(person3.name);
System.out.println(person3.old);
for (String address : person3.address) {
  System.out.println(address);
}

createManipulatorするのは、Manipulatorためのファクトリの中にして、必要に応じてそのファクトリでManipulatorを生成してDXOするとよいと思います。

object-manipulatorの作者*1曰く、「こういうことするなら、Scalaのが向いてるよー」ということだったので、Scalaを始めたわけですが、最近やっと言われたことがわかるようになってきました。

*1:作者はid:ashigeruです