Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
def index(id:String) = Action {
getFirstData(id)
}
private def getFirstData(id:String) = {
Cache.get(id) match {
case Some(id2) => getSecondData(id2)
case None => NotFound
}
}
private def getSecondData(id2:String) = {
Cache.get(id2) match {
case Some(result) => Ok(result)
case None => NotFound
}
}
def index(id:String) = Action {
Cache.get(id) match {
case Some(id2) => {
Cache.get(id2) match {
case Some(result) => Ok(result)
case None => NotFound
}
}
case None => NotFound
}
}
def index(id:String) = Action {
  (for {
    id2 <- Cache.get(id)
    result <- Cache.get(id2)
  } yield Ok(result)).getOrElse(NotFound)
}
def index(id:String) = Action {
  (Cache.get(id), Cache.get(id2)) match {
    case (Some(_), Some(_)) => Ok
    case _ => NotFound
  }
}

普通はforを使うと思います

↑だめじゃん...

mumoshu commented Apr 14, 2012

def index(id:String) = Action {
  val maybeId2 = Cache.get(id)
  (maybeId2, maybeId2.flatMap(Cache.get)) match {
    case (Some(_), Some(result)) => Ok(result)
    case (Some(_), None) => NotFound
    case (None,      None) => NotFound
  }
}

こうかな

Owner

rirakkumya commented Apr 14, 2012

チェーンが増えた時困りそう

複雑になったときの懸念点を明確にするために、もうちょい複雑な例にしてみますね。

def index(id:String) = Action {
  Cache.get(id) match {
    case Some(id2) => {
      Cache.get(id2) match {
        case Some(id3) => {
          Cache.get(id3) match {
            case Some(result) => Ok(result)
            case None => NotFound
          }
        }
        case None => BadRequest
      }
    }
    case None => NotFound
  }
}

これをかっこよくどう書くかですよね。

うわーw

Owner

rirakkumya commented Apr 14, 2012

Either使ってチェーンして最後に判定が良いような気がして来た

def index(id:String) = Action {

  def getOrNotFound(id: String): Either[Response, String] = Cache.get(id) match {
    case Some(result) => Right(result)
    case _ => Left(NotFound)
  }
  def getOrBadRequest(id: String): Either[Response, String] = Cache.get(id) match {
    case Some(result) => Right(result)
    case _ => Left(BadRequest)
  }

  getOrNotFound(id)
  .right.flatMap(getOrBadRequest)
  .right.flatMap(getOrNotFound) match {
    case Right(result) => Ok(result)
    case Left(result) => result
  }
}

んー

mumoshu commented Apr 14, 2012

def index(id: String) = Action {
  (Cache.getAs[String](id) match {
    case None => Left(NotFound)
    case Some(id2) => Right(Cache.getAs[String](id2))
  }).right.flatMap {
    case None => Left(BadRequest)
    case Some(id3) => Right(Cache.getAs[String](id3))
  }.fold(
    r => r,
    {
      case None => NotFound
      case Some(result) => Ok(result)
    }
  )
}

