Erlangとlists:zf/2

普段はEmacs + Distelで開発しているのですが、ErlIDEって最近どうなんだろうと思って久しぶりに触ってみました。
初回はかなり待たされましたが、その後は快適です。補完も効くし、F3(Open Declaration)もきちんと効いて快適です。UTF-8での保存もサポートするようになったようです。以前はだめだったんですが風向きが変わったのでしょうか。

補完が効くので面白がっていろいろ眺めていたら、lists:zf/2というのを発見しました。説明がまったくありません。F3で飛んでみると「The name zf is a joke!」なるコメントが。実装を見る限りだと、mapとfilterを合わせたようなものみたいですね。以下とても作為的な例ですが、FizzBuzzしつつ7の倍数を省くという処理を書いてみました。

3> lists:zf(
	  fun(X) when X rem 15 =:= 0 ->
			  {true, "FizzBuzz"};
		 (X) when X rem 5 =:= 0 ->
			  {true, "Buzz"};
		 (X) when X rem 3 =:= 0 ->
			  {true, "Fizz"};
		 (X) when X rem 7 =:= 0 ->
			  false;
		 (X) ->
			  true
	  end, lists:seq(1,100)).
[1,2,"Fizz",4,"Buzz","Fizz",8,"Fizz","Buzz",11,"Fizz",13,
 "FizzBuzz",16,17,"Fizz",19,"Buzz","Fizz",22,23,"Fizz",
 "Buzz",26,"Fizz",29,"FizzBuzz",31,32|...]
4> 

何がジョークなのかいまひとつ分からないのですが...

以前の記事で、最初に見つかったところで止まるループを作りたい...つまり以下のようなfoldl/3があったらいいなあと思ったのですが、

foldl2(_F,Acc,[]) -> Acc;
foldl2(F,Acc,[H|TL]) when is_function(F, 2)->
	N = F(H, Acc),
	case N of
		stop -> N;
		{stop, M} -> M;
		N -> foldl2(F, N, TL)
	end.

今回のlists:zf/2でも似たようなことができそうですね。roman_to_arabicのcase文を置き換えてみます。

(オリジナル)
    case lists:foldl(fun({DD, DN} ,Acc)->
			case Acc of
			    [] ->
				case string:str(S, DD) of
				    1 ->
					{DN, string:len(DD)};
				    _Else ->
					Acc
				end;
			    Acc-> Acc
			end end, [], D) of
	{X, Len} ->
	    roman_to_arabic(string:substr(S, Len + 1), Res + X);
	[] ->
	    error
    end.

(lists:zf/2)
    case lists:zf(
	   fun({DD, DN}) ->
		   case string:str(S, DD) of
		       1 ->
			   {true, {DN, string:len(DD)}};
		       _ -> false
		   end
	   end, D) of
	[] ->
	    error;
	[{X, Len}|_] ->
	    roman_to_arabic(string:substr(S, Len + 1), Res + X)
    end.

しかしまあ、よく考えてみるとlists:dropwhile/2で十分ですね...

    case lists:dropwhile(
	   fun({DD, _DN}) -> string:str(S, DD) =/= 1 end, D) of
	[] ->
	    error;
	[{DD, DN}|_] ->
	    X = DN,
	    Len = string:len(DD),
	    roman_to_arabic(string:substr(S, Len + 1), Res + X)
    end.

なお、途中に挙げた foldl2を使うとこのようになります。

    case foldl2(
	   fun({DD, DN} ,Acc)->
		   case string:str(S, DD) of
		       1 ->
			   {stop, {DN, string:len(DD)}};
		       _ ->
			   Acc
		   end
	   end, [], D) of
	{X, Len} ->
	    roman_to_arabic(string:substr(S, Len + 1), Res + X);
	[] ->
	    error
    end.

この処理ではAccをまともに使ってないので実用性がいまひとつ見づらいですが、処理を途中で打ち切ることができるので便利な場面もあるのかなあ...と思いました。そういうのがすでにあったら恥ずかしいですが。