2012-10-04 1 views
4

다음은 매우 일반적인 플레이 프레임 워크 2 컨트롤러입니다 :스칼라 :이 코드 조각의 가독성과 스타일을 향상

def save(ideaId : Long) = CORSAction { request => 
    Idea.findById(ideaId).map { idea => 
    request.body.asJson.map { json => 
     json.asOpt[Comment].map { comment => 
     comment.copy(idea = idea).save.fold(
      errors => JsonBadRequest(errors), 
      comment => Ok(toJson(comment).toString) 
     ) 
     }.getOrElse  (JsonBadRequest("Invalid Comment entity")) 
    }.getOrElse  (JsonBadRequest("Expecting JSON data")) 
    }.getOrElse   (JsonBadRequest("Could not find idea with id '%s'".format(ideaId))) 
} 

나는 그것이 조금 짜증나는 모든 중첩 된 .maps을 찾아보고, 나는 또한 약간을 찾아 각 오류 처리가 맨 아래에 있음 지루하다.

더 읽기 쉽도록 기능을 향상시키고 동시에 관용적 인 스칼라 코드로 남겨 두겠다.

나는 아마이 (가 컴파일되지 않습니다 여전히 seudo 코드의) 같은 것을 생각

def save(ideaId : Long) = CORSAction { request => 

    val idea = Idea.findById(ideaId).getOrElse(
    return JsonBadRequest("Could not find idea with id '%s'".format(ideaId))) 

    val json = request.body.asJson.getOrElse(
    return JsonBadRequest("Expecting JSON data")) 

    val comment = json.asOpt[Comment].getOrElse(
    return JsonBadRequest("Invalid Comment entity")) 

    comment.copy(idea = idea).save.fold(
    errors => JsonBadRequest(errors), 
    comment => Ok(toJson(comment).toString) 
) 

} 

추신 : 나는 ... return 문을 피하기 위해 더 나은 것 알고

+8

축하합니다. 방금 모나드를 발명했습니다! –

+0

'return's를 스타터로 제거하십시오. 오, 기다려, 그들은 '저장'방법의 흐름을 깨뜨리는거야? – pedrofurla

답변

7

우선 단순화. 나는 String을 반환하는 세 가지 방법이 있다고 가정 Option[String] : 내가 길을 따라 None을받을 경우 해당 오류 메시지를 반환 파이프이를 통해 문자열을하고 방법을 원하는

def foo(s: String): Option[String] = if (s.size >= 4) Some(s + "1") else None 
def bar(s: String): Option[String] = if (s(0) != 'A') Some(s + "2") else None 
def baz(s: String): Option[String] = if (s toSet ' ') Some(s + "3") else None 

. 나는 이것을 쓸 수있다 :

def all(s: String): Either[String, String] = 
    foo(s).map { x => 
    bar(x).map { y => 
     baz(y).map { z => 
     Right(z) 
     } getOrElse Left("Doesn't contain a space!") 
    } getOrElse Left("Starts with an A!") 
    } getOrElse  Left("Too short!") 

그러나 이것은 옳지 않다. 우리는 훨씬 깨끗한 버전 작성하는 for -comprehension 및 OptiontoRight 방법을 사용할 수 있습니다 : 그것은 비어 경우 Option

toRight(msg)를 호출
def all(s: String): Either[String, String] = for { 
    x <- (foo(s) toRight "Too short!"    ).right 
    y <- (bar(x) toRight "Starts with an A!"  ).right 
    z <- (baz(y) toRight "Doesn't contain a space!").right 
} yield z 

우리에게 Left(msg)을 제공을하고, 그렇지 않으면 Right(whatever). Scala의 Either은 오른쪽 바이어스가 아니므로 Either의 올바른 투영을 .right으로 가져 가야합니다.

귀하의 경우 등가은 다음과 같이 될 것이다 : 원하는 구문으로

def save(ideaId: Long) = CORSAction { request => 
    val saveResult = for { 
    idea <- (Idea.findById(ideaId) toRight "Could not find id" ).right 
    json <- (request.body.asJson toRight "Invalid Comment entity").right 
    comment <- (json.asOpt[Comment] toRight "Expecting JSON data" ).right 
    result <- comment.copy(idea = idea).save().right 
    } yield result 

    saveResult.fold(
    error => JsonBadRequest(error), 
    comment => Ok(toJson(comment).toString) 
) 
} 

매우 간결하지 않습니다,하지만 오류 메시지가 더 논리적 인 장소에 나타납니다, 그리고 우리가 제거 왔 못생긴 둥지.

+0

중첩 된지도가 실제로 flatMaps라고 생각합니다. 그렇지 않으면 결국 [끝내기 [...], 끝내기 ...] 중 하나가됩니다. 그렇지 않습니까? – pedrofurla

+1

@pedrofurla : 중첩 된 버전에서는 모든 레벨에서'getOrElse'가 이미 그 평탄화를하고 있습니다. –

+1

@ om-non-nom : 편집 해 주셔서 감사합니다! 나는 스칼라에 결코 * 그 단어를 타이핑하지 않는다고 맹세합니다! –