我一直在研究一个小型数学脚本引擎(或 DSL,如果您愿意的话)。让它变得有趣,这没什么大不了的。无论如何,我想要的功能之一是以类型安全的方式从中获取结果的能力。问题是它可以返回 5 种不同的类型。
Number、bool、Fun、FunN 和 NamedValue。还有 AnyFun,它是 Fun 和 FunN 的抽象基类。Fun 和 FunN 的区别在于 Fun 只接受一个参数,而 FunN 接受多个参数。认为使用一个参数来保证单独的类型已经足够普遍(可能是错误的)。
目前,我正在使用一个名为 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。