开关/模式匹配的想法

时间:2020-03-06 14:57:48  来源:igfitidea点击:

我一直在关注Frecently,尽管我不太可能很快就克服障碍,但它无疑突出了C(或者库支持)可以使生活更轻松的某些领域。

特别是,我正在考虑F#的模式匹配功能,该功能允许使用非常丰富的语法,比当前的switch / conditional Cequivalents具有更高的表达力。我不会尝试举一个直接的例子(我的想法不正确),但总而言之,它允许:

  • 按类型匹配(对有区别的联合进行全覆盖检查)[请注意,这还会推断出绑定变量的类型,并为成员提供访问权限等]
  • 谓词匹配
  • 以上(以及我可能不知道的其他一些情况)的组合

尽管Cto最终会借用这些丰富的东西是很可爱的,但是在此期间,我一直在研究例如可以在运行时执行的操作,但是将一些对象组合在一起以允许以下操作是相当容易的:

var getRentPrice = new Switch<Vehicle, int>()
        .Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
        .Case<Bicycle>(30) // returns a constant
        .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
        .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
        .ElseThrow(); // or could use a Default(...) terminator

其中getRentPrice是Func <Vehicle,int>。

[请注意,这里的Switch / Case是错误的术语……但它表明了这个主意]

对我来说,这比使用重复的if / else或者复合三元条件(对于非平凡的表达式括起来非常混乱)等效的方法要清晰得多。它还避免了很多转换,并允许简单地扩展(直接或者通过扩展方法)到更特定的匹配项,例如,与VB Select ... Case相当的InRange(...)匹配项。 " 用法。

我只是在尝试衡量人们是否认为上述结构(在没有语言支持的情况下)有很多好处?

另外请注意,我一直在玩上述的3个变体:

  • 用于评估的Func <TSource,TValue>版本-与复合三元条件语句相当
  • 一个Action <TSource>版本-与if / else if / else if / else if / else相当
  • 一个Expression <Func <TSource,TValue >>版本-作为第一个版本,但可由任意LINQ提供程序使用

此外,使用基于表达式的版本可以重写表达式树,实质上是将所有分支内联到单个复合条件表达式中,而不是使用重复调用。我最近没有检查过,但是在某些早期的Entity Framework构建中,我似乎想起来这是必要的,因为它不太喜欢InvocationExpression。它还允许LINQ-to-Objects更有效地使用,因为它避免了重复的委托调用测试,显示与以上所述(使用Expression形式)相匹配的匹配项,与等效的Ccomposite条件条件相比,其运行速度相同(实际上要快得多)。陈述。为了完整起见,基于Func <...>的版本花费的时间是Cconditional语句的四倍,但仍然非常快,并且在大多数用例中不太可能成为主要瓶颈。

我欢迎上面的任何想法/投入/批评/等等(或者关于更丰富的语言支持的可能性...这里希望;-p)。

解决方案

尽管打开类型不是很" C尖锐",但我知道该构造在一般使用中会非常有用,因为我至少有一个可以使用它的个人项目(尽管其可管理的ATM)。用表达式树重写会有很多编译性能问题吗?

我认为这类库(起语言扩展的作用)可能不会获得广泛的接受,但是它们很有趣,并且对于在特定领域工作的小型团队确实很有用。例如,如果我们编写大量的"业务规则/逻辑"来进行诸如此类的任意类型测试,那么我会发现它很方便。

我不知道这是否有可能成为语言功能(似乎值得怀疑,但是谁能看到未来?)。

作为参考,相应的Fis大致为:

let getRentPrice (v : Vehicle) = 
    match v with
    | :? Motorcycle as bike -> 100 + bike.Cylinders * 10
    | :? Bicycle -> 30
    | :? Car as car when car.EngineType = Diesel -> 220 + car.Doors * 20
    | :? Car as car when car.EngineType = Gasoline -> 200 + car.Doors * 20
    | _ -> failwith "blah"

假设我们已经沿着

type Vehicle() = class end

type Motorcycle(cyl : int) = 
    inherit Vehicle()
    member this.Cylinders = cyl

type Bicycle() = inherit Vehicle()

type EngineType = Diesel | Gasoline

type Car(engType : EngineType, doors : int) = 
    inherit Vehicle()
    member this.EngineType = engType
    member this.Doors = doors

可以说C不能简单地打开类型的原因是因为它主要是一种面向对象的语言,而用面向对象的术语来做到这一点的"正确"方法是在Vehicle上定义GetRentPrice方法并重写它在派生类中。

