我想知道什么是最好的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
这里有两件事需要考虑.
首先,存在"无"的问题.当链条的一部分可能不返回任何东西时,你如何链接东西?答案是使用Option和for理解.例如:
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)
这个怎么样?
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)
小智 6
Monadic绑定(flatMap/map)与scala.Option类型.for-comprehensions也提供支持.如果您愿意,Scalaz提供适用的仿函数样式.
这不是等价的,但是出于多种原因,这是一个比Groovy运算符更好的解决方案.
| 归档时间: |
|
| 查看次数: |
6606 次 |
| 最近记录: |