如何实现Functor [Dataset]

时间:2018-02-10 20:40:38

标签: scala apache-spark scala-cats scala-implicits apache-spark-encoders

我正在努力研究如何创建Functor[Dataset]的实例......问题在于,mapAB Encoder[B]必须在隐含的范围内,但我不知道该怎么做。

implicit val datasetFunctor: Functor[Dataset] = new Functor[Dataset] {
    override def map[A, B](fa: Dataset[A])(f: A => B): Dataset[B] = fa.map(f)
  }

当然这段代码抛出了一个编译错误,因为Encoder[B]不可用但是我不能将Encoder[B]添加为隐式参数,因为它会改变map方法签名,我该如何解决这个问题?

1 个答案:

答案 0 :(得分:6)

您无法立即申请f,因为您错过了Encoder。唯一明显的直接解决方案是:接受cats并重新实现所有接口,添加一个implict Encoder参数。我没有看到任何方法直接为Functor 实施Dataset

然而可能以下替代解决方案已经足够了。 您可以做的是为数据集创建一个包装器,它具有map方法而没有隐式Encoder,但另外还有一个方法toDataset,需要Encoder在最后。

对于这个包装器,你可以应用一个与所谓的Coyoneda非常相似的结构 - 或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打包到MappedDataset(ds),然后根据需要使用隐式map MappedDatasetFunctor,然后调用toDataset在最后,你可以为最终结果提供具体的Encoder

请注意,这会将map内的所有函数合并为一个火花阶段:它无法保存中间结果,因为缺少所有中间步骤的Encoder

我还没有完成学习cats,我无法保证这是最惯用的解决方案。可能在库中已经存在Coyoneda - esque。

编辑:猫库中有Coyoneda,但它需要对仿函数F ~> G进行自然转换G。不幸的是,我们Functor没有Dataset(这首先是问题)。我上面的实现是:代替Functor[G],它需要一个固定X的(不存在的)自然变换的单态态(这就是Encoder[X]是。)