就是说,我花了一些时间来使用像Fand Haskell这样的多范式和功能性语言,并且遇到了很多以前有用的地方(例如,当我们不编写我们需要打开的类型,这样我们就不能在它们上实现虚拟方法),这是我欢迎我们将语言与可区分的联合一起使用到该语言中的方法。

[编辑:删除了有关性能的部分,如马克所指出的可能短路]

另一个潜在的问题是可用性,从最终调用中可以明显看出,如果匹配项不满足任何条件会发生什么,但是如果匹配两个或者多个条件会发生什么呢?它应该引发异常吗?它应该返回第一个还是最后一个比赛?

我倾向于用来解决此类问题的一种方法是使用字典字段,其类型作为键,而lambda作为值,使用对象初始值设定项语法构造起来非常简洁;但是,这仅考虑具体类型,并且不允许其他谓词,因此可能不适用于更复杂的情况。 [附带说明,如果我们查看Ccompiler的输出,它经常将switch语句转换为基于字典的跳转表,因此似乎没有充分的理由表明它不支持on type的切换]

Bart De Smet的优秀博客由8部分组成,系列内容完全按照描述进行。在这里找到第一部分。

我认为这看起来确实很有趣(+1),但要注意的一件事:Ccompiler非常擅长优化switch语句。不仅仅因为短路,还会根据情况的不同而获得完全不同的IL,依此类推。

特定示例做了一些我认为非常有用的事情,没有语法与按类型区分的情况,例如(例如)typeof(Motorcycle)不是常量。

在动态应用程序中,这变得更加有趣,逻辑可以轻松地由数据驱动,从而提供"规则引擎"样式的执行。

在尝试用C做这种"功能性"的事情(甚至尝试用它写一本书)之后,我得出的结论是,除了少数例外,这样的事情没有太大帮助。

主要原因是诸如F之类的语言从真正支持这些功能中获得了巨大的力量。不是"我们可以做到",而是"这很简单,很明显,很期待"。

例如,在模式匹配中,编译器会告诉我们是否存在不完整的匹配,或者何时永远不会再匹配其他匹配。这对于开放式类型不太有用,但是当与已区分的联合或者元组匹配时,这非常好用。在F#中,我们希望人们进行模式匹配,并且这立即有意义。

"问题"是,一旦我们开始使用某些功能概念,就自然会继续。但是,在Cgets中利用元组,函数,部分方法应用程序和currying,模式匹配,嵌套函数,泛型,monad支持等非常丑陋,非常迅速。这很有趣,并且一些非常聪明的人在C#中做了一些很酷的事情,但是实际上使用它感觉很沉重。

我最终在C#中经常使用的内容(跨项目):

  • 序列函数,通过IEnumerable的扩展方法。因为C#语法很好地支持了ForEach或者Process(" Apply"?-对序列项进行操作)之类的事情,所以很适合。
  • 抽象常见的语句模式。复杂的try / catch / finally块或者其他涉及的(通常是非常通用的)代码块。扩展LINQ-to-SQL也适用于此。
  • 元组,在某种程度上。

**但请注意:缺乏自动归纳和类型推断确实阻碍了甚至使用这些功能。 **

正如其他人所提到的,所有这些都是在一个小型团队中,出于特定的目的,是的,如果我们对C#有所了解,他们可能会提供帮助。但是以我的经验,他们通常觉得比YMMV值得的麻烦更多。

其他一些链接:

  • Mono.Rocks游乐场具有许多类似的功能(以及非功能性编程但有用的功能)。
  • Luca Bolognese的功能性C#库
  • MSDN上的Matthew Podwysocki的功能性C#

恕我直言,OO做这种事情的方式是访客模式。访问者成员方法只是充当大小写构造,我们可以让语言本身处理适当的调度,而不必"窥视"类型。

模式匹配(如此处所述),其目的是根据值的类型规范来解构值。但是,C语言中的类(或者类型)的概念与我们不同意。

多范式语言设计有个错误,相反,在C#中使用lambda很好,Haskell可以在其中做一些必要的事情。 IO。但这不是一个非常优雅的解决方案,不是Haskell的方式。

但是,由于可以使用lambda演算来理解顺序过程编程语言,并且Chappens恰好适合顺序过程语言的参数,因此非常适合。但是,从Haskell的纯函数上下文中获取一些信息,然后将该特性放到一种不纯净的语言中,好吧,这样做不能保证获得更好的结果。

我的意思是,使模式匹配刻度线与语言设计和数据模型相关联。话虽如此,我不认为模式匹配是C的有用功能,因为它不能解决典型的C问题,也不能很好地适应命令式编程范例。