如何使方法 return 与输入具有相同的通用性?

How to make method return the same generic as the input?

我想拆分以逗号分隔的字符串,并将结果用作 SeqSet:

def splitByComma(commaDelimited: String): Array[String]
  = commaDelimited.trim.split(',')

def splitByCommaAsSet(commaDelimited: String): Set[String]
  = splitByComma(commaDelimited).toSet

def splitByCommaAsSeq(commaDelimited: String): Seq[String]
  = splitByComma(commaDelimited).toSeq

val foods = "sushi,taco,burrito"
val foodSet = splitByCommaAsSet(foods)
// foodSet: scala.collection.immutable.Set[String] = Set(sushi, taco, burrito)
val foodSeq = splitByCommaAsSeq(foods)
// foodSeq: Seq[String] = List(sushi, taco, burrito)

但是,这是相当重复的。理想情况下,我希望有类似 genericSplitByComma[Set](foods) 的东西,当我传入 Set 时,它只是 returns 一个 Set,当我传入 returns 时,Seq我通过 Seq.

Scala 中有一个名为 to 的方法,只要作用域中存在名为 CanBuildFrom 的类型类,它就可以将任意集合转换为另一个集合。

import scala.collection.generic.CanBuildFrom
import scala.languageFeature.higherKinds

def genericSplitByComma[S[_]](s: String)(implicit cbf: CanBuildFrom[Nothing, String, S[String]]): S[String] = {
    s.split(",").to[S]
}

genericSplitByComma[Set]("Hello, hello") //Set(Hello,  hello)
genericSplitByComma[List]("Hello, hello") //List(Hello,  hello)
genericSplitByComma[Array]("Hello, hello") //Array(hello, world!)

我们不需要约束S[_],因为如果范围内没有合适的CanBuildFrom,这个函数将不会编译。例如,这将失败:

genericSplitByComma[Option]("Hello, hello")

下面也会失败,因为我们的类型构造函数 S[_] 只接受一个类型参数,而映射需要两个:

genericSplitByComma[Map]("Hello, hello")

正如 Luis Miguel Mejía Suárez 和 Dmytro Mitin 所注意到的,在 just-released Scala 2.13 的集合中有 major refactor,所以它可以工作到 Scala 2.12。

@KrzysztofAtłasik 的回答非常适合 Scala 2.12.
这是 2.13 的解决方案。 (不确定这是否是最好的方法).

import scala.collection.Factory
import scala.language.higherKinds

def splitByComma[C[_]](commaDelimited: String)(implicit f: Factory[String, C[String]]): C[String] =
  f.fromSpecific(commaDelimited.split(","))
  // Or, as Dmytro stated, which I have to agree looks better.
  commaDelimited.split(",").to(f)

你可以这样使用:

splitByComma[Array]("hello,world!")
// res: Array[String] = Array(hello, world!)

splitByComma[Set]("hello,world!")
// res: Set[String] = Set(hello, world!)

splitByComma[List]("hello,world!")
// res: List[String] = List(hello, world!)

对此有一个简单的解决方法。不完全是要求的语法,但同样简洁,它应该与 2.13 兼容。

def simpleSplitByComma(coll :Iterable[String]) =
  coll.flatMap(_.trim.split(","))

simpleSplitByComma(Set("hello,world"))          //res0: Set(hello, world)
simpleSplitByComma(Seq("bellow,world"))         //res1: List(bellow, world)
simpleSplitByComma(Array("fellow,old"))         //res2: ArrayBuffer(fellow, old)
simpleSplitByComma(Stream("end,of,the,world"))  //res3: Stream(end, ?)