Skip to content

Instantly share code, notes, and snippets.

@rirakkumya
Created April 14, 2012 05:55
  • Star 44 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save rirakkumya/2382341 to your computer and use it in GitHub Desktop.
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
}
}
@lyricallogical
Copy link

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

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

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

@tototoshi
Copy link

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
Copy link

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
Copy link

一行www

@lyricallogical
Copy link

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

???

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

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

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

@xuwei-k
Copy link

xuwei-k commented Apr 14, 2012

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

@lyricallogical
Copy link

よしだくん、ひどい ><

@halcat0x15a
Copy link

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

@xuwei-k
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
Copy link

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

@ogatatsu
Copy link

ボクはこう書くかな~

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
Copy link

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

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
Copy link

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

@yuroyoro
Copy link

<|*|>だとだめで結局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
Copy link

中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
Copy link

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

  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
Copy link

👍

@tototoshi
Copy link

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

@lyricallogical
Copy link

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

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

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

@rirakkumya
Copy link
Author

みなさんの意見を参考にして、書きたかったコードが書けました。
冗長かもしれませんが、お仕事で使うには改変にも柔軟に対応しないといけないので。
これから使わせて頂きます。
ありがとうございました。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
Copy link

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

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

@tototoshi
Copy link

一点だけ気になったので

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

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

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

にするべきでしょう。

@rirakkumya
Copy link
Author

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

@rirakkumya
Copy link
Author

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

@rirakkumya
Copy link
Author

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
Copy link
Author

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

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