Returning original collection type in generic method

前端 未结 3 451
滥情空心
滥情空心 2020-12-01 07:06

Say we want to make a function like minBy that returns all elements of equal minimalism in a collection:

def multiMinBy[A, B: Ordering](xs: Trav         


        
相关标签:
3条回答
  • 2020-12-01 07:16

    How about using CanBuildFrom?

    import scala.collection.immutable._
    import scala.collection.generic._
    
    def multiMinBy[A, B, From[X] <: Traversable[X], To](xs: From[A])(f: A => B)
      (implicit ord: Ordering[B], bf: CanBuildFrom[From[_], A, To])  = {
      val minVal = f(xs minBy f)
      val b = bf()
      b ++= (xs filter (f(_) == minVal))
      b.result
    } 
    
    
    
    scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)
    res1: List[java.lang.String] = List(zza, zzza)
    
    0 讨论(0)
  • 2020-12-01 07:24

    Your problem is that when viewed as a method on GenTraversable[A] (which I'll use instead of Traversable[A] in this answer) the result type of the filter method is no more precise than GenTraversable[A]. Unfortunately within the body of the multiMinBy method as written that's all you know about xs.

    To get the result you're after you'll have to make the signature of multiMinBy more precise. One way of doing this while still leaving the container type relatively open is to use a structural type as follows,

    type HomFilter[CC[X] <: GenTraversable[X], A] = 
      CC[A] { def filter(p : A => Boolean) : CC[A] }
    
    def multiMinBy[CC[X] <: GenTraversable[X], A, B: Ordering]
      (xs: HomFilter[CC, A])(f: A => B) : CC[A] = {
        val minVal = f(xs minBy f)
        xs filter (f(_) == minVal)
      }
    

    The structural type HomFilter allows us to assert that the argument to multiMinBy must have a filter method with the desired result type.

    Sample REPL session,

    scala> val mmb = multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)
    mmb: List[String] = List(zza, zzza)
    

    Bear in mind that this is a stricter requirement than that the container just be Traversable: it's permissible for subtypes of GenTraversable to define filter methods which aren't regular in this way. The signature above will statically prevent values of such types from being passed to multiMinBy ... presumably that's the behaviour you're after.

    0 讨论(0)
  • 2020-12-01 07:36

    I think Miles Sabin solution is way too complex. Scala's collection already have the necessary machinery to make it work, with a very small change:

    import scala.collection.TraversableLike
    def multiMinBy[A, B: Ordering, C <: Traversable[A]]
                  (xs: C with TraversableLike[A, C])
                  (f: A => B): C = {
      val minVal = f(xs minBy f)
      xs filter (f(_) == minVal)
    }
    
    0 讨论(0)
提交回复
热议问题