我想使用一些使用Option中的值创建另一个Try的函数来平滑一个Try [Option [A]],我希望解决方案简单而惯用。我用一个例子来说明问题。目标是创建一个Option [Group],其成员和事件包含在一个Try中,可以包含来自三个函数中的任何一个的错误。
def getGroup(id: Long): Try[Option[Group]]
def getMembersForGroup(groupId: Long): Try[Seq[Member]]
def getMeetingsForGroup(groupId: Long): Try[Seq[Meeting]]
我发现从getGroup
返回的Try到成员会议功能和会议功能的tryMap很难进行flatMap,因为在"中有一个选项"。这是我到目前为止所提出的:
getGroup(id).flatMap(
groupOpt => groupOpt.map(
group => addStuff(group).map(group => Some(group))
).getOrElse(Success(None))
)
def addStuff(g: Group): Try[Group] =
for {
members <- getMembersForGroup(g.id)
meetings <- getMeetingsForGroup(g.id)
} yield g.copy(members = members, meetings = meetings)
我不喜欢我的解决方案是我必须将addStuff
返回的组包装在一个选项中以执行getOrElse。此时类型为Option [Try [Option [Group]]],我认为乍一看难以理解。
这个问题是否有更简单的解决方案?
答案 0 :(得分:3)
Cats的OptionT
类型可能会简化此类:documentation here和source here。
你的例子是:
def getGroupWithStuff(id: Long): OptionT[Try, Group] = {
for {
g <- OptionT(getGroup(id))
members <- OptionT.liftF(getMembersForGroup(g.id))
meetings <- OptionT.liftF(getMeetingsForGroup(g.id))
} yield g.copy(members = members, meetings = meetings)
}
答案 1 :(得分:1)
您可以使用.fold
代替.map.getOrElse
...这会让它更好一些:
getGroup(id)
.flatMap {
_.fold(Try(Option.empty[Group])){
addStuff(_).map(Option.apply)
}
}
或者明确地写下两个案例 - 在这种情况下看起来可能会更清楚一点,因为你可以避免拼出丑陋的类型签名:
getGroup(id).flatMap {
case None => Success(None)
case Some(group) => addStuff(group).map(Option.apply)
}
答案 2 :(得分:0)
您可能可以将getGroup调用简化为:
public WorkItem CreateWorkItem(string title, string description, string comments)
{
HttpClient client = new HttpClient();
var task = client.PostAsync("http://localhost:57765/API.svc/CreateWorkItem?title="+title+"&description="+description+"&history="+comments, null);
task.Wait();
var contents = task.Result.Content;
}
然而,这将以忽略来自addStuff调用的潜在故障信息为代价。如果不可接受,那么您不太可能进一步简化代码。
答案 3 :(得分:0)
试试这个。您可以从三个调用中的任何一个(以先发生者为准)中保留您的理解语法和失败信息。
def getFullGroup(id: Long): Try[Option[Group]] =
getGroup(id).flatMap[Option[Group]] { _.map[Try[Group]]{ group =>
for {
meetings <- getMeetingsForGroup(id)
members <- getMembersForGroup
} yield group.copy(meetings = meetings, members = members)
}.fold[Try[Option[Group]]](Success(None))(_.map(Some(_)))
}
注意最后的杂技类型:
fold[Try[Option[Group]]](Success(None))(_.map(Some(_)))
没有类型注释和IDE,很难做到正确。在这种特殊情况下,这并不算太糟糕,但想象会议和成员依赖于另一个嵌套的try选项,而后者依赖于原始选项。或者想象一下,如果你想要理解个别的会议和团体,而不是使用整个列表。
您可以尝试使用cat或scalaz中的OptionT monad变换器将Try[Option[Group]]
堆叠为非嵌套OptionT[Try, Group]
。如果使用monad变换器,它可能如下所示:
def getFullGroup(id: Long): OptionT[Try, Group] =
OptionT(getGroup(id)).flatMapF { group =>
for {
meetings <- getMeetingsForGroup(id)
members <- getMembersForGroup(id)
} yield group.copy(meetings = meetings, members = members)
}
}
对于这种特殊情况,并没有太大的收益。但是,如果你有很多这样的代码,请调查它。
顺便说一下,翻转Try和Option的第一个示例末尾的样板文件称为sequence
。当它跟随地图时,整个事物被称为traverse
。它是一种经常出现的模式,被函数式编程库抽象出来。您可以执行以下操作,而不是使用OptionT:
def getFullGroup(id: Long): Try[Option[Group]] =
getGroup(id).flatMap[Option[Group]] { _.traverse { group =>
for {
meetings <- getMeetingsForGroup(id)
members <- getMembersForGroup
} yield group.copy(meetings = meetings, members = members)
}
}
(通常情况下,如果您正在映射f
然后翻转monad,则需要使用f
进行遍历。)