您无法f
立即申请,因为您缺少Encoder
. 唯一明显的直接解决方案是:采用cats
并重新实现所有接口,添加一个隐含Encoder
参数。我没有看到任何直接Functor
实现for 的方法。Dataset
但是,也许以下替代解决方案就足够了。你可以做的是为数据集创建一个包装器,它有一个map
没有隐含的方法Encoder
,但还有一个方法toDataset
,它Encoder
最终需要。
对于这个包装器,您可以应用一个与所谓的Coyoneda
-construction 非常相似的结构(或者Coyo
?他们今天怎么称呼它?我不知道......)。它本质上是一种为任意类型构造函数实现“自由函子”的方法。
这是一个草图(它与猫 1.0.1 一起编译,Spark
用假人替换了特征):
import scala.language.higherKinds
import cats.Functor
/** Dummy for spark-Encoder */
trait Encoder[X]
/** Dummy for spark-Dataset */
trait Dataset[X] {
def map[Y](f: X => Y)(implicit enc: Encoder[Y]): Dataset[Y]
}
/** Coyoneda-esque wrapper for `Dataset`
* that simply stashes all arguments to `map` away
* until a concrete `Encoder` is supplied during the
* application of `toDataset`.
*
* Essentially: the wrapped original dataset + concatenated
* list of functions which have been passed to `map`.
*/
abstract class MappedDataset[X] private () { self =>
type B
val base: Dataset[B]
val path: B => X
def toDataset(implicit enc: Encoder[X]): Dataset[X] = base map path
def map[Y](f: X => Y): MappedDataset[Y] = new MappedDataset[Y] {
type B = self.B
val base = self.base
val path: B => Y = f compose self.path
}
}
object MappedDataset {
/** Constructor for MappedDatasets.
*
* Wraps a `Dataset` into a `MappedDataset`
*/
def apply[X](ds: Dataset[X]): MappedDataset[X] = new MappedDataset[X] {
type B = X
val base = ds
val path = identity
}
}
object MappedDatasetFunctor extends Functor[MappedDataset] {
/** Functorial `map` */
def map[A, B](da: MappedDataset[A])(f: A => B): MappedDataset[B] = da map f
}
现在您可以将数据集包装ds
到 aMappedDataset(ds)
中,然后map
根据需要使用隐式MappedDatasetFunctor
,然后在最后调用toDataset
,您可以Encoder
为最终结果提供具体的内容。
请注意,这会将内部的所有函数组合map
到一个 spark 阶段:它将无法保存中间结果,因为Encoder
所有中间步骤的 s 都丢失了。
我还没有完全学习cats
,我不能保证这是最惯用的解决方案。Coyoneda
图书馆里可能已经有一些东西了。
编辑:猫库中有Coyoneda,但它需要自然转换F ~> G
为 functor G
。不幸的是,我们没有Functor
for Dataset
(这首先是问题所在)。我上面的实现所做的是:代替 a Functor[G]
,它需要一个固定的(不存在的)自然变换的单一态射X
(这就是事实Encoder[X]
)。