vir*_*yes 13 scala type-inference return playframework-2.0
参考:
Scala 在scala控制器中返回关键字
处理错误
EDIT3
这是"最终"解决方案,再次感谢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)
}
Run Code Online (Sandbox Code Playgroud)
然后是pimp'd Either项目方法,放在你的应用程序的某个地方(在我的例子中,一个impbits特性,sbt root和子项目扩展了它们的基础包对象:
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)
Run Code Online (Sandbox Code Playgroud)
EDIT2
Evolution已经从嵌入式返回语句转变为密度的这个小白矮星(感谢@DanBurton,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)
...
}
Run Code Online (Sandbox Code Playgroud)
我已经使用上面的"项目"方法将Dan的onLeft Either投影作为pimp添加到了Either,这允许右偏eitherResult project(left-outcome).基本上你将失败优先错误视为左派,成功作为右派,这在将选项结果提供给理解时不会起作用(你只得到一些/无结果).
我唯一不感兴趣的是必须指定类型project(Conflict(param)); 我认为编译器能够从传递给它的Either推断左条件类型:显然不是.
无论如何,很明显,该功能的方法避免了需要嵌入式return语句,因为我试图用的if/else势在必行进场做.
编辑
功能等同于:
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
)}
)}
)
Run Code Online (Sandbox Code Playgroud)
这肯定是一个密集的强大的代码块,很多发生在那里; 但是,我认为相应的具有嵌入式返回的命令式代码不仅同样简洁,而且更容易理解(附加的好处是更少的尾随曲线和parens来跟踪)
原始
在紧急情况下找到自己; 希望看到以下的替代方法(由于使用了return关键字并且方法缺少显式类型,因此无效):
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我喜欢使用return,因为你避免嵌套几个if/else块,折叠,匹配,或填空的非命令方法.问题当然是它不起作用,必须指定显式返回类型,这有其自身的问题,因为我还没有弄清楚如何指定满足Play魔法工作的类型 - 不,def save: Result,不起作用编译器然后抱怨implicit result现在没有明确的类型;-(
无论如何,Play框架示例提供la,la,la,la happy 1-shot-deal fold(错误,成功)条件,这在现实世界中并非总是如此;-)
那么上面代码块的惯用等价物(不使用return)是什么?我相信它会被嵌套的if/else,比赛,或折叠,从而变得有点难看,每一层嵌套条件缩进.
Dan*_*ton 27
所以作为一个Haskeller,显然在我看来,一切的解决方案都是Monads.和我一起进入一个简化的世界(简化为我,就是这样),你的问题在Haskell中,你有以下类型要处理(作为Haskeller,我对类型有这种迷信):
bindFormRequest :: Request -> Form -> BoundForm
hasErrors :: BoundForm -> Bool
processPayment :: Model -> OrderNum -> TransID
isEmpty :: TransID -> Bool
Run Code Online (Sandbox Code Playgroud)
我们暂停一下 在这一点上,我有点畏缩了一下,在boundFormHasErrors和transIDisEmpty.这些事情都意味着失败的可能性被注入到 BoundForm和TransID分别.那很糟.相反,应该将失败的可能性分开.请允许我提出这个替代方案:
bindFormRequest :: Request -> Form -> Either FormBindError BoundForm
processPayment :: Model -> OrderNum -> Either TransError TransID
Run Code Online (Sandbox Code Playgroud)
感觉好一点,这些Eithers正在引领使用Either monad.让我们写一些更多的类型.我会忽略,OK因为几乎所有事情都包含在内; 我有点捏造,但概念仍然会翻译相同.相信我; 我最终将它带回Scala.
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
Run Code Online (Sandbox Code Playgroud)
好的,现在我要做一些有点不可思议的事情,让我告诉你为什么.Either monad就是这样的:只要你击中一个Left,你就会停下来.(我选择这个monad来模仿早期的回报是否有任何意外?)这一切都很好,但是我们总是想停下来Action,所以停止使用a FormBindError不会削减它.因此,让我们定义两个函数,让我们以这样的方式处理Eithers,如果我们发现一个,我们可以安装更多的"处理" 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
Run Code Online (Sandbox Code Playgroud)
在这一点上,在Haskell中,我会谈论monad变换器,并在堆叠EitherT之上IO.但是,在Scala中,这不是一个问题,所以无论我们在哪里看到IO Foo,我们都可以假装它是一个Foo.
好吧,我们来写吧save.我们将使用do语法,后来将它翻译成Scala的for语法.回想一下for语法,你可以做三件事:
<-(这与Haskell相当<-)=(这相当于Haskell的let)if(这与Haskell的guard功能相当,但我们不会使用它,因为它不会让我们控制产生的"异常"值)然后最后我们可以yield,这与returnHaskell中的相同.我们将自己限制在这些事情上,以确保从Haskell到Scala的转换是顺利的.
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
Run Code Online (Sandbox Code Playgroud)
注意什么?它看起来几乎与您在命令式风格中编写的代码相同!
你可能想知道我为什么要经历所有这些努力来在Haskell中写出答案.好吧,这是因为我喜欢检查我的答案,而且我对Haskell中如何做到这一点非常熟悉.这是一个typechecks的文件,并且包含我刚刚指定的所有类型签名(sans IO):http://hpaste.org/69442
好的,现在让我们把它翻译成Scala.一,Either助手.
这里开始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)
}
Run Code Online (Sandbox Code Playgroud)
现在,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) ) }
Run Code Online (Sandbox Code Playgroud)
请注意,左侧的变量<-或=隐含地被认为是vals,因为它们位于for块内.你应该随意改变,onLeft以便Either为更漂亮的用法而加以限制.另外,请确保为Eithers 导入适当的"Monad实例" .
总之,我只想指出monadic sugar的全部目的是为了压缩嵌套的功能代码.所以用吧!
[编辑:在Scala中,你必须"正确偏见" Either才能使它们与for语法一起使用.这是通过添加.right到Either右侧的值来完成的<-.无需额外进口.这可以在onLeft更漂亮的代码内部完成.另见:https://stackoverflow.com/a/10866844/208257 ]
一些嵌套defs怎么样?
def save = Action { implicit request =>
def transID = {
val model = bound.get
val orderNum = generateOrderNum()
processPayment(model, orderNum)
}
def result = {
val ip = request.headers.get("X-Forwarded-For")
dao.createMember(model, ip, orderNum, transID)
}
val bound = form.bindFromRequest
if(bound.hasErrors) Ok(withForm(bound))
else if(transID.isEmpty) Ok(withForm( bound.withGlobalError(...) ))
else result match {
case Left(_) =>
Ok(withForm( bound.withGlobalError(...) ))
case Right((foo, bar, baz)) =>
// all good: generate pdf, email, redirect with success msg
}
}
}
Run Code Online (Sandbox Code Playgroud)