最好的Scala模仿Groovy的安全解除引用运算符(?.)?

Ant*_*bbs 25 groovy scala

我想知道什么是最好的Scala模仿Groovy的安全解除引用运算符(?.),或者至少是一些接近的替代方案?

我在Daniel Spiewak的博客上讨论过它,但是想把它打开到StackOverFlow ......

为了每个人的时间,这是丹尼尔的最初回应,我的反击,以及他的第二个回应:

@Antony

实际上,我先看了那个.或者更确切地说,我试图从Ruby土地上复制Ragenwald和"操作员".问题是,如果没有代理,这有点困难.考虑以下表达式(使用Ruby的andand,但它与Groovy的运算符相同):

test.andand().doSomething的()

我可以从Any =>某种类型实现andand()方法创建隐式转换,但这就是魔术停止的地方.无论值是否为null,doSomething()方法仍将执行.由于它必须以类型安全的方式在某个目标上执行,这将需要实现字节码代理,这将是片状和奇怪的(注释,最终方法,构造函数等的问题).

更好的选择是回归到两者的灵感来源,以及Groovy的安全解除引用运算符:monadic map操作.以下是一些使用Option实现模式的Scala语法:

val something:Option [String] = ... //可能是Some(...)或None

val length = something.map(_.length)

在此之后,length要么是Some(str.length)(其中str是Option中包含的String对象),要么是None.这正是安全解除引用操作符的工作原理,除了它使用null而不是类型安全的monad.

如上所述,我们可以从某种类型T => Option [T]定义隐式转换,然后以这种方式映射,但是某些类型已经定义了映射,因此它不会非常有用.或者,我可以实现类似于map但具有单独名称的东西,但无论如何实现它,它将依赖于高阶函数而不是简单的链式调用.它似乎只是静态类型语言的本质(如果有人能解决这个问题,请随时纠正我).

Daniel Spiewak,2008年7月7日星期一下午1:42

我的第二个问题:

感谢Daniel对此的回应?我想我错过了!我想我明白你的建议是什么,但是假设你无法控制来源,那么这样的事情是怎样的:

company?.getContactPerson?.getContactDetails?.getAddress?.getCity
Run Code Online (Sandbox Code Playgroud)

假设它是一个java bean,你不能进入并将返回值更改为Something [T] - 我们可以在那里做什么?

Antony Stubbs 2009年7月21日星期二晚上8:07哦天哪 - 重新阅读那就是你提出从T到Option [T]的隐式转换的权利吗?但是你还能像这样把它连在一起吗?你还需要地图吗?嗯....

var city = company.map(_.getContactPerson.map(_.getContactDetails.map(_.getAddress.map(_.getCity))))
Run Code Online (Sandbox Code Playgroud)

Antony Stubbs于2009年7月21日星期二晚上8:10发布

他的第二回应:

@Antony

在公司的情况下,我们真的无法做很多事情吗?.getContactPerson等等......即使假设这是有效的Scala语法,我们仍然需要一些方法来阻止链中的后续调用.如果我们不使用函数值,这是不可能的.因此,像地图这样的东西确实是唯一的选择.

对Option的隐式转换不会很糟糕,但是通过隐含事物,我们会绕过对类型系统的一些保护.做这种事情的最好方法是使用与Option一致的for-comprehension.我们可以做map和flatMap,但它的魔法语法更好:

 for {
   c < - company
   person <- c.getContactPerson   
   details <- person.getContactDetails
   address <- details.getAddress 
  } yield address.getCity
Run Code Online (Sandbox Code Playgroud)

Daniel Spiewak,2009年7月21日星期二,下午9:28

如果Daniel在他的博客上发布他的原始答案作为答案,我会编辑问题以便为系统删除它们.

Dan*_*ral 15

这里有两件事需要考虑.

首先,存在"无"的问题.当链条的一部分可能不返回任何东西时,你如何链接东西?答案是使用Optionfor理解.例如:

scala> case class Address(city: Option[String] = None, street: Option[String] = None, number: Option[Int] = None)
defined class Address

scala> case class Contact(name: String, phone: Option[String] = None, address: Option[Address] = None)
defined class Contact

scala> case class ContactDetails(phone: Option[String] = None, address: Option[Address] = None)
defined class ContactDetails

scala> case class Contact(phone: Option[String] = None, address: Option[Address] = None)
defined class Contact

scala> case class Person(name: String, contactDetails: Option[Contact] = None)
defined class Person

scala> case class Company(name: String, contactPerson: Option[Person] = None)
defined class Company

scala> val p1 = Company("ABC", Some(Person("Dean", Some(Contact(None, Some(Address(city = Some("New England"))))))))
p1: Company = Company(ABC,Some(Person(Dean,Some(Contact(None,Some(Address(Some(New England),None,None)))))))

