Scala:回归有它的位置 [英] Scala: return has its place

查看:26
本文介绍了Scala:回归有它的位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

参考:
Scala 返回关键字
处理 scala 控制器中的错误

EDIT3
这是最终"解决方案,再次感谢 Dan Burton.

EDIT3
This is the "final" solution, again thanks to Dan Burton.

def save = Action { implicit request =>
  val(orderNum, ip) = (generateOrderNum, request.remoteAddress)
  val result = for {
    model   <- bindForm(form).right // error condition already json'd
    transID <- payment.process(model, orderNum) project json
    userID  <- dao.create(model, ip, orderNum, transID) project json
  } yield (userID, transID)
}

然后是 pimp'd Either 项目方法,放置在您的应用程序的某个位置(在我的情况下,一个隐式特征,即 sbt 根和子项目从以下位置扩展其基本包对象:

Then the pimp'd Either project method, placed somewhere in your application (in my case, an implicits trait that sbt root & child project(s) extends their base package object from:

class EitherProvidesProjection[L1, R](e: Either[L1, R]) {
  def project[L1, L2](f: L1 => L2) = e match {
    case Left(l:L1) => Left(f(l)).right
    case Right(r)   => Right(r).right
  }
}
@inline implicit final def either2Projection[L,R](e: Either[L,R]) = new EitherProvidesProjection(e)

EDIT2
进化,已经从嵌入的返回语句变成了这个密度的小白矮星(感谢@DanBurton,Haskell 流氓;-))

EDIT2
Evolution, have gone from embedded return statements to this little white dwarf of density (kudos to @DanBurton, the Haskell rascal ;-))

def save = Action { implicit request =>
  val(orderNum, ip) = (generateOrderNum, request.remoteAddress)
  val result = for {
    model   <- form.bindFromRequest fold(Left(_), Right(_)) project( (f:Form) => Conflict(f.errorsAsJson) )
    transID <- payment.process(model, orderNum) project(Conflict(_:String))
    userID  <- dao.create(model, ip, orderNum, transID) project(Conflict(_:String))
  } yield (userID, transID)
  ...
}

我已将 Dan 的 onLeft Either 投影作为皮条客添加到 Either,使用上述项目"方法,该方法允许右偏 eitherResult project(left-outcome).基本上,您将失败优先错误作为左派,将成功作为右派,这在将选项结果提供给理解时不起作用(您只会得到 Some/None 结果).

I have added Dan's onLeft Either projection as a pimp to Either, with the above "project" method, which allows for right-biased eitherResult project(left-outcome). Basically you get fail-first error as a Left and success as a Right, something that would not work when feeding Option outcomes to for comprehension (you only get Some/None outcome).

我唯一不满意的是必须指定 project(Conflict(param)); 的类型;我认为编译器能够从传递给它的 Either 推断左条件类型:显然不是.

The only thing I'm not thrilled with is having to specify the type for the project(Conflict(param)); I thought the compiler would be able to infer the left condition type from the Either that is being passed to it: apparently not.

无论如何,很明显,函数式方法不需要嵌入返回语句,就像我尝试使用 if/else 命令式方法一样.

At any rate, it's clear that the functional approach obviates the need for embedded return statements as I was trying to do with if/else imperative approach.

编辑
等效的功能是:

EDIT
The functional equivalent is:

val bound = form.bindFromRequest
bound fold(
  error=> withForm(error),
  model=> {
    val orderNum = generateOrderNum()
    payment.process(model, orderNum) fold (
      whyfail=> withForm( bound.withGlobalError(whyfail) ),
      transID=> {
        val ip = request.headers.get("X-Forwarded-For")
        dao.createMember(model, ip, orderNum, transID) fold (
          errcode=> 
            Ok(withForm( bound.withGlobalError(i18n(errcode)) )),
          userID=> 
            // generate pdf, email, redirect with flash success
        )}
    )}
)

这当然是一个功能强大的代码块,在那里发生了很多事情;但是,我认为具有嵌入式返回的相应命令式代码不仅同样简洁,而且更容易理解(另外还有更少的尾随花括号和括号来跟踪)

which is certainly a densely power packed block of code, a lot happening there; however, I would argue that corresponding imperative code with embedded returns is not only similarly concise, but also easier to grok (with added benefit of fewer trailing curlies & parens to keep track of)

原创
发现自己处于迫在眉睫的境地;希望看到以下方法的替代方法(由于使用 return 关键字和方法上缺少显式类型而不起作用):

ORIGINAL
Finding myself in an imperative situation; would like to see an alternative approach to the following (which does not work due to the use of return keyword and lack of explicit type on method):

def save = Action { implicit request =>
  val bound = form.bindFromRequest
  if(bound.hasErrors) return Ok(withForm(bound))

  val model = bound.get
  val orderNum = generateOrderNum()
  val transID  = processPayment(model, orderNum)
  if(transID.isEmpty) return Ok(withForm( bound.withGlobalError(...) ))

  val ip = request.headers.get("X-Forwarded-For")
  val result = dao.createMember(model, ip, orderNum, transID)
  result match {
    case Left(_) => 
      Ok(withForm( bound.withGlobalError(...) ))
    case Right((foo, bar, baz)) =>
      // all good: generate pdf, email, redirect with success msg
    }
  }
}

