在集合中存储两种类型对象的类型安全方法

发布于 2024-11-26 22:58:08 字数 998 浏览 2 评论 0原文

我一直在实现增强的 Shunting-Yard 算法来解析算术表达式。该算法的一个方面是它维护一个Queue 和一个Stack

在我的实现中,Queue 包含ExpressionsOperators堆栈包含运算符括号

表达式括号运算符没有任何共同点可以保证它们中的任何两个具有共享接口。

方法:

  • 我当前的实现由实现 INotParanthesisExpressionOperator 组成。 OperatorParanthesis 实现 INotExpression。然后,我声明QueueStack

    我不喜欢这个实现 - 这些接口似乎是为了更清晰的算法代码而进行的黑客攻击。我还认为接口应该描述对象是什么,而不是它不是什么。

  • 另一方面,我也不想使用 集合,因为很难确定此类代码的正确性。

  • 到目前为止,我唯一想到的就是实现我自己的 NonParanthesisQueueNonExpressionStack 容器。这样做的优点是可以对从这些容器中拉出的对象进行更一致的类型检查 - 缺点是需要更多代码。

我的方法有任何合理的替代方案吗?

I've been implementing an enhanced Shunting-Yard algorithm for parsing an arithmetic expression. One aspect of the algorithm, is that it maintains a Queue, and a Stack.

In my implementation, the Queue contains Expressions and Operators.
The Stack contains Operators and Parenthesis.

Expressions, Parenthesis, and Operators have nothing in common that warrants any two of them having a shared interface.

Approaches:

  • My current implementation consists of Expression and Operator implementing a INotParanthesis. Operator and Paranthesis implement a INotExpression. I then declare Queue <INotParanthesis>, and Stack <INotExpression>.

    I don't like this implementation - these interfaces seem very much a hack for the purpose of cleaner algorithm code. I also believe that interfaces should describe what an object is, as opposed to what it isn't.

  • On the other hand, I also don't want to use collections of <Object>, as it can be difficult to be certain of the correctness of such code.

  • The only one I've come up with, so far, is implementing my own NonParanthesisQueue and NonExpressionStack containers. This has the advantage of more consistent type checking on objects getting pulled out of those containers - and the disadvantage of a lot more code.

Are there any reasonable alternatives to my approaches?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乖乖兔^ω^ 2024-12-03 22:58:08

听起来您真正想要的是 sum 类型。尽管 C# 没有内置这些​​,但函数式编程有一个技巧,您可以使用称为 Church 编码的技巧来实现此目的。它是完全类型安全的,不涉及强制转换,但是在 C# 中使用它有点奇怪,主要是由于类型推断的限制。

主要技巧是,我们不是使用属性和检查来检索两个替代方案之一,而是使用一个高阶函数 Map ,它接受两个函数作为参数,并根据存在的替代方案调用适当的函数。下面是如何使用它:

var stack = new Stack<IEither<Operator, Parenthesis>>();

stack.Push(new Left<Operator, Parenthesis>(new Operator()));
stack.Push(new Right<Operator, Parenthesis>(new Parenthesis()));

while (stack.Count > 0)
{
    stack.Pop().Map(op  => Console.WriteLine("Found an operator: " + op),
                    par => Console.WriteLine("Found a parenthesis: " + par));
}

这是 IEitherLeftRight 的实现。它们是完全通用的,可以在任何需要总和类型的地方使用。

public interface IEither<TLeft, TRight>
{
    TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight);
    void Map(Action<TLeft> onLeft, Action<TRight> onRight);
}

public sealed class Left<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TLeft value;

    public Left(TLeft value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onLeft(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onLeft(value);
    }
}

public sealed class Right<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TRight value;

    public Right(TRight value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onRight(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onRight(value);
    }
}

参考文献:

It sounds like what you really want is a sum type. Although C# does not have these built in, there's a trick from functional programming that you can use called Church encoding to achieve this. It's completely type safe with no casts involved, however it's a bit weird to use in C# mostly due to the limitations of the type inference.

The main trick is that instead of using properties and checks to retrieve one of the two alternatives, we have a higher order function Map that takes two functions as arguments and calls the appropriate one depending on which alternative was present. Here's how you would use it:

var stack = new Stack<IEither<Operator, Parenthesis>>();

stack.Push(new Left<Operator, Parenthesis>(new Operator()));
stack.Push(new Right<Operator, Parenthesis>(new Parenthesis()));

while (stack.Count > 0)
{
    stack.Pop().Map(op  => Console.WriteLine("Found an operator: " + op),
                    par => Console.WriteLine("Found a parenthesis: " + par));
}

Here's the implementation of IEither, Left and Right. They are fully generic and could be used anywhere you want a sum type.

public interface IEither<TLeft, TRight>
{
    TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight);
    void Map(Action<TLeft> onLeft, Action<TRight> onRight);
}

public sealed class Left<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TLeft value;

    public Left(TLeft value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onLeft(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onLeft(value);
    }
}

public sealed class Right<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TRight value;

    public Right(TRight value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onRight(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onRight(value);
    }
}

References:

巡山小妖精 2024-12-03 22:58:08

也许您可以为每个类型定义一个小持有者类型,一个具有 Expression 属性和 Operator 属性,另一个具有 Operator 属性和 Parenthesis 属性。访问器和构造器可以断言或以其他方式确保只填充一个。队列和堆栈将各自包含适当的持有者类型。

有点尴尬,但类型安全且可行。

希望有人能有更聪明的主意。

Maybe you could define a small holder type for each, one with an Expression property and an Operator property and the other with an Operator property and a Parenthesis property. Accessors and constructors could assert or otherwise ensure that only one is populated. The queue and the stack would each contain the appropriate holder type.

A little awkward but typesafe and workable.

Hopefully someone will have a more clever idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文