我有一个相当复杂的ADT代表一种小的查询语言(mongodb,具体而言)。简化版看起来有点像:
sealed abstract class Query extends Product with Serializable
final case class Eq[A](field: String, value: A) extends Query
final case class And(queries: Seq[Query]) extends Query
case object None extends Query
我已声明Query
没有类型参数,因为并非所有值实际上都有一个 - None
,例如,无参数。
我还有一个类型类DocumentEncoder[A]
,可让我将A
转换为BsonDocument
。
我遇到的问题是Query
需要DocumentEncoder
。为每个替代方案声明一个是相当微不足道的:
Eq[A]
自我写作,提供A: DocumentEncoder
。And
非常相似,如果我们假设Query
确实有DocumentEncoder
个实例。None
只是编码为空的BSON文件我所挣扎的是撰写全球DocumentEncoder[Query]
。我通常做的是每个替代方案上的模式匹配,但在这种情况下,我会遇到Eq[A]
:我需要表达类似case Eq[A: DocumentEncoder](field, value) => ...
的内容,但这个据我所知,是不可能的 - 模式匹配在运行时发生,在编译时隐式解析。
我发现的解决方案非常不令人满意,就是将BsonEncoder[A]
存储为Eq[A]
的字段。这允许我写一些类似的东西:
implicit val queryEncoder: DocumentEncoder[Query] = DocumentEncoder.from {
case e@Eq(field, value) => [...] e.encoder.encode(value) [...]
[...]
}
我无法帮助但发现这很糟糕,但无法找到更优雅的解决方案。我唯一能想到的是我的前提(Query
不应该有类型参数)是有缺陷的,但是:
And
的类型声明?None
声明为Query[Unit]
吗?好吧,好吧,所以我可以想到另一个解决方案,但感觉有点过分:让Query
类型成为类型成员而不是类型参数,并声明{{1将类型成员提升为参数的类型别名(用于隐式解析)。这种感觉就像一个大男孩的解决方案,但是 - 我已经看到它在图书馆中使用像无形的,我不知何故觉得我的代码或问题不是。还需要这种专家概念。