scala> val p2 = Company("Finnicky", Some(Person("Gimli", None)))
p2: Company = Company(Finnicky,Some(Person(Gimli,None)))

scala> for(company <- List(p1, p2);
     | contactPerson <- company.contactPerson;
     | contactDetails <- contactPerson.contactDetails;
     | address <- contactDetails.address;
     | city <- address.city) yield city
res28: List[String] = List(New England)
Run Code Online (Sandbox Code Playgroud)

这就是你应该如何编写可能在Scala中返回或不返回的代码.

当然,第二个问题是,有时您可能无法访问源代码来进行正确的转换.在这种情况下,除非可以使用隐式,否则还有一些额外的语法开销.我将在下面给出一个例子,其中我使用了一个" toOption"函数 - 在Scala 2.8上有这样的东西,我将在下面讨论它.

scala> def toOption[T](t: T): Option[T] = if (t == null) None else Some(t)
toOption: [T](t: T)Option[T]

scala> case class Address(city: String = null, street: String = null, number: Int = 0)
defined class Address

scala> case class Contact(phone: String = null, address: Address = null)
defined class Contact

scala> case class Person(name: String, contactDetails: Contact = null)
defined class Person

scala> case class Company(name: String, contactPerson: Person = null)
defined class Company

scala> val p1 = Company("ABC", Person("Dean", Contact(null, Address(city = "New England"))))
p1: Company = Company(ABC,Person(Dean,Contact(null,Address(New England,null,0))))

scala> val p2 = Company("Finnicky", Person("Gimli"))
p2: Company = Company(Finnicky,Person(Gimli,null))

scala> for(company <- List(p1, p2);
     | contactPerson <- toOption(company.contactPerson);
     | contactDetails <- toOption(contactPerson.contactDetails);
     | address <- toOption(contactDetails.address);
     | city <- toOption(address.city)) yield city
res30: List[String] = List(New England)
Run Code Online (Sandbox Code Playgroud)

请记住,在命名函数时,您可以非常有创意.因此,toOption我可能将其命名为" " ,而不是" ?",在这种情况下,我会编写类似" ?(address.city)"的内容.

感谢nuttycom提醒我,在Scala 2.8上有一个Option工厂Option,所以我可以写Option(something).实际上,您可以将toOption上面的" Option" 替换为" ".如果您更喜欢使用?,可以使用import重命名.


Cra*_*lin 10

创建此隐式转换.

class SafeDereference[A](obj: A) {
  def ?[B >: Null](function: A => B): B = if (obj == null) null else function(obj)
}

implicit def safeDereference[A](obj: A) = new SafeDereference(obj)
Run Code Online (Sandbox Code Playgroud)

用法并不像Groovy那么漂亮,但它并不可怕.

case class Address(state: String)
case class Person(first: String, last: String, address: Address)
val me = Person("Craig", "Motlin", null)

scala> me ? (_.first)
res1: String = Craig

scala> me ? (_.address)
res2: Address = null

scala> me ? (_.address) ? (_.state)
res3: String = null
Run Code Online (Sandbox Code Playgroud)


Wal*_*ang 8

这个怎么样?

def ?[A](block: => A) =
  try { block } catch {
    case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null
    case e => throw e
  }
Run Code Online (Sandbox Code Playgroud)

使用这个小片段,您可以安全地取消引用,代码本身非常简洁:

val a = ?(b.c.d.e)
Run Code Online (Sandbox Code Playgroud)

a ==如果b或bc或bcd或bcde为null,则为null,否则为a == bcde

我认为当你使用像Scala这样的语言时,安全解除引用运算符的价值会降低,因为Scala具有逐个调用和隐含的功能.

ps:我根据下面的一条注释修改了上面的代码,以处理在被调用函数中实际抛出NullPointerException的情况.

顺便说一句,我认为使用下面的函数是一种更加惯用的Scala编写方式:

def ??[A](block: => A): Option[A] = ?(block) match {
    case a: A => Some(a)
    case _ => None
  }
Run Code Online (Sandbox Code Playgroud)

像这样:

??(a.b.c.d) match {
    case Some(result) => // do more things with result
    case None => // handle "null" case
  }
Run Code Online (Sandbox Code Playgroud)

  • 如果其中一个方法调用实际抛出NPE,这有点废话. (9认同)
  • 这太糟糕了.超越可怕.你首先捕获可能是合法的NPE,其次,忽略一个操作在抛出NPE之前可能会产生副作用的事实. (5认同)

小智 6

Monadic绑定(flatMap/map)与scala.Option类型.for-comprehensions也提供支持.如果您愿意,Scalaz提供适用的仿函数样式.

这不是等价的,但是出于多种原因,这是一个比Groovy运算符更好的解决方案.