[英]When to use Option/None return types
I understand the whole principle and concept behind the Some/None/Option and I can certainly appreciate its merits. 我理解Some / None / Option背后的整个原理和概念,我当然可以理解它的优点。 My question is more of best practice.
我的问题更多是最佳做法。 When does it become overkill and when does it make sense to use it religiously.
什么时候变得矫枉过正,什么时候有意义地使用它。 I think (and i could be wrong) but it makes sense to use it as much as possible since it's a safer way to pass around nothing (as opposed to null).
我认为(我可能是错的)但是尽可能多地使用它是有道理的,因为它是一种更安全的方式来传递任何东西(而不是null)。 The thing I see myself doing a lot though is having some functions littered with map, getOrElse, get, match and even sometimes nesting them which tends to look ugly.
我看到自己做了很多事情,虽然有一些功能乱七八糟的地图,getOrElse,get,match甚至有时嵌套它们往往看起来很难看。 Is there some concept I'm missing or some best practice to do with a function that receives multiple Optional values.
是否有一些我缺少的概念或一些最佳实践与接收多个Optional值的函数有关。 For example:
例如:
def updateJobs(id: Int) = withAuth {
request => {
User.userWithToken(request.headers.get("token").get).map {
user =>
Job.jobsAfterIdForForeman(id.toString, user.id.toString) match {
case Some(json) => Ok(json)
case _ => NoContent
}
}.getOrElse(BadRequest)
}
}
or even worse for example: 甚至更糟的例如:
def addPurchaseRequest(json: JsValue) = {
(json \ "jobId").asOpt[Long].map {
jobId => JobDAO.jobWithId(jobId).map {
job => PurchaseRequestDAO.insert(new PurchaseRequest(json, job)).map {
model =>
val request = model.asInstanceOf[PurchaseRequest]
(json \ "items").asOpt[List[JsObject]].map {
list => {
if (PurchaseItemAssociationDAO.bulkInsert(PurchaseItemAssociation.itemsFromJsonArray(list, request.id))) Option(request.addResponseJson) else None
}
}.getOrElse(None)
}.getOrElse(None)
}.getOrElse(None)
}.getOrElse(None)
}
I managed to refactor some to not look so crazy, but is there a better way to refactor this so it doesn't look so crazy? 我设法重构一些看起来不那么疯狂,但有没有更好的方法来重构这个看起来不那么疯狂? Am I missing something or do you get used to things just looking like this?
我错过了什么或你是否习惯了这样的事情? Seems like there should certainly be a cleaner practice.
似乎应该有一个更清洁的做法。
Since the Option class is monadic, you should use for
comprehensions to make that code look cleaner. 由于期权类是一元,你应该使用
for
内涵,使代码看起来更干净。 For example, your second example can be rewritten as: 例如,您的第二个示例可以重写为:
def addPurchaseRequest(json: JsValue) =
for {
jobId <- (json \ "jobId").asOpt[Long]
job <- JobDAO.jobWithId(jobId)
model <- PurchaseRequestDAO.insert(new PurchaseRequest(json, job))
request = model.asInstanceOf[PurchaseRequest]
list <- (json \ "items").asOpt[List[JsObject]]
if PurchaseItemAssociationDAO.bulkInsert(PurchaseItemAssociation.itemsFromJsonArray(list, request.id))
} yield request.addResponseJson
Use for-comprehensions or at least use flatMap
instead of map / getOrElse(None)
to make things more compact. 使用for-comprehensions或至少使用
flatMap
而不是map / getOrElse(None)
来使事情更紧凑。 Also, it's customary to put the new variable at the end of the previous line instead of on its own line. 此外,习惯上将新变量放在前一行的末尾而不是它自己的行。 This would clean up your worst case tremendously.
这将极大地清理你的最坏情况。
Alternatively, with particularly long chains that require intermediate logic, I find an exception-like mechanism works even better than for-comprehensions (based on the same principle as nonlocal returns): 或者,对于需要中间逻辑的特别长的链,我发现类似异常的机制比for-comprehensions更好(基于与非本地返回相同的原理):
trait Argh extends scala.util.control.ControlThrowable
implicit class GetOrArgh[A](val underlying: Option[A]) extends AnyVal {
def or(a: Argh) = underlying match {
case None => throw a
case _ => underlying.get
}
}
def winnow[A](f: Argh => A): Option[A] = {
val argh = new Argh { }
try { Some(f(argh)) }
catch { case x: Argh if (x eq argh) => None }
}
Which you then use like this: 你然后使用这样:
def addPurchaseRequest(json: JsValue) = winnow { fail =>
val jobId = (json \ "jobId").asOpt[Long] or fail
val job = JobDAO.jobWithId(jobId) or fail
val model = PurchaseRequestDAO.insert(new PurchaseRequest(json, job)) or fail
val request = model match {
case pr: PurchaseRequest => pr
case _ => throw fail
}
val list = (json \ "items").asOpt[List[JsObject]]
if (!PurchaseItemAssociationDAO.bulkInsert(
PurchaseItemAssociation.itemsFromJsonArray(list, request.id)
)) throw fail
request.addResponseJson
}
Whether this approach or the for comprehension works better depends (in my experience) on how much intermediate processing you have to do. 这种方法或理解是否更好地取决于(根据我的经验)你需要做多少中间处理。 If you can make everything one-liners, the for-comprehension is nice and clean.
如果你能把所有东西都做成单行,那么理解就是美好而干净。 Once you require something a little more tangled, I like this approach better.
一旦你需要更纠结的东西,我更喜欢这种方法。
Note that for comprehensions are canonical Scala constructs, whereas this will require some learning for new users. 请注意,对于理解是规范的Scala构造,而这需要一些新用户的学习。 Thus, favor for comprehensions or flatMaps in code that people may need to get up to speed on rapidly.
因此,有利于人们可能需要快速加速的代码中的理解或flatMaps。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.