参考資料:
ScalaコントローラーでのScalaリターンキーワード
処理エラー
EDIT3
これも「最終的な」解決策です。これもDanBurtonのおかげです。
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 whichプロジェクトメソッド(私の場合、sbtルートと子プロジェクトがベースパッケージオブジェクトを拡張する暗黙の特性:
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
Evolutionは、埋め込まれたreturnステートメントから、この小さな白色矮星の密度に移行しました(kudosから@DanBurton、Haskellラスカル;-))
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)
...
}
ダンのonLeftEitherプロジェクションをEitherにポン引きとして追加しました。上記の「プロジェクト」メソッドを使用すると、右バイアスが可能になりますeitherResult project(left-outcome)
。基本的に、フェイルファーストエラーは左として、成功は右として取得されます。これは、オプションの結果を理解のためにフィードする場合には機能しません(一部/なしの結果のみが取得されます)。
私がわくわくしないのは、project(Conflict(param))
;のタイプを指定する必要があることだけです。コンパイラーは、渡されているEitherから左の条件タイプを推測できると思いました。明らかにそうではありません。
いずれにせよ、私がif / else命令型アプローチでやろうとしていたように、機能的アプローチによって組み込みのreturnステートメントが不要になることは明らかです。
編集
機能的に同等のものは次のとおりです。
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
)}
)}
)
これは確かに密集したパワーパックされたコードのブロックであり、そこで多くのことが起こっています。ただし、リターンが埋め込まれた対応する命令型コードは、同様に簡潔であるだけでなく、簡単に理解できると主張します(追跡する後続のカーリーとパレンが少ないという追加の利点があります)
ORIGINAL
命令型の状況に自分自身を見つける; 次の代替アプローチを確認したい(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
}
}
}
この場合、いくつかのif / elseブロック、フォールド、マッチ、または空白を埋める非命令型アプローチをネストしないようにするため、returnの使用が好きです。もちろん、問題はそれが機能しないことです。明示的な戻り型を指定する必要があります。これには、Playマジックが機能しているものをすべて満たす型を指定する方法がまだわからないため、独自の問題があります。def save: Result
、コンパイラimplicit result
が明示的な型を持たないことについて文句を言うので機能しません;-(
とにかく、Playフレームワークの例は、la、la、la、laの幸せなワンショットディールフォールド(エラー、成功)条件を提供しますが、これは現実の世界では常に当てはまるとは限りません™;-)
では、上記のコードブロックと(returnを使用せずに)慣用的に同等なものは何ですか?if / else、match、またはfoldはネストされていると思いますが、ネストされた条件ごとにインデントされて少し見苦しくなります。