在这种情况下,我喜欢使用 return 来避免嵌套多个 if/else 块、折叠、匹配或填充空白的非命令式方法.当然,问题在于它不起作用,必须指定显式返回类型,这有其自身的问题,因为我还没有弄清楚如何指定一个满足 Play 魔法工作的类型 - 不,def save: Result,不起作用,因为编译器然后抱怨 implicit result 现在没有显式类型 ;-(

In this case I like the use of return as you avoid nesting several if/else blocks, or folds, or matches, or fill-in-the-blank non-imperative approach. The problem of course, is that it doesn't work, an explicit return type has to specified, which has its own issues as I have yet to figure out how to specify a type that satisfies whatever Play magic is at work -- no, def save: Result, does not work as the compiler then complains about implicit result now not having an explicit type ;-(

无论如何,Play 框架示例提供了 la, la, la, la 快乐的 1-shot-deal fold(error,success) 条件,这在现实世界中并非总是如此™;-)

At any rate, Play framework examples provide la, la, la, la happy 1-shot-deal fold(error, success) condition which is not always the case in the real world™ ;-)

那么,上述代码块的惯用等价物(不使用 return)是什么?我假设它会嵌套 if/else、match 或 fold,这有点难看,每个嵌套条件都会缩进.

So what is the idiomatic equivalent (without use of return) to above code block? I assume it would be nested if/else, match, or fold, which gets a bit ugly, indenting with each nested condition.

推荐答案

所以作为一个 Haskeller,显然在我看来,所有问题的解决方案都是 Monads.和我一起进入一个简化的世界(对我来说很简单),你的问题在 Haskell 中,你有以下类型需要处理(作为一个 Haskeller,我有点迷恋类型):

So as a Haskeller, obviously in my mind, the solution to everything is Monads. Step with me for a moment into a simplified world (simplified for me, that is) where your problem is in Haskell, and you have the following types to deal with (as a Haskeller, I sort of have this fetish for types):

bindFormRequest :: Request -> Form -> BoundForm
hasErrors :: BoundForm -> Bool

processPayment :: Model -> OrderNum -> TransID
isEmpty :: TransID -> Bool

让我们在这里暂停一下.在这一点上,我对 boundFormHasErrorstransIDisEmpty 有点畏缩.这两件事都意味着失败的可能性分别注入 BoundFormTransID.那很糟.相反,应该单独维护失败的可能性.请允许我提出这个替代方案:

Let's pause here. At this point, I'm sort of cringing a bit at boundFormHasErrors and transIDisEmpty. Both of these things imply that the possibility of failure is injected into BoundForm and TransID respectively. That's bad. Instead, the possibility of failure should be maintained separate. Allow me to propose this alternative:

bindFormRequest :: Request -> Form -> Either FormBindError BoundForm
processPayment :: Model -> OrderNum -> Either TransError TransID 

感觉好多了,这些 Either 导致了 Either monad 的使用.让我们写一些更多的类型.我将忽略 OK 因为它几乎包含了所有内容;我有点捏造,但概念仍然会翻译成一样的.相信我;最后,我将把它带回 Scala.

That feels a bit better, and these Eithers are leading into the use of the Either monad. Let's write up some more types though. I'm going to ignore OK because that is wrapped around pretty much everything; I'm fudging a little bit but the concepts will still translate just the same. Trust me; I'm bringing this back around to Scala in the end.

save :: Request -> IO Action

form :: Form
withForm :: BoundForm -> Action

getModel :: BoundForm -> Model
generateOrderNum :: IO OrderNum
withGlobalError :: ... -> BoundForm -> BoundForm

getHeader :: String -> Request -> String
dao :: DAO
createMember :: Model -> String -> OrderNum -> TransID
             -> DAO -> IO (Either DAOErr (Foo, Bar, Baz))

allGood :: Foo -> Bar -> Baz -> IO Action

好的,现在我要做一些有点奇怪的事情,让我告诉你为什么.Either monad 的工作方式是这样的:一旦你点击了 Left,你就会停下来.(我选择这个 monad 来模拟提前返回有什么意外吗?)这一切都很好,但是我们希望总是以 Action 停止,因此以 FormBindError 不会削减它.所以让我们定义两个函数,让我们以这样的方式处理 Eithers,如果我们发现一个 Left,我们可以安装更多的处理".

OK, now I'm going to do something a bit wonky, and let me tell you why. The Either monad works like this: as soon as you hit a Left, you stop. (Is it any surprise I chose this monad to emulate early returns?) This is all well and good, but we want to always stop with an Action, and so stopping with a FormBindError isn't going to cut it. So let's define two functions that will let us deal with Eithers in such a way that we can install a little more "handling" if we discover a Left.

-- if we have an `Either a a', then we can always get an `a' out of it!
unEither :: Either a a -> a
unEither (Left a) = a
unEither (Right a) = a

