Как определить ‹*› для Option[List[_]] n Scala

Это продолжение моего предыдущего вопроса с примером, найденным в Интернете.

Предположим, я определяю класс типов Applicative следующим образом:

trait Functor[T[_]]{
  def map[A,B](f:A=>B, ta:T[A]):T[B]
}

trait Applicative[T[_]] extends Functor[T] {
  def unit[A](a:A):T[A]
  def ap[A,B](tf:T[A=>B], ta:T[A]):T[B]
}

Я могу определить экземпляр Applicative для List

object AppList extends Applicative[List] {
  def map[A,B](f:A=>B, as:List[A]) = as.map(f)
  def unit[A](a: A) = List(a)
  def ap[A,B](fs:List[A=>B], as:List[A]) = for(f <- fs; a <- as) yield f(a)
}

Для удобства я могу определить implicit conversion, чтобы добавить метод <*> к List[A=>B]

implicit def toApplicative[A, B](fs: List[A=>B]) = new {
  def <*>(as: List[A]) = AppList.ap(fs, as)
}

Теперь я могу сделать классную вещь!
сжать два списка List[String] и применить f2 к каждой паре в аппликативном стиле.

val f2: (String, String) => String = {(first, last) => s"$first $last"}
val firsts = List("a", "b", "c")
val lasts  = List("x", "y", "z")

scala> AppList.unit(f2.curried) <*> firsts <*> lasts
res31: List[String] = List(a x, a y, a z, b x, b y, b z, c x, c y, c z)

Пока все хорошо, но теперь у меня есть:

val firstsOpt = Some(firsts)
val lastsOpt  = Some(lasts) 

Я хочу заархивировать firsts и lasts, применить f2 и получить Option[List[String]] в аппликативном стиле. Другими словами, мне нужно <*> для Option[List[_]]. Как мне это сделать ?


person Michael    schedule 05.03.2015    source источник


Ответы (1)


Во-первых, вам нужен экземпляр аппликации для Option:

implicit object AppOption extends Applicative[Option] {
  def map[A, B](f: A => B, o: Option[A]) = o.map(f)
  def unit[A](a: A): Option[A] = Some(a)
  def ap[A, B](of: Option[A => B], oa: Option[A]) = of match {
    case Some(f) => oa.map(f)
    case None => None
  }
}

Затем вы также можете создать экземпляр аппликации для композиции из двух аппликативов (обратите внимание, на основе Версия Haskell):

class AppComp[F[_], G[_]](fa: Applicative[F], ga: Applicative[G]) extends Applicative[({ type f[A] = F[G[A]]})#f] {
  def map[A, B](f: A => B, a: F[G[A]]): F[G[B]] = fa.map((g: G[A]) => ga.map(f, g), a)
  def unit[A](a: A) = fa.unit(ga.unit(a))
  def ap[A, B](f: F[G[A => B]], a: F[G[A]]): F[G[B]] = {
    val liftg: G[A => B] => (G[A] => G[B]) = gf => (gx => ga.ap(gf, gx))
    val ffg: F[G[A] => G[B]] = fa.map(liftg, f)
    fa.ap(ffg, a)
  }
}

implicit def toComp[F[_], G[_]](implicit fa: Applicative[F], ga: Applicative[G]) = new AppComp[F, G](fa, ga)

Наконец, теперь вы можете сделать:

val ola = toComp[Option, List]
ola.ap(ola.ap(ola.unit(f2.curried), firstsOpt), lastsOpt)

Возможно, вы также могли бы удалить часть шума, обобщив <*> для работы с любым аппликативом.

person Lee    schedule 05.03.2015
comment
Большой ! Спасибо. Вы использовали scalaz для реализации AppComp? Могу ли я найти AppComp или что-то подобное в scalaz ? - person Michael; 05.03.2015
comment
@Michael - я не знаю scalaz, но это похоже на compose в Applicative.scala делает то, что вы хотите. - person Lee; 05.03.2015
comment
Спасибо. Я посмотрю на compose. - person Michael; 05.03.2015
comment
Обратите внимание, что наличие нескольких экземпляров Applicative[Option[List[?]] в области действия может привести к проблемам (включая запутанный или невозможный вывод типов и, что более важно, нарушение ожиданий пользователей). Идти по маршруту Haskell (который здесь будет включать оболочку с новым Applicative через изоморфизм), вероятно, лучше. - person Travis Brown; 05.03.2015