2010-07-12 35 views
7

Giả sử tôi muốn thêm chức năng như map vào Scala List, thứ gì đó dọc theo các dòng list mapmap f, áp dụng hàm f cho mỗi thành phần của list hai lần. (Một ví dụ nghiêm trọng hơn có thể thực hiện một bản đồ song song hoặc phân tán, nhưng tôi không muốn bị phân tâm bởi các chi tiết theo hướng đó.)Tôi có thể "pimp thư viện của tôi" với tương tự TraversableLike.map có các loại biến thể độc đáo không?

tiếp cận đầu tiên của tôi sẽ là

object MapMap { 
    implicit def createFancyList[A](list: List[A]) = new Object { 
     def mapmap(f: A => A): List[A] = { list map { a: A => f(f(a)) } } 
    } 
} 

này ngay bây giờ hoạt động tuyệt vời

scala> import MapMap._ 
import MapMap._ 

scala> List(1,2,3) mapmap { _ + 1 } 
res1: List[Int] = List(3, 4, 5) 

trừ tất nhiên nó chỉ cho List s, và không có lý do chúng ta không nên muốn điều này để làm việc cho bất cứ điều gì Traverseable, với một chức năng map, ví dụ: Set s hoặc Stream s. Vì vậy, các nỗ lực thứ hai trông giống như

object MapMap2 { 
    implicit def createFancyTraversable[A](t: Traversable[A]) = new Object { 
     def mapmap(f: A => A): Traversable[A] = { t map { a: A => f(f(a)) } } 
    } 
} 

Nhưng bây giờ, tất nhiên, kết quả không thể được gán cho một List[A]:

scala> import MapMap2._ 
import MapMap2._ 

scala> val r: List[Int] = List(1,2,3) mapmap { _ + 1 } 
<console>:9: error: type mismatch; 
found : Traversable[Int] 
required: List[Int] 

Có một số mặt đất giữa? Tôi có thể viết một chuyển đổi tiềm ẩn để thêm một phương thức cho tất cả các lớp con của Traversable và trả về thành công các đối tượng có kiểu đó không?

(tôi đoán này liên quan đến sự hiểu biết về đặc điểm đáng sợ CanBuildFrom, và thậm chí có thể breakout!)

Trả lời

10

Bạn không thể làm điều này cho tất cả Traversables, như họ sẽ không bao không đảm bảo rằng bản đồ trả về bất cứ điều gì cụ thể hơn Traversable. Xem Cập nhật 2 bên dưới.

import collection.generic.CanBuildFrom 
import collection.TraversableLike 

class TraversableW[CC[X] <: TraversableLike[X, CC[X]], A](value: CC[A]) { 
    def mapmap(f: A => A)(implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): CC[A] 
     = value.map(f andThen f) 
    def mapToString(implicit cbf: CanBuildFrom[CC[A], String, CC[String]]): CC[String] 
     = value.map(_.toString) 
} 

object TraversableW { 
    implicit def TraversableWTo[CC[X] <: TraversableLike[X, CC[X]], A](t: CC[A]): TraversableW[CC, A] 
     = new TraversableW[CC, A](t) 
} 

locally { 
    import TraversableW._ 

    List(1).mapmap(1+) 
    List(1).mapToString 
    // The static type of Seq is preserved, *and* the dynamic type of List is also 
    // preserved. 
    assert((List(1): Seq[Int]).mapmap(1+) == List(3)) 
} 