onLeft :: Either l r -> (l -> l') -> Either l' r
(Left l)  `onLeft` f = Left (f l)
(Right r) `onLeft` _ = Right r

此时,在 Haskell 中,我讨论 monad 转换器,以及将 EitherT 堆叠在 IO 之上.然而,在 Scala 中,这不是问题,所以无论我们在哪里看到 IO Foo,我们都可以假装它是一个 Foo.

At this point, in Haskell, I would talk about monad transformers, and stacking EitherT on top of IO. However, in Scala, this is not a concern, so wherever we see IO Foo, we can just pretend it is a Foo.

好的,我们来写save.我们将使用 do 语法,稍后将其转换为 Scalafor 语法.回忆一下 for 语法,你可以做三件事:

Alright, let's write save. We will use do syntax, and later will translate it to Scala's for syntax. Recall in for syntax you are allowed to do three things:

  • 使用 <- 从生成器分配(这与 Haskell 的 <- 相当)
  • 使用 = 为计算结果指定一个名称(这类似于 Haskell 的 let)
  • 使用带有关键字 if 的过滤器(这类似于 Haskell 的 guard 函数,但我们不会使用它,因为它不能让我们控制产生的特殊"价值)
  • assign from a generator using <- (this is comparable to Haskell's <-)
  • assign a name to the result of a computation using = (this is comparable to Haskell's let)
  • use a filter with the keyword if (this is comparable to Haskell's guard function, but we won't use this because it doesn't give us control of the "exceptional" value produced)

然后最后我们可以yield,这和Haskell中的return是一样的.我们将把自己限制在这些事情上,以确保从 Haskell 到 Scala 的翻译顺利.

And then at the end we can yield, which is the same as return in Haskell. We will restrict ourselves to these things to make sure that the translation from Haskell to Scala is smooth.

save :: Request -> Action
save request = unEither $ do
  bound <- bindFormRequest request form
           `onLeft` (err -> withForm (getSomeForm err))

  let model = getModel bound
  let orderNum = generateOrderNum
  transID <- processPayment model orderNum
             `onLeft` (err -> withForm (withGlobalError ... bound))

  let ip = getHeader "X-Forwarded-For" request
  (foo, bar, baz) <- createMember model ip orderNum transID dao
                     `onLeft` (err -> withForm (withGlobalError ... bound))

  return $ allGood foo bar baz

注意到什么了吗?它与您以命令式编写的代码几乎相同

Notice something? It looks almost identical to the code you wrote in imperative style!

您可能想知道为什么我要花这么多精力在 Haskell 中写出答案.嗯,这是因为我喜欢对我的答案进行类型检查,而且我对如何在 Haskell 中执行此操作相当熟悉.这是一个类型检查的文件,它包含我刚刚指定的所有类型签名(无 IO):http://hpaste.org/69442

You may be wondering why I went through all this effort to write up an answer in Haskell. Well, it's because I like to typecheck my answers, and I'm rather familiar with how to do this in Haskell. Here's a file that typechecks, and has all of the type signatures I just specified (sans IO): http://hpaste.org/69442

好的,现在让我们将其转换为 Scala.首先,Either 助手.

OK, so now let's translate that to Scala. First, the Either helpers.

Scala 开始

// be careful how you use this.
// Scala's subtyping can really screw with you if you don't know what you're doing
def unEither[A](e: Either[A, A]): A = e match {
  case Left(a)  => a
  case Right(a) => a
}

def onLeft[L1, L2, R](e: Either[L1, R], f: L1 => L2) = e match {
  case Left(l) = Left(f(l))
  case Right(r) = Right(r)
}

现在,save 方法

def save = Action { implicit request => unEither( for {
  bound <- onLeft(form.bindFormRequest,
                  err => Ok(withForm(err.getSomeForm))).right

  model = bound.get
  orderNum = generateOrderNum()
  transID <- onLeft(processPayment(model, orderNum),
                    err => Ok(withForm(bound.withGlobalError(...))).right

  ip = request.headers.get("X-Forwarded-For")
  (foo, bar, baz) <- onLeft(dao.createMember(model, ip, orderNum, transID),
                            err => Ok(withForm(bound.withGlobalError(...))).right
} yield allGood(foo, bar, baz) ) }

请注意,<-= 左侧的变量被隐式视为 val,因为它们位于for 块.您应该随意更改 onLeft 以便将其添加到 Either 值上以更好地使用.另外,请确保为 Eithers 导入适当的Monad 实例".

Note that variables on the left hand side of <- or = are implicitly considered to be vals since they are inside of a for block. You should feel free to change onLeft so that it is pimped onto Either values for prettier usage. Also, make sure you import an appropriate "Monad instance" for Eithers.

最后,我只想指出,monadic sugar 的全部目的是扁平化嵌套的函数式代码.所以用它吧!

In conclusion, I just wanted to point out that the whole purpose of monadic sugar is to flatten out nested functional code. So use it!

这篇关于Scala:回归有它的位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