2
1

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.

コメントよりもコードに語らせる (Maybe戻り値)

2
Posted at

目次

  1. はじめに
  2. 個別の型に語らせる
  3. 汎用的な型に語らせる
  4. さいごに

はじめに

 皆さんも下記のようなコメント・ドキュメント付きの関数を使ったことがあるかと思います。

java.lang.String
 * ...
 * 戻り値:
 *   このオブジェクトによって表される文字シーケンス内で指定された文字が最初に出現する位置のインデックス文字がない場合は-1
 */
public int indexOf(int ch);

 この関数の戻り値は、「位置のインデックス」という成功値と、「-1」という失敗値を同時に表現しようとしています。ここで、失敗値=-1 というのはおそらく、特に根拠のある値でなく、成功値>=0 に重ならなければ何でも良かったのだと思われます。従って、この手のIFの関数が成功したか失敗したかをチェックするためにはドキュメントを参照しなければいけません。つまり、この関数はコードに語らせられていないのです。
 コードに語らせる、とは、コメントやドキュメントを参照しなくてもわかりやすく、使いやすいコードを書くということです。
 以下では、同関数を題材にコードに語らせる例を記載します。

個別の型に語らせる

 これは戻り値を独自定義のclassのobjectにするということです。

StringUtil
class StringUtil {
	public static ResultOfIndexOf indexOf(String haystack, char needle) {
		for (int i = 0; i < haystack.length(); ++i)
			if (haystack.charAt(i) == needle)
				return new ResultOfIndexOf(i);
		return new ResultOfIndexOf();
	}
}

class ResultOfIndexOf {
	private final boolean isFound;
	private final int foundIndex;
	ResultOfIndexOf() {
		this(false, 0);
	}
	ResultOfIndexOf(int foundIndex) {
		this(true, foundIndex);
	}
	private ResultOfIndexOf(boolean isFound, int foundIndex) {
		this.isFound = isFound;
		this.foundIndex = foundIndex;
	}
	public boolean isFound() {
		return isFound;
	}
	public int foundIndex() {
		if (!isFound)
			throw new RuntimeException("index not found");
		return foundIndex;
	}
}

 これを使う側のコードは以下のようになります。(ideone)

Main
class Ideone
{
	public static void main (String[] args) throws java.lang.Exception
	{
		ResultOfIndexOf result = StringUtil.indexOf("hello, world", 'l');
		if (result.isFound())
			System.out.println(String.format("index=%d", result.foundIndex()));
	}
}

 中間の変数が邪魔なら、コールバックを渡して成功した場合のみ呼び出してもらいます。(ideone)

Main
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.function.*;

class Ideone
{
	public static void main (String[] args) throws java.lang.Exception
	{
		StringUtil.indexOf("hello, world", 'l')
			.DoIfSuccess(foundIndex -> {
				System.out.println(String.format("index=%d", foundIndex));
			});
	}
}

class StringUtil {
	public static ResultOfIndexOf indexOf(String haystack, char needle) {
		for (int i = 0; i < haystack.length(); ++i)
			if (haystack.charAt(i) == needle)
				return new ResultOfIndexOf(i);
		return new ResultOfIndexOf();
	}
}

class ResultOfIndexOf {
	private final boolean isFound;
	private final int foundIndex;
	ResultOfIndexOf() {
		this(false, 0);
	}
	ResultOfIndexOf(int foundIndex) {
		this(true, foundIndex);
	}
	private ResultOfIndexOf(boolean isFound, int foundIndex) {
		this.isFound = isFound;
		this.foundIndex = foundIndex;
	}
	public void DoIfSuccess(Consumer<Integer> postAction) {
		if (isFound && postAction != null)
			postAction.accept(foundIndex);
	}
}

汎用的な型に語らせる

 上記の例だと、関数ごとに型が増えてしまうのでGenericsなclassを作成し、Maybeと名付けて利用します。
(ideone)

Maybe
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.function.*;

class Ideone
{
	public static void main (String[] args) throws java.lang.Exception
	{
		StringUtil.indexOf("hello, world", 'l')
			.DoIfSuccess(foundIndex -> {
				System.out.println(String.format("index=%d", foundIndex));
			});
	}
}

class StringUtil {
	public static Maybe<Integer> indexOf(String haystack, char needle) {
		for (int i = 0; i < haystack.length(); ++i)
			if (haystack.charAt(i) == needle)
				return Maybe.<Integer>just(i);
		return Maybe.<Integer>nothing();
	}
}

class Maybe<ResultType> {
	public static <R> Maybe<R> nothing() {
		return new Maybe(false, null);
	}
	
	public static <R> Maybe<R> just(R resultValue) {
		return new Maybe(true, resultValue);
	}
	
	private final boolean isSucceeded;
	private final ResultType resultValue;
	
	private Maybe(boolean isSucceeded, ResultType resultValue) {
		this.isSucceeded = isSucceeded;
		this.resultValue = resultValue;
	}
	
	public void DoIfSuccess(Consumer<ResultType> postAction) {
		if (isSucceeded && postAction != null)
			postAction.accept(resultValue);
	}
}

さいごに

 個人的には Maybe 戻り値にするのが好みです。もちろん標準ライブラリにあればそれを使ったほうが良いと思います(java8ならOptional)。無ければ自作してでもコードに語らせましょう。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?