(;´∀`)…うわぁ…

パターンマッチなくしてみた

def index(id:String) = Action {
  def getOrNotFound(id: String): Either[Response, String] = 
    Cache.get(id).map(Right.apply).getOrElse(Left(NotFound))
  def getOrBadRequest(id: String): Either[Response, String] = 
    Cache.get(id).map(Right.apply).getOrElse(Left(BadRequest))
  getOrNotFound(id)
  .right.flatMap(getOrBadRequest)
  .right.flatMap(getOrNotFound)
  .fold(identity,Ok(_))
}

map と orElse じゃダメなんでしょうか

Cache.get(id) map { id2 =>
  Cache.get(id2) map { id3 =>
    Cache.get(id3) map { result =>
      OK(result)
    } orElse {
      NotFound
    }
  } orElse {
    BadRequest
  }
} getOrElse {
  NotFound
}

ダメなんでしょうか、とか言っておきつつボクはだめで、何故かというとエラー処理が離れてしまっているから。
なのでまあ

case class RichOption[+T](o: Option[T]) {
  def mapOrElse[R](none: => R)(f: T => R): R = o map f getOrElse r
}
implicit def `Option to RichOption`[T](o: Option[T]) = RichOption(o)

Cache.get(id) mapOrElse(none = NotFound) { id2 =>
  Cache.get(id2) mapOrElse(none = BadRequest) { id3 =>
    Cache.get(id3) mapOrElse(none = NotFound) { OK(_) }
  }
}

こんなのかなあ
コンパイルとかぜんぜん試してないのでおかしかったらスイマセン…

xuwei-k commented Apr 14, 2012

きっと Scalaz を使ってねこはる先生が・・・

きっと Scalaz を使ってねこはる先生が・・・

kxbmap commented Apr 14, 2012

import com.twitter.util.{Return, Throw, Try}

implicit def optW[T](o: Option[T]) = new {
  def toTry[E <: Throwable](t: => E): Try[T] = o map { Return(_) } getOrElse Throw(t)
}

class NotFoundException extends Exception
class BadRequestException extends Exception

def index(id: String) = Action {
  ((for {
    id2 <- Cache.getAs[String](id).toTry(new NotFoundException)
    id3 <- Cache.getAs[String](id2).toTry(new BadRequestException)
    result <- Cache.getAs[String](id3).toTry(new NotFoundException)
  } yield Ok(result)) handle {
    case _: NotFoundException => NotFound
    case _: BadRequestException => BadRequest
  })()
}

Try便利だよ!
コンパイルできなかったので getAs[String] にしました。

scalaz は知りませんが haskell なら

getCache :: String -> Maybe String
nanika id = do
  id2 <- getCacheOr id NotFound
  id3 <- getCacheOr id2 BadRequest
  result <- getCacheOr id3 NotFound
  return result
  where
    getCacheOr id nothing = fromMaybe (Left nothing) $ Right <$> getCache id

こんなのですかねえ。Either a が Monad なのがズルイですね…

👍

以下 Scala の Either a が Monad じゃないことを愚痴るスレ

xuwei-k commented Apr 14, 2012

とにかくこういうときに、 Option とか Either に対して match 使ってるだけだと綺麗にならないから match 使っちゃだめっていうのだけは、なんとなく経験上感じてて、こういうの突き詰めていくと標準ライブラリだけだと微妙に不便だし結局 Scalaz っぽくなるっていう。

Option とか Either に対する match (というかmatch式そのもの?) ってこういうことになっちゃって大量にでてくると綺麗に合成不可能で冗長になりがちだから、
例として Optionに対する match の適用の紹介 ばかりが巷に溢れてて、それより上の段階(すでに定義されてるメソッドや、Scalazのように型クラスを使って綺麗に合成する) の紹介が少ないのはわりとあれ

Applicative なのか Monad なんのかわからないけど、既存の Scalaz の型クラスに適用できそうだけどなぁ

mumoshu commented Apr 14, 2012

👍

xuwei-k commented Apr 14, 2012

それ Scalaz 使えば(ry

以下 Scala の Either a が Monad じゃないことを愚痴るスレ

ところで、 Either 自体は、Scalaz 由来らしいですけど、なぜ Haskell の Either と異なるようになっちゃんたんですかね。
お客様のなかに誰かその経緯を知ってる方はいらっしゃいませんか

👍

どうでもいい話なんですが。
初学者には「構造」を意識させたほうがいいと思うので、最初は match で書いてればいいと思うんですけどねえ。

関数型言語とかで ADT 自然に使ってる人たちはその辺の理解が最初からあるわけですけど、
Java からきた人とかはそういうのないわけで…

便利げなメソッドつかったりするのは後でもいいんじゃないでしょうか。

あと Option にそもそも List や Either にあるような catamorphism な何か(というか fold ですけど)があれば mapOrElse とかなくても

get(id).fold(none = NotFound, some = { id2 => .... })

みたいに書けるし fold いれていい気がしますね

import scalaz._
import Scalaz._

def index(id:String) = Action {
  def getOrNotFound(id: String): Either[Response, String]
    = Cache.get(id).map(_.right).getOrElse(NotFound.left)
  def getOrBadRequest(id: String): Either[Response, String]
    = Cache.get(id).map(_.right).getOrElse(BadRequest.left)
  (getOrNotFound(id) >>= (getOrBadRequest) >>= (getOrNotFound))
  .fold(identity, Ok(_))
}
import scalaz._
import Scalaz._

def index = Action {
    Cache.getAs[String](id).toSuccess(NotFound).flatMap(Cache.getAs[String](_).toSuccess(BadRequest)).flatMap(Cache.getAs[String](_).toSuccess(NotFound)).fold(identity, Ok(_))
  }
}

一行www

ところで、 Either 自体は、Scalaz 由来らしいですけど、

???

なぜ Haskell の Either と異なるようになっちゃんたんですかね。
お客様のなかに誰かその経緯を知ってる方はいらっしゃいませんか

歴史の話はあまり知りませんが、実際のところ Either という名前に「あってる」のは scala のほうだと思います。
Haskell の Either は Either a を「a というエラーが起こりえるコンテキスト」として扱うという「文化」があって、
Right は「右」と「正しい」でかけてる、みたいなそんな感じですよね。

なんかでも、調べたところ scalaz は scala の Either a を Haskell like なモナドにしてくれるらしいのでそれ使うもしくは自分で定義すれば解決ですね。
というかそれよしださんにこの前伺った気がする…忘れてた。てへぺろ

xuwei-k commented Apr 14, 2012

やっぱり流石ねこはる先生 (無駄に横に長くてある意味読みづらい) ワンライナー!

よしだくん、ひどい ><

やはりScalazはさいきょーですね!

xuwei-k commented Apr 14, 2012

ところで、 Either 自体は、Scalaz 由来らしいですけど、

???

すいません多分ウソついたかもしれません。調べたら Scalaz を作り始めたかなり初期の時点(Scala の versionは2.7.2
)で

https://github.com/scalaz/scalaz/blob/8648716fd7a5e7967616c1a0a0301f607f537d2c/src/main/scalaz/EitherW.scala
https://github.com/scalaz/scalaz/blob/8648716fd7a5e7967616c1a0a0301f607f537d2c/scalaz.iml#L19

Eitherは存在して、それを拡張するためのものもScalazに存在してますね。

ただ、 標準ライブラリの Either の作者は Scalaz の作者と同じ なので
https://github.com/scala/scala/blob/v2.9.2/src/library/scala/Either.scala#L64

  • Either がScala標準ライブラリに入った => Haskell と違うし色々不便だから(ちょっと設計ミスしたなぁーと思いつつ) Eitherを拡張するものを後からScalazに入れた

のではなく、

  • 最初から意図して Haskell と異なる形で、EitherをScala標準ライブラリに入れた

のかなぁーとかそのあたりが気になる

ボクは後者だと思うんですけどねえ、本人にきくのが早そうです

ボクはこう書くかな~

def index(id: String) = Action {
  val id2 = Cache.get(id)
  val id3 = id2.flatMap(Cache.get)
  val res = id3.flatMap(Cache.get)

  (id2, id3, res) match {
    case (Some(_), Some(_), Some(result)) => Ok(result)
    case (Some(_), None   , None)         => BadRequest
    case _                                => NotFound
  }
}

わーい最初やろうと思ってたことできたよー

https://gist.github.com/2383028 こういうコンテナがあれば

  implicit def optW[T](o: Option[T]) = new {
    def calc[R](r: => R): Container[R, T] = o map { Calculating(_) } getOrElse Result(r)
  }

  def index(id: String) = Action {
    (for {
      id2 <- Cache.get(id).calc(NotFound)
      id3 <- Cache.get(id2).calc(BadRequest)
      result <- Cache.get(id3).calc(NotFound)
    } yield Ok(result)).get
  }

という感じで書けまする。

お、いいですね。ちょっとnet.liftweb.common.Box思い出しました。

<|*|>だとだめで結局flatMapになってねこはる先生と同じになって悔しいビクンビクンッ

import scalaz._;import Scalaz._

trait OptionWR[T] {
  val v:Option[T]
  def orResult(r:Result) = v.toSuccess[Result](r)
}

implicit def toOptionWR[T](opt:Option[T]) = new OptionWR[T]{ val v = opt }

def index(id: String) = Action { (for{ 
  id2 <- Cache.get(id).orResult(NotFound)
  id3 <- Cache.get(id2).orResult(BadRequest)
  result <- Cache.get(id3).orResult(NotFound)
} yield { result }) fold(identity, Ok(_)) }

中2心、大切だと思います

import scalaz._
import Scalaz._
implicit val m = Validation.validationMonad[Status]
def index = Action {
  (Cache.getAs[String](id) toSuccess NotFound >>=
  (Cache.getAs[String](_) toSuccess BadRequest) >>=
  (Cache.getAs[String](_) toSuccess NotFound) fail) ||| (Ok(_))
}

もしかしてこれでよかったんじゃ……

  def index(id: String) = Action {
    (for {
      id2 <- Cache.get(id).toRight(NotFound).right
      id3 <- Cache.get(id2).toRight(BadRequest).right
      result <- Cache.get(id3).toRight(NotFound).right
    } yield result).fold(identity, Ok.apply)
  }

👍

不勉強で (Right|Left)Projection について全く誤解していました。Projection の名が示すとおり射影なんですね。コレクションでいうところの view のような。
Haskell のより right とか left とかしないといけなくて面倒な代わりに、left のコンテキストに切り替えて計算とかできるので、こっちのほうが汎用性がありますね。
例えば、エラーレスポンスだけに何か適用したい、みたいな時に下のようにかける。
で、そういうことなら恐らく Left と Right で型が一致している時に限り使えるメソッドもあるだろうと思ったら矢張り implicit conversion が提供されていますね

  def index(id: String) = Action {
    (for {
      id2 <- Cache.get(id).toRight(NotFound).right
      id3 <- Cache.get(id2).toRight(BadRequest).right
      result <- Cache.get(id3).toRight(NotFound).right
    } yield OK(result))
    .left.map(_.withHeaders(headersForError)).merge
  }

例によってコンパイルは試してません

kxbmap commented Apr 14, 2012

👍

def index(id: String) = Action {
  (for {
    id2 <- Cache.get(id).toRight(NotFound).right
    id3 <- Cache.get(id2).toRight(BadRequest).right
    result <- Cache.get(id3).toRight(NotFound).right
  } yield Ok(result)).merge
}

さよならidentity

ということで、よしださんの質問に対する回答は恐らく後者の
「最初から意図して Haskell と異なる形で、EitherをScala標準ライブラリに入れた」
でよいはずです。Haskell より汎用的かつ高機能です(swap や merge は Haskell 標準は提供しない)

ハー全くお恥ずかしい限りです。まあ mapOrElse も気に入ってるんですけどね。

Owner

rirakkumya commented Apr 15, 2012

みなさんの意見を参考にして、書きたかったコードが書けました。
冗長かもしれませんが、お仕事で使うには改変にも柔軟に対応しないといけないので。
これから使わせて頂きます。
ありがとうございました。scalaコミュニティ最高!

def index(id:String) = Action {
  (for {
    id2 <- getFirstData(id1).right
    result <- getSecondData(id2).right
  } yield Ok(result)).merge
}
private def getFirstData(id:String) = {
  Cache.get(id) match {
    case Some(r) if r.startsWith("x") => Right("foo")
    case Some(r) if r == "badcode" => Left(BadRequest)
    case Some(r) => Right(r)
    case None => Left(NotFound)
  }
}
private def getSecondData(id:String) = {
  Cache.get(id) match {
    case Some(r) => Right(r)
    case None => Left(NotFound)
  }
}

またつまらない茶々入れになってしまうのですが、get(First|Second)Data みたいなのは、下のように書くとハッピーかもしれません。

def getFirstData(id: String) = {
  def validateWith(id: String)(cont: String => String) = for {
    r <- get(id).toRight(NotFound).right
    r2 <- Either.cond(r != "badrequest", r, BadRequest).right
  } yield cont(r2)

  validateWith(id) {
    case x if x.startsWith("x") => "foo"
    case x => x
  }
}
Owner

rirakkumya commented Apr 15, 2012

たぶん後でunhappyになると思います…

一点だけ気になったので

    case Some(r) if r.head == 'x' => Right("foo")

これは r == "" のときにNoSuchElementExceptionが飛ぶので、

    case Some(r) if r.startsWith("x") => Right("foo")

にするべきでしょう。

Owner

rirakkumya commented Apr 15, 2012

確かに。ツッコミありがとうございます。

Owner

rirakkumya commented Apr 15, 2012

Action拡張すれば使い易くなりそう。 Right => Continue Left => Break でAlias切るか。 名前がイマイチだけど。良いネーミング無いかな。

Owner

rirakkumya commented May 3, 2012

forもscalazも使わずにシンプルに書けた
playで使うならこれが一番良さそう

case class Bind[a1,a2](x:Either[a1,a2]) {
  def >>=[b](f:a2 => Either[a1,b]):Either[a1,b] = x.right flatMap f
}

implicit def either2Bind[a](s:Either[Result,a]) = Bind(s)

def index(id:String) = Action {
  Right(id) >>= firstData >>= secondData >>= result merge
}

private def firstData(id:String) = {
  Cache.get(id) match {
    case Some(r) if r.startsWith("x") => Right("foo")
    case Some(r) => Right(r)
    case None => Left(NotFound)
  }
}
private def secondData(id:String) = {
  Cache.get(id) match {
    case Some(r) if r == "badcode" => Left(BadRequest)
    case Some(r) => Right(r)
    case None => Left(NotFound)
  }
}
private def result(id:String) = Right(Ok(id))
Owner

rirakkumya commented May 3, 2012

これでもいいけど、上の書き方の方が保守しやすいな

case class Bind[a1,a2](x:Either[a1,a2]) {
  def >>=[b](f:a2 => Either[a1,b]):Either[a1,b] = x.right flatMap f
}

implicit def either2Bind[a](s:Either[Result,a]) = Bind(s)

def index(id:String) = Action {
  Right(id) >>= { id => 
    Cache.get(id) match {
      case Some(r) if r.startsWith("x") => Right("foo")
      case Some(r) => Right(r)
      case None => Left(NotFound)
    }
  } >>= { id => 
    Cache.get(id) match {
      case Some(r) if r == "badcode" => Left(BadRequest)
      case Some(r) => Right(r)
      case None => Left(NotFound)
    }
  } >>= { r => Right(Ok(r)) } merge
}

xuwei-k commented Jun 6, 2013

https://github.com/scalaz/scalaz/blob/v7.0.0/core/src/main/scala/scalaz/Kleisli.scala#L15

Kleisli の合成だった

trait Response
case object BadRequest extends Response
case object NotFound extends Response
case class Ok(message: String) extends Response

object Cache{ def get(id: String): Option[String] = ??? }

import scalaz._,Scalaz._

val getOrNotFound = Kleisli[({type λ[+α]=Either[Response, α]})#λ, String, String]{
  id => Cache.get(id) toRight NotFound
}

val getOrBad = Kleisli[({type λ[+α]=Either[Response, α]})#λ, String, String]{
  id => Cache.get(id) toRight BadRequest
}

def index(id: String) = (getOrNotFound >=> getOrBad >=> getOrNotFound) run id map Ok merge
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment