2013-04-16 72 views
3

我想實現一個功能,可以在類型有mapflatMap方法。我已經制作了Traversable,但這不包括FutureOption。所以我決定去與我自己的接口,使用類型類:Typeclass和斯卡拉集合接口

trait CanMap[A, M[_]] { 
    def map[B](l: M[A])(f: A => B): M[B] 
    def flatMap[B](l: M[A])(f: A => M[B]): M[B] 
} 

我已經實現了這個爲Option

implicit def canmapopt[A] = new CanMap[A, Option] { 
    def map[B](l: Option[A])(f: A => B): Option[B] = l.map(f) 
    def flatMap[B](l: Option[A])(f: A => Option[B]): Option[B] = l.flatMap(f) 
    } 

而這一次的作品非常好。現在,我想要實現它的Traversable任何亞型,我想非常接近的一個選項的實現:

implicit def canmaptrav[A, B, T[B] <: Traversable[B]] = new CanMap[A, T] { 
    def map[B](l: T[A])(f: A => B): T[B] = l.map(f) 
    def flatMap[B](l: T[A])(f: A => T[B]): T[B] = l.flatMap(f) 
    } 

,但我得到的錯誤:

type mismatch; found : Traversable[B] required: T[B] Note: implicit method canmaptrav is not applicable here because it comes after the application point and it lacks an explicit result type 

l.map返回類型。我不明白爲什麼l.map(f)會返回一個Traversable而不是特定類型T[B]。所以我試圖在上下文中明確地放置正確類型的CanBuildFrom:

implicit def canmaptrav[A, B, T[B] <: Traversable[B]](implicit cbf: CanBuildFrom[T[A], B, T[B]]) = new CanMap[A, T] { 
    def map[B](l: T[A])(f: A => B): T[B] = l.map(f) 
    def flatMap[B](l: T[A])(f: A => T[B]): T[B] = l.flatMap(f) 
    } 

錯誤仍然存​​在。

任何想法,我哪裏出錯了?這可能很明顯,但我對我猜測的泛型類型簽名感到困惑。

更新:解決方案

首先,作爲回答指出,CanMap主要是一個函子/單子,所以如果你敢,你可以使用scalaz來實現這一點。但是,如果你像我一樣,想嘗試沒有它,這裏是解決方案的基礎上,通過Kipton巴羅斯答案:

trait CanMap[A, B, M[_]] { 
    def map(l: M[A])(f: A => B): M[B] 
    def flatMap(l: M[A])(f: A => M[B]): M[B] 
} 

implicit def canmapopt[A, B] = new CanMap[A, B, Option] { 
    def map(l: Option[A])(f: A => B): Option[B] = l.map(f) 
    def flatMap(l: Option[A])(f: A => Option[B]): Option[B] = l.flatMap(f) 
} 

implicit def canmaptrav[A, B, M[+_]](implicit bf: CanBuildFrom[M[A], B, M[B]], ev: M[A] => TraversableLike[A, M[A]], eb: M[B] => TraversableLike[B, M[B]]) = new CanMap[A, B, M] { 
    def map(l: M[A])(f: (A) => B): M[B] = l.map(f) 
    def flatMap(l: M[A])(f: A => M[B]): M[B] = l.flatMap[B, M[B]] { (a: A) => 
    f(a) 
    } 
} 

訣竅是使用隱式轉換M[A] => TraversableLike[A, M[A]],而不是試圖亞型Traversable

回答

4

第一個問題是在Traversablemap方法中有很多「引擎蓋下」正在進行。它做了一些工作來返回最具體的收集類型,這就是爲什麼你需要CanBuildFrom。第二個問題是Option沒有實現接口Traversable,所以它的map方法不是需要一個CanBuildFrom

這裏是最接近我能得到,

import scala.collection.generic.CanBuildFrom 
import collection.TraversableLike 

trait CanMap[A, M[_]] { 
    def map[B](l: M[A])(f: A => B)(implicit bf: CanBuildFrom[M[A], B, M[B]]): M[B] 
} 

object Test { 

    // ugly hack to work around nonexistent CanBuildFrom for Option 
    implicit def optionBuilder[A, B]: CanBuildFrom[Option[A], B, Option[B]] = null 

    implicit def canmapopt[A] = new CanMap[A, Option] { 
    def map[B](l: Option[A])(f: A => B)(implicit bf: CanBuildFrom[Option[A], B, Option[B]]): Option[B] = l.map(f) 
    } 

    implicit def canmaptrav[A, M[_]](implicit ev: M[A] => TraversableLike[A, M[A]]) = new CanMap[A, M] { 
    def map[B](l: M[A])(f: (A) => B)(implicit bf: CanBuildFrom[M[A], B, M[B]]): M[B] = l.map(f) 
    } 

    // example usage 

    def mapper[A, B, M[_]](l: M[A])(f: A => B)(implicit cm: CanMap[A,M], bf: CanBuildFrom[M[A], B, M[B]]) = { 
    cm.map(l)(f) 
    } 
    mapper(List(1,2,3))(_ + 1)   // List(2,3,4) 
    mapper(Some(2): Option[Int])(_ + 1) // Some(3) 
    // (cast to Option[Int] is needed to find the canmapopt implicit) 
} 

順便說一句,隱式轉換爲TraversableLike使得這個還使用數組,

mapper(Array(1,2,3))(_ + 1)   // Array(2, 3, 4) 
+0

優秀! 'TraversableLike'隱式轉換很酷:)​​ 我不得不適應你的解決方案,將'CanBuildFrom'作爲一個隱式移動到'canmaptrav',這樣它就不會「毒化」'canmapopt'。爲了能夠實現'flatMap',我還必須將另一個隱含的'CanBuildFrom'添加到'''M [B]'到'canmaptrav'中。 'B'現在是CanMap [A​​,B,M [_]]中的一個參數,不再是函數。 – Mortimer

+0

很高興你找到了解決方案。在CanMap中包含'B'類型可以簡化很多事情,但缺點是每個CanMap對象只映射到一個特定的類型。我看着斯卡拉斯。它們完全避免了'CanBuildFrom',它具有上升(更簡單)和負面影響(目標類型不特定;例如不會自動工作)。 –

+0

我真的沒有'B'的問題必須是一個特定的類型,因爲它也是提供CanMap的含義的參數。所以它根據我在哪裏使用它而通過scala動態生成。 – Mortimer

2

首先,我試了兩次失敗的嘗試,但沒有得到太多的結果。然後我決定去簡單的做我自己的CanMap實現。我結束了這一點:

def canmaptrav[A] = new CanMap[A, Traversable]{ 
    def map[B](l: Traversable[A])(f: A => B): Traversable[B]= l.map(f) 
    def flatMap[B](l: Traversable[A])(f: A => Traversable[B]): Traversable[B] = l.flatMap(f) 
    } 

展望完全CanMap[_,Option]。假設你正在尋找的亞型是用例是這樣的:

canmaptrav[Int].map(List(1,2,3,4))(_ + 1)  //> res0: Traversable[Int] = List(2, 3, 4, 5) 
canmaptrav[Int].map(Vector(1,2,3,4))(_ + 1)  //> res1: Traversable[Int] = Vector(2, 3, 4, 5) 

現在,如果你想RES1和RES0類型是具體類型(列表,向量),比方法將不得不確實依靠CanBuildFrom自。

順便說一句,你知道CanMap幾乎是Monad接口,對不對?

+0

是的,我這樣做了,但我希望得到正確的類型在map/flatMap之外,這就是爲什麼我走了'CanBuildFrom'瘋狂的道路。 – Mortimer

2

Scalaz已經包含了這些類型類,它們被稱爲MonadFunctor。一個簡單的例子:

// map 
def foo[F[_] : Functor](xs: F[Int]) = xs.map(_ + 1) 

scala> foo(List(1,2,3)) 
res2: List[Int] = List(2, 3, 4) 

// flatMap 
def flatten[M[_] : Monad](xs: M[M[Int]]) = xs.flatMap(identity) 

scala> flatten(List(List(1,2,3))) 
res3: List[Int] = List(1, 2, 3) 

編輯

Future函子實例可以是這樣的:

implicit object FutureFunctor extends Functor[Future] { 
    def map[A,B](fa: Future[A])(f: A => B) = fa.map(f) 
} 
+0

謝謝,確實,scalaz提供了我需要的所有東西,但是我想在沒有整個scalaz庫的情況下做到這一點。特別是因爲我查看了代碼,而且我真的沒有得到如何使用它,我甚至無法將它作爲依賴下載。斯卡拉茲看起來很酷,但是爲了得到一個Monad類型類別,這太複雜了。 – Mortimer