scala:如何链接不同类型的部分函数

时间:2013-01-17 18:14:57

标签: scala

我有一个使用链式部分函数处理Web服务请求的模式(我认为这是一个责任链模式?)。在我的例子中,假设请求有两个参数,一个字符串Id和一个日期。有一个涉及id的验证步骤,一个检查日期的验证步骤,以及最后使用两者的一些业务逻辑。所以我实现了它们:

object Controller {
  val OK = 200
  val BAD_REQUEST = 400

  type ResponseGenerator = PartialFunction[(String, DateTime), (String, Int)]

  val errorIfInvalidId:ResponseGenerator = {
    case (id, _) if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
  }

  val errorIfFutureDate:ResponseGenerator = {
    case (_, date) if (date.isAfter(DateTime.now)) => ("Error, date in future!", BAD_REQUEST)
  }

  val businessLogic:ResponseGenerator = {
    case (id, date) => {
      // ... do stuff
      ("Success!", OK)
    }
  }

  def handleRequest(id:String, date:DateTime) = {
    val chained = errorIfInvalidId orElse errorIfFutureDate orElse businessLogic
    val result: (String, Int) = chained(id, date)

    // make some sort of a response out of the message and status code
    // e.g. in the Play framework...
    Status(result._2)(result._1)
  }
}

我喜欢这种模式,因为它非常具有表现力 - 通过查看链接函数,您可以轻松掌握控制器方法逻辑。而且,我可以轻松地混合和匹配不同请求的不同验证步骤。

问题在于,当我尝试扩展此模式时,它开始崩溃。假设我的下一个控制器需要一个我要验证的id,但没有date参数,也许它有一些需要验证的第三种类型的新参数。我不想继续将该元组扩展到(String, DateTime, Other)并且必须传入虚拟DateTime或其他。我想要部分函数接受不同类型的参数(它们仍然可以返回相同的类型)。但我无法弄清楚如何撰写它们。

对于具体问题 - 假设示例验证器方法更改为如下所示:

val errorIfInvalidId:PartialFunction[String, (String, Int)] = {
  case id if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}

val errorIfInvalidDate:PartialFunction[DateTime, (String, Int)] = {
  case date if (date.isAfter(DateTime.now)) => ("Error, date in future!", BAD_REQUEST)
}

我还可以将它们连在一起吗?看起来我应该能够将元组映射到它们,但我无法弄清楚如何。

3 个答案:

答案 0 :(得分:6)

我很喜欢使用scalaz验证这样的事情。它为您提供了很多控制权,可以控制错误以及如何处理错误。这是一个使用你的控制器的例子:

import scalaz._
import Scalaz._

object Controller {
  val OK = 200
  val BAD_REQUEST = 400

  case class Response(response: String, status: Int)

  def validateIfInvalidId(id: String) = (id == "invalid") ?
    Response("Error, Invalid ID!", BAD_REQUEST).fail[String] |
    id.success[Response]


  def validateIfFutureDate(date: DateTime, currentDate: DateTime = DateTime.now) = (date.isAfter(currentDate)) ?
    Response("Error, date in future!", BAD_REQUEST).fail[DateTime] |
    date.success[Response]

  def handleRequest(id: String, date: DateTime) = {
    val response = for {
      validatedId <- validateIfInvalidId(id)
      validatedDate <- validateIfFutureDate(date)
    } yield {
      // ... do stuff
      Response("Success!", OK)
    }

    // make some sort of a response out of the message and status code
    // e.g. in the Play framework...
    response.fold(
      failure => Status(failure.response, failure.status),
      success => Status(success.response, success.status)
    )
  }
}

您可以将不同的验证功能移到他们自己的世界中,然后随时使用scala中的for comprehension组合它们。

答案 1 :(得分:0)

好的,我找到了一种方法,这似乎并不太糟糕。最初我认为将部分函数的“基础”版本包含在另一个采用元组的部分函数中可能会起作用。但我无法弄清楚如何做到这一点,直到我找到了在案件保护声明中使用isDefined的显而易见的想法。像这样:

// "base" version
val errorIfInvalidId:PartialFunction[String, (String, Int)] = {
  case id if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}

// wrapped to take tuple as parameter
val wrappedErrorIfInvalidId:PartialFunction[(String, DateTime), (String, Int)] = {
  case (id, _) if (errorIfInvalidId.isDefinedAt(id)) => errorIfInvalidId(id)
}

这种方法是可用的,但我仍然想知道是否没有更直接的方法来实现它。 (在我有机会玩一下之后,我也可以切换到Noah建议的Scalaz验证。)

答案 2 :(得分:0)

你可以让PartialFunction更通用,使它成为PartialFunction [Any,(String,Int)] 但是,它会慢一些。不知道PartialFunction下的匹配机制