如何实现 Functor[Dataset]
How to implement Functor[Dataset]
我正在为如何创建 Functor[Dataset]
的实例而苦苦挣扎...问题是当您 map
从 A
到 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
并重新实现所有接口,添加一个隐含的 Encoder
参数。我看不出有什么方法可以直接.
为 Dataset
实现 Functor
但是也许以下替代解决方案就足够了。
你可以做的是为数据集创建一个包装器,它有一个没有隐式 Encoder
的 map
方法,但另外还有一个需要 Encoder
的方法 toDataset
最后。
对于这个包装器,您可以应用与所谓的 Coyoneda
构造(或 Coyo
非常相似的构造?他们今天怎么称呼它?我不知道...)。它本质上是一种为任意类型构造函数实现 "free functor" 的方法。
这是一个草图(它用 cats 1.0.1 编译,用假人替换了 Spark
traits):
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
风格的东西了。
编辑: cats 库中有 Coyoneda,但它需要自然转换 F ~> G
到函子 G
。不幸的是,我们没有 Functor
对应 Dataset
(这首先是问题所在)。我上面的实现是:代替 Functor[G]
,它需要固定 X
处的(不存在的)自然变换的 单态射 (这Encoder[X]
是什么)。
我正在为如何创建 Functor[Dataset]
的实例而苦苦挣扎...问题是当您 map
从 A
到 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
并重新实现所有接口,添加一个隐含的 Encoder
参数。我看不出有什么方法可以直接.
Dataset
实现 Functor
但是也许以下替代解决方案就足够了。
你可以做的是为数据集创建一个包装器,它有一个没有隐式 Encoder
的 map
方法,但另外还有一个需要 Encoder
的方法 toDataset
最后。
对于这个包装器,您可以应用与所谓的 Coyoneda
构造(或 Coyo
非常相似的构造?他们今天怎么称呼它?我不知道...)。它本质上是一种为任意类型构造函数实现 "free functor" 的方法。
这是一个草图(它用 cats 1.0.1 编译,用假人替换了 Spark
traits):
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
风格的东西了。
编辑: cats 库中有 Coyoneda,但它需要自然转换 F ~> G
到函子 G
。不幸的是,我们没有 Functor
对应 Dataset
(这首先是问题所在)。我上面的实现是:代替 Functor[G]
,它需要固定 X
处的(不存在的)自然变换的 单态射 (这Encoder[X]
是什么)。