Сначала я опишу способ мышления, а затем мои вопросы.Я использую определение Functor в качестве примера.Он определен в scalaz, поэтому я использую его в качестве примера с небольшими изменениями, чтобы сделать его менее сложным.
Вот определение модуля Functor
, которое я вставил в файл Functor.scala
:
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
Теперь я хочу определить некоторые значения модуля для точных типов, например для List
.Кроме того, я хочу определить общую функцию fmap
, которая принимает неявно Functor
.Лучше определить их как можно ближе к признаку Functor
.Я думаю, что лучшее место (соображение № 1) в Scala для него - это объект-компаньон.Вот его определение в том же файле Functor.scala
:
object Functor {
def fmap[F[_], A,B](as:F[A])(f:A=>B)
(implicit ff:Functor[F]):F[B] =
ff.map(as)(f)
implicit val listFunctor = new Functor[List] {
def map[A,B](as: List[A])(f: A => B): List[B] = as map f
}
}
Я могу неявно использовать fmap
и Functor
в клиентском коде, например:
import com.savdev.NewLibrary._
val r = fmap(List(1,2))(_.toString)
Довольно часто, когда мы определяем модуль с функцией / набором функций, мы также хотим обогатить существующие типы, чтобы иметь возможность использовать наши новые функции как часть API этих существующих типов.В Scala мы можем достичь этого посредством неявных преобразований.Вот окончательный класс FunctorOps
, который позволяет преобразовывать из некоторого универсального типа в Functor
:
final class FunctorOps[F[_], A](self: F[A])(implicit ff:Functor[F]){
def qmap[B](f:A=>B):F[B] = ff.map(self)(f)
}
Я называю функции: fmap
, qmap
по-разному, чтобы избежать ошибочного использования a map
функция, которая существует для разных типов.
Теперь я хочу определить неявный преобразователь в FunctorOps
.В качестве первого варианта, который приходит мне в голову, - использовать сопутствующий объект.Но, глядя на скалярный код, я обнаружил, что они используют в основном черты:
trait ToFunctorOps {
implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F]) =
new FunctorOps[F,A](v)
}
Имея черты, я могу составлять их и импортировать одним словом.Вот пример одной черты, но я могу расширить многие из них:
object NewLibrary extends ToFunctorOps
Прямо сейчас в коде клиента я могу импортировать только NewLibrary
, что намного проще, чем импортировать каждый объект-компаньон:
import com.savdev.NewLibrary._
val r = fmap(List(1,2))(_.toString)
Сравнивая параметры, черты и сопутствующие объекты, я могу обнаружить, что использование черт является гораздо более гибким способом.Прежде всего, потому что я могу их составить.Возможно, я что-то упускаю, и мой вывод неверен.Итак, вопросы:
- Можете ли вы дать мне явные варианты использования, когда использование вспомогательного объекта является более эффективным способом определения обобщенных функций и последствий, если таковые существуют.
- Есть лиспособ создания сопутствующих объектов?Они синглтоны, но Scala - более мощный язык, чем я ожидал.Возможно, есть несколько способов сделать это.
- Есть ли руководство, которое объясняет, какой из вариантов мне лучше всего выбрать и когда.