是否可以在Scala中使用具有不同类型参数的通用值列表?

时间:2013-02-25 21:09:21

标签: scala generics types functional-programming

我想实现以下目标:

  1. 我需要处理一个字符串列表。
  2. 这些处理器有几种不同的类型,每种处理器都知道要读取的字符串的哪一部分。
  3. 我需要分两个阶段工作:首先,处理器需要查看每个输入字符串以构建特定于处理器的数据;第二,每个输入字符串由每个处理器处理,结果字符串合并为一个。
  4. 以可变的方式实现它很容易:所有处理器都有一个共同的基类,它们聚合的不同类型的数据被封装在具体的实现中;界面只包含2个函数---“查看输入字符串并构建内部数据”和“使用内部数据处理输入字符串”。

    当我在Scala中编写它时,我想知道是否存在纯粹的功能方法。问题是现在这些处理器的基本特征是通过其内部数据的类型进行参数化的,并且似乎没有办法获得不同类型的处理器列表。

    这个问题可以在一个更简单的案例中得到证明:比如我坚持使用可变方法,但由于某种原因已经参数化了处理器从字符串中获取的类型:

    trait F[V] {
      def get(line: String) : V
      def aggregate(value: V)
      def process(value: V) : String
    }
    
    class F1 extends F[Int] // ...
    class F2 extends F[HashMap[Int, Int]] // ...
    
    for (s <- List("string1", "string2"); 
      f <- List(new F1(), new F2()) 
    {
      f.aggregate(f.get(s)); // Whoops --- doesn't work   
    }
    

    它不起作用,因为f.get(s)返回Any。看起来我需要在Scala的类型系统中表达List(new F1(), new F2())包含F[?],它们不同但是一致,如果我采用该列表的元素,它具有其类型参数的一些具体值,并且{ {1}}属于该类型,应由f.get(s)接受。

    最后,我想有这样的事情(遗漏因为我不知道该怎么做):

    f.aggregate()

    问题:

    1. 此任务是否可以在Scala中以纯函数方式解决?
    2. 此任务是否可以用其他功能语言解决?
    3. 这种情况看起来很普遍。我可以搜索一个名字吗?
    4. 在类型和函数式编程语言理论方面几乎没有背景的情况下,在哪里阅读它的最佳位置?

2 个答案:

答案 0 :(得分:1)

另外,您可以使用abstract types代替泛型,所以:

trait F {
  type D
  def initData: D
  def aggregate(line: String, data: D): D
  def process(line: String, data: D): String
}

class F1 extends F { type D = Int } // ...
class F2 extends F { type D = Map[Int, Int] } // ...

val strings = List("string1", "string2")
for (f <- List(new F1(), new F2())) {
  val d = strings.foldLeft(f.initData) { (d, s) => f.aggregate(s, d) }

  for (s <- strings)
    f.process(s, d)
}

不确定,如果我没有正确的操作顺序,但它可能是一个起点。

答案 1 :(得分:0)

编辑刚刚注意到,我以前的解决方案过于冗长,无需任何临时数据结构。

我不确定,你的意思是“纯功能”。以下解决方案(如果它是您的问题的解决方案)是“纯粹功能性的”,因为除了println中的最终main调用之外它没有任何副作用。

注意,List[F[_]](...)很重要,否则,编译器会推断出列表元素的非常特定的内部类型,这与aggregateAndProcess函数不相符。 / p>

trait F[D] {

    type Data = D  // Abbreviation for easier copy+paste below. Does not
                       // contribute to the actual solution otherwise

    def initData: Data
    def aggregate(line: String, data: Data) : Data
    def process(line: String, aggData: Data): String
}

class F1 extends F[Int] {
    def initData: Data = 1
    def aggregate(line: String, data: Data) : Data = data + 1
    def process(line: String, aggData: Data): String = line + "/F1" + aggData
}

class F2 extends F[Boolean] {
    def initData: Data = false
    def aggregate(line: String, data: Data) : Data = !data
    def process(line: String, aggData: Data): String = line + "/F2" + aggData
}

object Main {

    private def aggregateAndProcess[T](line: String, processor: F[T]): String =
        processor.process(line, processor.aggregate(line, processor.initData))

    def main(args: Array[String]) {

        val r = for {
            s <- List("a", "b")
            d <- List[F[_]](new F1, new F2)
        } yield
            aggregateAndProcess(s, d)

        println(r.toList)
    }
}

但是,请注意,我仍然不确定您实际想要完成的任务。 F接口没有真正指定,哪个信息从哪个方法流到什么时间的任何位置,所以:这仍然是最好的猜测efford。