Skip to content

Instantly share code, notes, and snippets.

Embed
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
}
}
@gakuzzzz

This comment has been minimized.

Copy link

gakuzzzz commented Apr 14, 2012

def index(id:String) = Action {
  (for {
    id2 <- Cache.get(id)
    result <- Cache.get(id2)
  } yield Ok(result)).getOrElse(NotFound)
}
@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

def index(id:String) = Action {
  (Cache.get(id), Cache.get(id2)) match {
    case (Some(_), Some(_)) => Ok
    case _ => NotFound
  }
}

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

↑だめじゃん...

@mumoshu

This comment has been minimized.

Copy link

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
  }
}

こうかな

@rirakkumya

This comment has been minimized.

Copy link
Owner Author

rirakkumya commented Apr 14, 2012

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

@gakuzzzz

This comment has been minimized.

Copy link

gakuzzzz 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
  }
}

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

うわーw

@rirakkumya

This comment has been minimized.

Copy link
Owner Author

rirakkumya commented Apr 14, 2012

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

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

This comment has been minimized.

Copy link

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)
    }
  )
}

(;´∀`)…うわぁ…

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

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

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(_))
}
@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

This comment has been minimized.

Copy link

xuwei-k commented Apr 14, 2012

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

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

@kxbmap

This comment has been minimized.

Copy link

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] にしました。

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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 なのがズルイですね…

@pomu0325

This comment has been minimized.

Copy link

pomu0325 commented Apr 14, 2012

👍

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

@xuwei-k

This comment has been minimized.

Copy link

xuwei-k commented Apr 14, 2012

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

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

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

@mumoshu

This comment has been minimized.

Copy link

mumoshu commented Apr 14, 2012

👍

@xuwei-k

This comment has been minimized.

Copy link

xuwei-k commented Apr 14, 2012

それ Scalaz 使えば(ry

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

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

@gakuzzzz

This comment has been minimized.

Copy link

gakuzzzz commented Apr 14, 2012

👍

@xuwei-k

This comment has been minimized.

Copy link

xuwei-k commented Apr 14, 2012

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

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

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

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

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

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

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(_))
}
@halcat0x15a

This comment has been minimized.

Copy link

halcat0x15a commented Apr 14, 2012

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(_))
  }
}
@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

一行www

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

???

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

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

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

@xuwei-k

This comment has been minimized.

Copy link

xuwei-k commented Apr 14, 2012

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

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

よしだくん、ひどい ><

@halcat0x15a

This comment has been minimized.

Copy link

halcat0x15a commented Apr 14, 2012

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

@xuwei-k

This comment has been minimized.

Copy link

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標準ライブラリに入れた

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

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

@ogatatsu

This comment has been minimized.

Copy link

ogatatsu commented Apr 14, 2012

ボクはこう書くかな~

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
  }
}
@gakuzzzz

This comment has been minimized.

Copy link

gakuzzzz commented Apr 14, 2012

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

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
  }

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

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

@yuroyoro

This comment has been minimized.

Copy link

yuroyoro commented Apr 14, 2012

<|*|>だとだめで結局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(_)) }
@halcat0x15a

This comment has been minimized.

Copy link

halcat0x15a commented Apr 14, 2012

中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(_))
}
@gakuzzzz

This comment has been minimized.

Copy link

gakuzzzz 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 result).fold(identity, Ok.apply)
  }
@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

👍

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 14, 2012

http://togetter.com/li/287993
貼っておこう

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

不勉強で (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

This comment has been minimized.

Copy link

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

@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 14, 2012

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

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

@rirakkumya

This comment has been minimized.

Copy link
Owner Author

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)
  }
}
@lyricallogical

This comment has been minimized.

Copy link

lyricallogical commented Apr 15, 2012

またつまらない茶々入れになってしまうのですが、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
  }
}
@rirakkumya

This comment has been minimized.

Copy link
Owner Author

rirakkumya commented Apr 15, 2012

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

@tototoshi

This comment has been minimized.

Copy link

tototoshi commented Apr 15, 2012

一点だけ気になったので

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

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

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

にするべきでしょう。

@rirakkumya

This comment has been minimized.

Copy link
Owner Author

rirakkumya commented Apr 15, 2012

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

@rirakkumya

This comment has been minimized.

Copy link
Owner Author

rirakkumya commented Apr 15, 2012

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

@rirakkumya

This comment has been minimized.

Copy link
Owner Author

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))
@rirakkumya

This comment has been minimized.

Copy link
Owner Author

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

This comment has been minimized.

Copy link

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
You can’t perform that action at this time.