#scala #monads #traits #higher-kinded-types
#scala #монады #Трейты #типы более высокого типа
Вопрос:
Как вы определяете HKT в Scala, чтобы предоставлять методы, такие как map
, в качестве методов в экземплярах вместо функций в объектах?
Я знаю, что вы можете
trait M[F[_]] {
def map[A, B](f: F[A])(fn: A => B): F[B]
}
object Q extends M[Q] {
def map[A, B](f: Q[A])(fn: A => B) = new Q(fn(f.e))
}
class Q[A](val e: A)
Q.map(new Q(1))(_.toDouble)
Однако вместо этого я хочу использовать map
для экземпляров. Я не видел никакой литературы, которая делает это, поэтому я написал следующее.
trait M[A, F[A]] {
def map[B](f: A => B): F[B]
}
class Q[A](val e: A) extends M[A, Q] {
def map[B](f: A => B) = new Q(f(e))
}
new Q(1).map(_.toDouble)
Это идиоматический Scala? Делает ли это то, что map
должно делать? Существуют ли ограничения по сравнению с версией object выше? (В сторону, с extends M[A, Q[A]]
он не будет компилироваться — почему?)
Я видел
trait M[A] {
def map[B](f: A => B): M[B]
}
но тогда Q
‘s map
может возвращать class R[A] extends M[A]
, что нежелательно.
Комментарии:
1. Итак, вам нужен typeclass . Идея в том, что существует только один экземпляр класса typeclass для вашего типа, потому что здесь связь является
Q _has_ an instance of M
вместоQ _is_ an instance of M
. Обычно в Scala эти экземпляры делаются неявными, поэтому вы можете использовать их без необходимости передавать их вручную. Кроме того, обычно определяется неявныйOps
классSyntax
or (как это сделал Джо К.) , чтобы предоставить функциям класса типов обычную нотацию метода , просто чтобы сделать их использование более естественным.
Ответ №1:
Специально для этого есть библиотека: https://github.com/mpilquist/simulacrum
Вы также можете сделать это самостоятельно. Типичный шаблон заключается в определении соответствующего неявного MOps
класса или чего-то еще:
trait M[F[_]] {
def map[A, B](f: F[A])(fn: A => B): F[B]
}
implicit class MOps[F[_] : M, A](x: F[A]) {
def map[B](f: A => B): F[B] = implicitly[M[F]].map(x)(f)
}
Затем пример использования:
case class Box[A](a: A)
implicit val BoxM = new M[Box] {
def map[A, B](f: Box[A])(fn: A => B): Box[B] = Box(fn(f.a))
}
Box(2).map(_.toString)
Но в нем много шаблонов, поэтому simulacrum делает все это за вас (и не только).
Комментарии:
1. итак, вы определяете map в соответствии с версией объекта, а затем используете implicits для предоставления этой функциональности экземпляру?
2. какие последствия имеет этот подход по сравнению с моим?
3. Ваш подход допускает некоторые странные вещи, такие как
class Foo[A] extends M[A, Bar]
. Это распространенная проблема, я думаю, что это лучшая статья, которую я видел по этому поводу: tpolecat.github.io/2015/04/29/f-bounds.html4. Кроме того, некоторые методы некоторых классов типов не имеют связанных с ними экземпляров, например,
empty
метод Monoid. Итак, эти классы типов должны использовать этот стиль представления, и как только у вас появляется несколько таких форм, вам часто требуется наследование (Monoid[A] extends Semigroup[A]
), и довольно скоро все становится проще делать таким образом