2014年7月28日

javascript 引数の無名関数内のみで使える関数

Jaml などのHTML ジェネレータを調べてる際に気付いたことだが、javascript では、引数に渡す無名関数内のみに関数を公開する方法がある。

[Jaml のサンプルコード]

Jaml.register('sample', function(){
    div(
        h1('サンプル'),
        p('Jaml です。'),
        br()
    );
});

上記サンプルの無名関数内で呼び出している div や h1 などの関数は、外部に公開されていない(グローバル関数ではない)。
どうやって実現しているのかさっぱり分からなかったので、Jaml のソースを拝見。
div や h1 は Jaml.Template の prototype 関数で、with と eval で実現できる模様。

[実現コード]

//[定義]
var Test = {
    console : function(callback){
        var logger = new Logger();
        //・callback を文字列展開して eval 実行することで、
        //  callback の変数スコープをこの関数内に変更
        //・with を使って prototype 関数に直接アクセス
        with( logger ) eval('(' + callback.toString() + ').call(logger)');
    }
};
var Logger = function(){ this.count = 0; };
Logger.prototype = {
    log: function(message){
        console.log(message);
        this.count++;
    },
};

//[実行]
Test.console(function(){
    log('one');
    log('two');
    log(count);
});

//[結果]
// one
// two
// 2

prototype 関数でなければ with で囲む必要はない。
上記例で、Test.console 内に定義した関数なら、外部に公開されず、引数の callback 内でのみ使える関数となる。

おまけ

本題とはあんまり関係ないけど、Jaml から eval と with を除いたもの。
Jaml.register に渡す無名関数の第一引数に Jaml.Template インスタンスが渡されるので、そこから div や h1 などのメソッドを使う。

2014年7月13日

オーバーロードの比較

比較といっても大きな違いは 1 つだけ。
C# は同じ型の引数でも、値渡しと参照渡し(ref または out)で別々のメソッドとして定義できるが、VB は不可。

具体的には、C# で次はコンパイル可。

public class TestOverloadCs
{
    public static void Action(int value)
    {
        Console.WriteLine("Action(int)");
    }
    public static void Action(ref int value)
    {
        Console.WriteLine("Action(ref int)");
    }
}

VB で次はコンパイル不可。

Public Class TestOverloadVb
    Public Shared Sub Action(ByVal value As Integer) 'コンパイルエラー
        Console.WriteLine("Action(int)")
    End Sub

    Public Shared Sub Action(ByRef value As Integer)
        Console.WriteLine("Action(ref int)")
    End Sub
End Class

VB は呼び出し側で ref とか out とかのキーワードを付けないから、上記のようなオーバーロードは禁止されているのだと思われる。

また、上記の C# のオーバーロードは、VB から普通に呼べないので注意。
VB_OverloadMethodCallError
リフレクションを使うとたぶん呼べる。

VB で使用するライブラリを C# で書く場合、上記のようなオーバーロードは避けるのが無難。
C# と VB のちゃんぽんはオススメしないが・・・

参考URL

2014年7月12日

SortedSet<T> コンストラクタのバグ (.NET4~4.5)

次のような拡張メソッドを作った際に気付いたのだが、SortedSet<T> のコンストラクタ (IEnumerable<T>, IComparer<T>) に (要素 2 個以上のコレクション, null) を渡すと例外が発生する。
引数 1 個の場合や、要素 1 個のコレクションでは例外にならないから分かりにくい。

public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source, IComparer<T> comparer)
{
    if( source == null ) throw new ArgumentNullException("source");
    return new SortedSet<T>(source, comparer);
}
public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source)
{
    return source.ToSortedSet(null);
}

[検証コード]

static void Main()
{
    var test1 = new SortedSet<int>(new[]{ 1 }, null);
    Console.WriteLine(test1.Count);
    var test2 = new SortedSet<int>(new[]{ 1, 2 }, null);
    Console.WriteLine(test2.Count);
}
[結果]
1

ハンドルされていない例外: System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
   場所 System.Collections.Generic.SortedSet`1..ctor(IEnumerable`1 collection, IComparer`1 comparer)

   場所 Program.Main()

原因は、SortedSet<T> のソース (Date: August 15, 2008) を見ると明らかで、168行目の comparer に this. の付け忘れ…
ちなみに、似たようなクラスである HashSet<T> は問題なし。

バグを考慮した拡張メソッド

そのうち修正されることを期待しつつ、拡張メソッドは次のように修正。

public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source, IComparer<T> comparer)
{
    if( source == null ) throw new ArgumentNullException("source");
    return new SortedSet<T>(source, comparer ?? Comparer<T>.Default;);
}
public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source)
{
    return source.ToSortedSet(null);
}

C# 6.0 からコンストラクタの型引数を推論してくれるようになるらしいんで、こんなメソッドはいらなくなるはず。