CẬP NHẬT Tôi đã thêm một phương pháp pimped, mapToString, để chứng minh tại sao TraversableW chấp nhận hai tham số kiểu, chứ không phải là một tham số như trong dung dịch Alexey của. Tham số CC là loại được phân loại cao hơn, nó đại diện cho loại vùng chứa của tập hợp gốc. Tham số thứ hai, A, thể hiện loại phần tử của tập hợp gốc. Do đó, phương thức mapToString có thể trả về loại vùng chứa ban đầu với loại phần tử khác: CC[String.

CẬP NHẬT 2 Nhờ nhận xét @oxbow_lakes, tôi đã suy nghĩ lại điều này. Nó thực sự là có thể trực tiếp pimp CC[X] <: Traversable[X], TraversableLike là không cần thiết. Nhận xét nội tuyến:

import collection.generic.CanBuildFrom 
import collection.TraversableLike 

class TraversableW[CC[X] <: Traversable[X], A](value: CC[A]) { 
    /** 
    * A CanBuildFromInstance based purely the target element type `Elem` 
    * and the target container type `CC`. This can be converted to a 
    * `CanBuildFrom[Source, Elem, CC[Elem]` for any type `Source` by 
    * `collection.breakOut`. 
    */ 
    type CanBuildTo[Elem, CC[X]] = CanBuildFrom[Nothing, Elem, CC[Elem]] 

    /** 
    * `value` is _only_ known to be a `Traversable[A]`. This in turn 
    * turn extends `TraversableLike[A, Traversable[A]]`. The signature 
    * of `TraversableLike#map` requires an implicit `CanBuildFrom[Traversable[A], B, That]`, 
    * specifically in the call below `CanBuildFrom[Traversable[A], A CC[A]`. 
    * 
    * Essentially, the specific type of the source collection is not known in the signature 
    * of `map`. 
    * 
    * This cannot be directly found instead we look up a `CanBuildTo[A, CC[A]]` and 
    * convert it with `collection.breakOut` 
    * 
    * In the first example that referenced `TraversableLike[A, CC[A]]`, `map` required a 
    * `CanBuildFrom[CC[A], A, CC[A]]` which could be found. 
    */ 
    def mapmap(f: A => A)(implicit cbf: CanBuildTo[A, CC]): CC[A] 
     = value.map[A, CC[A]](f andThen f)(collection.breakOut) 
    def mapToString(implicit cbf: CanBuildTo[String, CC]): CC[String] 
     = value.map[String, CC[String]](_.toString)(collection.breakOut) 
} 

object TraversableW { 
    implicit def TraversableWTo[CC[X] <: Traversable[X], A](t: CC[A]): TraversableW[CC, A] 
     = new TraversableW[CC, A](t) 
} 

locally { 
    import TraversableW._ 

    assert((List(1)).mapmap(1+) == List(3)) 

    // The static type of `Seq` has been preserved, but the dynamic type of `List` was lost. 
    // This is a penalty for using `collection.breakOut`. 
    assert((List(1): Seq[Int]).mapmap(1+) == Seq(3)) 
} 

Sự khác biệt là gì?Chúng tôi đã phải sử dụng collection.breakOut, vì chúng tôi không thể khôi phục tập hợp con phụ cụ thể từ một chỉ Traversable[A].

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = { 
    val b = bf(repr) 
    b.sizeHint(this) 
    for (x <- this) b += f(x) 
    b.result 
} 

Các Builderb được khởi tạo với bộ sưu tập ban đầu, đó là cơ chế để bảo tồn các loại động thông qua một map. Tuy nhiên, CanBuildFrom của chúng tôi đã từ chối tất cả kiến ​​thức về Từ, theo cách đối số loại Nothing. Tất cả các bạn có thể làm với Nothing là lờ nó đi, đó là chính xác những gì breakOut làm:

def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) = 
    new CanBuildFrom[From, T, To] { 
    def apply(from: From) = b.apply(); 
    def apply() = b.apply() 
    } 

Chúng ta không thể gọi b.apply(from), không nhiều hơn bạn có thể gọi def foo(a: Nothing) = 0.

+0

Hoạt động hoàn hảo! –

+2

Tôi đã thực hiện một cách tiếp cận hơi khác trong Scalaz, đó là một chút mạnh mẽ hơn: http://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/CanBuildAnySelf.scala#L24 http: //github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Functor.scala#L28 – retronym

+0

Tôi không có quyền chỉnh sửa, viết lại, nhưng có lẽ định dạng mã khối cần được mở rộng để bao gồm nhập khẩu trong ví dụ của bạn? Chúc mừng! – pr1001

5

Theo nguyên tắc chung, khi bạn muốn quay trở lại đối tượng với cùng loại, bạn cần TraversableLike (IterableLike, SeqLike , vv) thay vì Traversable. Dưới đây là phiên bản chung nhất mà tôi có thể đưa ra (lớp FancyTraversable riêng biệt ở đó để tránh suy luận kiểu cấu trúc và sự phản ánh hit):

class FancyTraversable[A, S <: TraversableLike[A, S]](t: S) { 
    def mapmap(f: A => A)(implicit bf: CanBuildFrom[S,A,S]): S = { t map { a: A => f(f(a)) } } 
} 

implicit def createFancyTraversable[A, S <: TraversableLike[A, S]](t: S): FancyTraversable[A, S] = new FancyTraversable(t) 
+0

Tôi có phải nhập nội dung nào đó không? Tôi nhận được một "lỗi: không tìm thấy: loại TraversableLike". (2.8.0RC7) –

+0

"nhập scala.collection._" và "nhập scala.collection.generic._" ít nhất cũng làm cho nó biên dịch, nhưng bây giờ "Bản đồ (1,2,3) bản đồ {_ + 1}" cung cấp cho tôi "error: value mapmap không phải là thành viên của List [Int]". –

Các vấn đề liên quan