Scala:使用谓词

我爱我一些Scala。 实际上,由于这是我现在的日常工作,所以我一直都很喜欢它。 它结合了我在Python中具有丰富的库基础(感谢Java)所具有的简短,富有表现力的特性,以及将我依赖于静态类型语言的编译器检查功能。 我不在乎别人怎么 。 我认识到这种语言并非没有缺陷。 可以说有些语言扩展缺失,尤其是谓词。

那是什么意思 语言中是否没有隐含的支持,使得它们可以泛化任何A =>布尔值 ? 当然。 但是,当我看到诸如List的 :: filter:: filterNot之方法时,我遇到了问题。 前者是有道理的,后者则突出显示了基本名称的缺失,这些名称可以直接在名称中看到。 也就是说,我们缺少“ Not ”辅助谓词功能:

case class Not[A](func: A => Boolean) extends (A => Boolean){ 
  def apply(arg0: A) = !func(arg0)
}

如果那只是一个简单的修复,而如果缺少所有这些,那么就很容易提出建议,并将其放到下一版本的Scala中。 当然,对于22个版本的Function ,我们还需要有22个版本的“ Not”,但这是另一天的争论。

可以说,Scala需要显式的谓词支持。 它不仅需要“不”,还需要易于阅读和维护的逻辑组合器,并且需要支持可用于形成高阶谓词逻辑基本构建块 。 使用其他公认的谓词库不会给我们所需的功能和灵活性。

添加谓词表达式
这正是我对谓词库所做的。 这个小型库的目标之一是为描述性和简洁的方式构成谓词功能添加一些简单的语法支持 。 具体来说,我想说“大于4但小于10? 或用几乎简明的英语表示“大于零或什至不是两者都大于零”。 我一直使用:: filter:: exists语句编写与该表达式相同的表达式:

myList.filter(x => x > 4 && x < 10)

对于小的短语,这并不困难。 添加的唯一额外样板是名称“ x =>”,表示我们正在形成一个匿名函数 。 不幸的是,如果我想重用,扩展或维护该逻辑,则必须使用更多样板。 有时,如果逻辑足够严格,我需要将其拼接为几种可能会或可能不会附加到特征/类层次结构的方法。 尽管编码风格不错,但这种附加的冗长性却使我口齿不清。

我真正想做的是让运算符本身适用于表达式,而不是表达式的求值。 这些运算符的结果将是功能本身,并保留了我们最初开始时的可组合性。 换句话说,“或”将两个谓词对象转换为代表逻辑或前两个谓词之间的第三个不同的谓词对象。 只要每个前体对象都建立在不可变, 参照透明的基础上,那么生成的复合谓词表达式将可以在任何环境中安全使用。

这就是被添加到内每个断言变种谓词库。 谓词成员函数用作工厂方法,以基于当前谓词和谓词参数生成新的谓词。 尽管概念上类似于函数之间的组合 ,但不能保证每个组合谓词都将被评估。

Predicate变体有22种,与Scala选择22种Function变体的方式非常相似,每种变体都具有以下方法:

