【发布时间】:2017-12-04 18:44:33
【问题描述】:
这是下面代码的 scaste:https://scastie.scala-lang.org/bQMGrAKgRoOFaK1lwCy04g
我有两个 JSON API 端点。首先items.cgi,返回item对象列表,格式如下
$ curl http://example.com/items.cgi
[
...
{ sn: "KXB1333", ownerId: 3, borrowerId: 0 },
{ sn: "KCB1200", ownerId: 1, borrowerId: 2 },
...
]
borrowerId == 0 表示项目没有借阅者。
第二,users.cgi,返回id查询参数指定的用户
$ curl http://example.com/user.cgi?id=1
{ id: 1, name: "frank" }
API 可能很糟糕,但我必须处理它。现在在 Scala 中,我想使用这个不错的数据模型
case class User(id: Int, name: String)
case class Item(sn: String, owner: User, borrower: Option[User])
我还有以下用于执行 HTTP 请求的方法
case class ApiFail(reason: String)
def get[T](url: String): Either[ApiFail, T] = ??? /* omitted for brevity */
get() 函数使用一些魔法从 URL 中获取 JSON 并从中构造一个 T(它使用一些库)。在 IO 失败或 HTTP 状态错误时,它会返回 Left。
我想写如下函数
def getItems: Either[ApiFail, Seq[Item]]
它应该获取项目列表,为每个项目获取链接的用户并返回Items 的新列表,或者在任何 HTTP 请求失败时失败。 (对于具有相同 ID 的用户可能会有多余的请求,但我还不关心 memoization/缓存。)
目前我只写了这个函数
def getItems: Either[ApiFail, Seq[Either[ApiFail, Item]]]
检索某些用户的失败仅对相应的项目而不是整个结果是致命的。这是实现
def getItems: Either[ApiFail, Seq[Either[ApiFail, Item]]] = {
case class ItemRaw(sn: String, ownerId: Int, borrowerId: Int)
get[List[ItemRaw]]("items.cgi").flatMap(itemRawList => Right(
itemRawList.map(itemRaw => {
for {
owner <- get[User](s"users.cgi?id=${itemRaw.ownerId}")
borrower <-
if (itemRaw.borrowerId > 0)
get[User](s"users.cgi?id=${itemRaw.borrowerId}").map(Some(_))
else
Right(None)
} yield
Item(itemRaw.sn, owner, borrower)
})
))
}
这似乎是一个家庭作业的请求,但我经常想到我想从一个 包装器(m-monad?)切换到另一个,我有点困惑如何仅使用包装函数(c-combinators?)来做到这一点。我当然可以切换到命令式实现。我只是好奇。
【问题讨论】: