我正在为如何创建Functor[Dataset]实例而苦苦挣扎……问题是,当您将mapA转换为B时,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方法的签名,我该如何解决呢?

最佳答案

您无法立即应用f,因为您缺少Encoder。唯一明显的直接解决方案是:采用cats并重新实现所有接口(interface),并添加一个隐式Encoder参数。我看不到直接为Functor实现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包装为MappedDataset(ds),然后根据需要使用隐式map对其进行MappedDatasetFunctor,然后在最后调用toDataset,在那里您可以为最终结果提供具体的Encoder

请注意,这会将map内的所有功能组合到单个spark阶段:它将无法保存中间结果,因为缺少所有中间步骤的Encoder

我还没有研究cats,我不能保证这是最惯用的解决方案。库中可能已经有Coyoneda -esque了。

编辑:在cats库中有Coyoneda,但是需要自然转换F ~> G到functor G。不幸的是,我们没有FunctorDataset(首先是问题)。我上面的实现所做的是:代替Functor[G],它需要在固定的X(这是Encoder[X])下对(不存在的)自然变换进行单态处理。

关于scala - 如何实现Functor [数据集],我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48725356/

10-16 01:53