=> pred1(…)&& pred2(…)}
andNot => pred1(…)&&!pred2(…)
nand =>!pred1(…)|| !pred2(…) => pred1(…)|| pred2(…) orNot => pred1(…)|| !pred2(…) 也不 =>!(pred1(…)|| pred2(…)) xor => if(pred1(…)!pred2(…)else pred2(…) xnor =>

就像我之前说的,这些函数中的每个函数都返回另一个谓词(实际上只是另一个函数。)实际上,使用这些成员函数看起来像这样:

case class LessThen(x: Int) extends Function[Int,Boolean]{ def apply(arg: Int) = arg < x }
case class Modulo(x: Int, group: Int) extends Function[Int,Boolean]{ def apply(arg: Int) = (arg % x) == group }
case class GreaterThanEqual(x: Int) extends Function[Int,Boolean]{ def apply(arg: Int) = arg >= x }
val myList = List(1,2,3,4,5,6,7,8,9)
myList.filter( LessThan(7) and GreaterThanEqual(4))
myList.filter( Modulo(4,2) or Modulo(3,0) or Modulo(5,1) )

谓词能够链接在一起以形成更复杂的逻辑表达式。

使用隐式转换避免污染
在面向对象的编程中,如果我想传递一些困难的逻辑,或者要调用与特定层次结构中的单个类相关联的逻辑,则可以将其添加为遵循单一职责原则的同伴类,也可以将其附加到对象上本身。 通常不鼓励后者,除非它需要进入私有国家或我们正在使用委托 。 就是说,如果需要几个功能,同伴类的接口可能会增长并成为帮助类 (男孩确实有些人喜欢增长它们。)随着库和代码库的成熟,组合谓词表达式变得异常复杂,危险且复杂。责备缠身的过程。 简而言之,代码经常成为维护的噩梦。

我想说明一下,这不是命令式或面向对象编程的先天问题,而是人们如何被允许在其中编程。 尽管OO设计具有策略模式 ,但它仅与强制执行一样好。 我对谓词的实现会产生一定的要求(工厂方法是实例方法),不能防止滥用。 有人认为Scala的功能不够,不能强制不变性,在某些方面确实如此。 与Java向后兼容是一个不幸的副作用(喜欢双关语)。

我想以一种一般的方式来避免以前使用严格的OO代码库所遇到的各种问题。 隐式转换将转换后的类隐藏在一个受限接口(即适配器模式)之后 ,就像Scala对匿名函数所做的那样。 我认为,任何可能添加到类中的杂物都将被此接口隐藏,因此不会污染谓词。 除此之外,还可以组合函数以从初始谓词创建不同类型的谓词,并且我们在不良代码生成方面获得了很大的支持。 函数组合必须成为Scala从函数编程中偷走的最好的东西之一。

还有什么?
在此库的“谓词”部分中仅添加了另一件事,即“ is”函数。 Haskell的Data.Function.Predicate盗用了此函数。 最初,我用Haskell的“ is”完全相同的签名创建了所有22个版本,但是后来我意识到Scala的热切评估导致类型不匹配,如果不添加样板就无法轻易克服。 因为“ is”的设计减少了样板,同时提高了可读性,所以简单的解决方案是使用一个接受谓词的“ is”方法来创建到匿名类的隐式转换。 如此编写,可以如下使用:

myStringList.filter(_。length小于(0))

可读性强,并将A => B类型的匿名函数映射到A =>布尔值。 缺点是它在每次调用时都会创建一个新对象。

未来的工作
条件函数很难很好地设计,但同时又是计算逻辑门的基础局部函数可用于创建谓词逻辑,但对于外部观察者而言是非透明的。 有一个:: orElse函数是有原因的(也是一个很好的原因),该函数更多地用于案例覆盖而不是案例完整性。 实际上,:: lift成员函数的存在表明与标准的“ if-else”语句不同,不需要“全部捕获”逻辑路径。 因此,PartialFunction对于谓词应用程序不是一个好的选择。

在充实了一些简单的逻辑组合函数以使用谓词之后,我想添加一种结构来构成更复杂的谓词表达式。 也就是说,该函数包含可控制和可扩展的谓词以控制流。 为谓词应用程序添加条件支持,以使谓词表达式控制程序流:

case class ApplyEither[A,B](pred: Predicate[A], thatTrue: A => B, thatFalse: A => B) extends (A => B){
  def apply(arg0: A) = if(pred(arg0)) thatTrue(arg0) else thatFalse(arg0)
}

遵循一个非常简单的命令式模型很容易 。 扩展到组成:

case class ComposeEither[A,B,C](pred: Predicate[B], that: A => B, thatTrue: B => C, thatFalse: B => C) extends (A => C){
  def apply(arg0: A) ={
    val out = that(arg0)
    if(pred(out)) thatTrue(out) else thatFalse(out)
  }
}

也被证明很容易。 如此简单,我编写了更多脚本来为22个版本的“ ApplyIf”,“ ApplyEither”,“ ComposeIf”,“ ComposeEither”,“ AndThenIf”和“ AndThenEither”生成代码。 然后,我扩展了我编写的代码,以便它们都扩展了相同的特征,从而允许将一个特征用于另一个特征中。

这只有一个大问题,它创建了一个不灵活的结构,如果不扩展各种谓词函数类的接口就无法轻松遍历。 “什么是所有潜在途径中的所有价值”问题需要一种新方法。 “我使用了什么功能”这个问题还需要另一个问题。 依此类推,以此类推,直到每个类的界面开始看起来像可怕的辅助类。 这是表达问题的经典例子。

事后看来,正确的方法是创建一个树状结构来表达计算树逻辑 。 保留函数和谓词排列的东西,并伴随着截然不同的一组函数来遍历该树。 我之所以说是事后看来,是因为我首先创建了所有类,然后在我开始感到所有其他问题的痛苦之后,便删除了它们,而这些问题如果不采用另一种方法就无法回答。

这是将来会发生的事情。 我个人想等待HList的正确实现,该实现不会遭受类型擦除或需要实验性编译器标志的影响,但与此同时。 Miles Sabin已经证明了可以使用他令人难以置信的Shapeshape库来完成。 现在,我需要做的就是等待编译器进行更改以使其成为主流。

参考: Scala:与我们JCG合作伙伴的 谓词一起工作   Owein Reese在“ 静态类型化”博客中。


翻译自: https://www.javacodegeeks.com/2012/02/scala-working-with-predicates.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值