Как реализовать Functor[набор данных]
Я борюсь за то, как создать экземпляр 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]
как неявный параметр, потому что это изменило бы сигнатуру метода карты, как я могу решить это?
1 ответ
Вы не можете подать заявку f
сразу, потому что вам не хватает Encoder
, Единственное очевидное прямое решение будет: взять cats
и повторно реализовать все интерфейсы, добавив следствие 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
s для всех промежуточных шагов отсутствуют.
Я еще не совсем там с учебой cats
Я не могу гарантировать, что это самое идиоматическое решение. Наверное, есть что-то Coyoneda
Ты уже в библиотеке.
РЕДАКТИРОВАТЬ: есть Койонеда в библиотеке кошек, но это требует естественного преобразования F ~> G
функтору G
, К сожалению, у нас нет Functor
за Dataset
(это была проблема в первую очередь). Что моя реализация выше делает: вместо Functor[G]
, это требует одного морфизма (несуществующего) естественного преобразования при фиксированном X
(это то, что Encoder[X]
является).