8

我一直在研究一个小型数学脚本引擎(或 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。

4

2 回答 2

2

您的匹配器可以通过执行以下操作来处理无限类型:

public class Matcher
{
    private readonly Result result; // pass this in
    private readonly List<Func<Result, bool>> cases = new ...();

    public Matcher Case<T>(Action<T> action)
    {
        cases.add(result =>
        {
            if(typeof(T).IsAssignableFrom(result.Value.GetType()))
            {
                action((T)(result.Value));
                return true;
            }
            return false;
        }
        return this;
    }

    public void Do()
    {
        for each(var @case in cases)
        {
            if(@case(result)) return;
        }
    }
}

我认为您实际上并不需要列表,除非您直到以后Result才拥有列表。Value我不太了解您的对象模型,但是如果结果的类型已知,则不要使用列表并立即进行类型测试。

于 2013-07-16T18:42:41.073 回答
0

如果您总是想以相同的方式处理 DSL 结果

如果您总是希望以相同的方式处理结果(例如,如果您总是希望以相同的方式转换/调整特定类型的 DSL 对象),我建议您使用一个或多个字典来放置适配器委托,如下所示

我不确切知道您打算如何扩展您的应用程序,但在我看来,在您的情况下,每个返回类型都有一个单独的字典,并让它们都有零个或一个输入参数是一个好主意。(而不是使用多个参数,只需将要返回的 DSL 参数包装到一个对象中)。

一个例子:

public class SomeClass
{
    public IDictionary<Type, Action<object>> RegistryVoid { get; set; }
    public IDictionary<Type, Func<object, int>> RegistryInt { get; set; }

    public void SomeDlsMethod()
    {
        ...

        // Example when you need to convert your DSL data object to int:
        int value = RegistryInt[someDslObject.GetType()](someDslObject);
    }
}

如果您想以不同的方式处理 DSL 结果

如果您想在代码中以不同的方式处理 DSL 结果,我建议使用此处找到的 TypeSwith 。TypeSwitch 比使用多个 if/else 语句和强制转换更简单。使用这种方法,您可以指定使用它的逻辑,因此您不限于放入字典的逻辑。(如果您愿意,可以轻松地将 TypeSwitch 修改为扩展方法)。

例子:

public class SomeClass
{
    public void SomeDlsMethod()
    {
        TypeSwitch.Do(someDslObject,
            TypeSwitch.Case<DslObjectA>(someDslObjectA => ...),
            TypeSwitch.Case<DslObjectB>(someDslObjectB => ...),
            TypeSwitch.Default(() => ...)
        );
    }
}
于 2013-07-16T18:55:03.123 回答