私は、小さな数学的スクリプト エンジン (または、必要に応じて DSL) に取り組んできました。楽しみのためにそれを作る、それは深刻なことではありません。いずれにせよ、私が欲しい機能の 1 つは、タイプ セーフな方法で結果を取得する機能です。問題は、返すことができる 5 つの異なる型があることです。
Number、bool、Fun、FunN、および NamedValue。Fun と FunN の抽象基底クラスである AnyFun もあります。Fun と FunN の違いは、Fun は引数を 1 つしか取りませんが、FunN は複数の引数を取ることです。別の型を保証するのに1つの引数で十分に一般的であると考えました(間違っている可能性があります)。
現時点では、これを実現するために Result というラッパー型と Matcher というクラスを使用しています (F# や Haskell などの言語のパターン マッチングに着想を得ています)。使ってみると基本的にはこんな感じ。
engine.Eval(src).Match()
.Case((Number result) => Console.WriteLine("I am a number"))
.Case((bool result) => Console.WriteLine("I am a bool"))
.Case((Fun result) => Console.WriteLine("I am a function with one argument"))
.Case((AnyFun result) => Console.WriteLine("I am any function thats not Fun"))
.Do();
これが私の現在の実装です。硬いですけどね。新しいタイプを追加するのはかなり面倒です。
public class Result
{
public object Val { get; private set; }
private Callback<Matcher> _finishMatch { get; private set; }
public Result(Number val)
{
Val = val;
_finishMatch = (m) => m.OnNum(val);
}
public Result(bool val)
{
Val = val;
_finishMatch = (m) => m.OnBool(val);
}
... more constructors for the other result types ...
public Matcher Match()
{
return new Matcher(this);
}
// Used to match a result
public class Matcher
{
internal Callback<Number> OnNum { get; private set; }
internal Callback<bool> OnBool { get; private set; }
internal Callback<NamedValue> OnNamed { get; private set; }
internal Callback<AnyFun> OnAnyFun { get; private set; }
internal Callback<Fun> OnFun { get; private set; }
internal Callback<FunN> OnFunN { get; private set; }
internal Callback<object> OnElse { get; private set; }
private Result _result;
public Matcher(Result r)
{
OnElse = (ignored) =>
{
throw new Exception("Must add a new exception for this... but there was no case for this :P");
};
OnNum = (val) => OnElse(val);
OnBool = (val) => OnElse(val);
OnNamed = (val) => OnElse(val);
OnAnyFun = (val) => OnElse(val);
OnFun = (val) => OnAnyFun(val);
OnFunN = (val) => OnAnyFun(val);
_result = r;
}
public Matcher Case(Callback<Number> fn)
{
OnNum = fn;
return this;
}
public Matcher Case(Callback<bool> fn)
{
OnBool = fn;
return this;
}
... Case methods for the rest of the return types ...
public void Do()
{
_result._finishMatch(this);
}
}
}
もっと種類を増やしていきたいということです。関数が数値とブール値の両方を返せるようにし、Fun を Fun< T > (T は戻り値の型) に変更したいと考えています。これは実際に主な問題がある場所です。私は AnyFun、Fun、FunN を持っています。この変更を導入した後、AnyFun、Fun< Number >、Fun< bool >、FunN< Number >、FunN< bool > に対処する必要があります。それでも、一致しない関数に対して AnyFun を一致させたいと思います。このような:
engine.Eval(src).Match()
.Case((Fun<Number> result) => Console.WriteLine("I am special!!!"))
.Case((AnyFun result) => Console.WriteLine("I am a generic function"))
.Do();
新しい型の追加をより適切に処理する、より優れた実装に関する提案はありますか? または、タイプセーフな方法で結果を取得する方法について他の提案はありますか? また、すべての戻り値の型に共通の基本クラスを用意する必要がありますか (および bool の新しい型を追加する必要がありますか)。
ところで、パフォーマンスは問題ではありません。
気をつけて、カー
編集:
フィードバックを読んだ後、代わりにこのマッチャー クラスを作成しました。
public class Matcher
{
private Action _onCase;
private Result _result;
public Matcher(Result r)
{
_onCase = null;
_result = r;
}
public Matcher Case<T>(Callback<T> fn)
{
if (_result.Val is T && _onCase == null)
{
_onCase = () => fn((T)_result.Val);
}
return this;
}
public void Else(Callback<object> fn)
{
if (_onCase != null)
_onCase();
else
fn(_result.Val);
}
public void Do()
{
if (_onCase == null)
throw new Exception("Must add a new exception for this... but there was no case for this :P");
_onCase();
}
}
短いですが、ケースの順序が重要です。たとえば、この場合、Fun オプションは決して実行されません。
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
.Case((Fun result) => Console.WriteLine("I am alone"))
しかし、場所を変えるとそうなるでしょう。
.Case((Fun result) => Console.WriteLine("I am alone"))
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
それを改善することは可能ですか?私のコードに他の問題はありますか?
編集2:
解決しました:D.