简体   繁体   English

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

[英]Is it possible to work with a list of generic values with different type parameters in Scala?

I want to achieve the following: 我要实现以下目标:

  1. There is a list of strings I need to process. 有一个我需要处理的字符串列表。
  2. There are several different kinds of these processors, each of which knows which part of the string to read. 这些处理器有几种,每种都知道要读取字符串的哪一部分。
  3. I need to work in 2 phases: first, processors need to see each input string to build processor-specific data; 我需要分两个阶段进行工作:首先,处理器需要查看每个输入字符串以构建处理器特定的数据; second, each input string is processed by each of the processors, and the resulting strings are combined into one. 第二,每个输入字符串由每个处理器处理,然后将结果字符串合并为一个。

It's easy to do it in a mutable way: there's a common base class for all processors, the different kinds of data they aggregate is encapsulated in the concrete implementations; 以可变的方式很容易做到:所有处理器都有一个通用的基类,它们聚合的不同类型的数据封装在具体的实现中; the interface consists of just 2 functions --- "look at input string and build internal data" and "process input string using your internal data." 该界面仅包含2个功能-“查看输入字符串并构建内部数据”和“使用内部数据处理输入字符串”。

As I am writing it in Scala, I am wondering if there exists a pure functional approach. 当我在Scala中编写代码时,我想知道是否存在一种纯粹的功能方法。 The problem is that now the base trait for these processors is parameterized by the type of their internal data, and there doesn't seem to be a way to have a list of processors of different kinds. 问题在于,现在这些处理器的基本特征是通过其内部数据的类型来参数化的,并且似乎没有一种方法可以列出不同种类的处理器。

This problem can be demonstrated on a simpler case: say I'd stick with the mutable approach, but for some reason have parameterized the type of what the processor takes from the string: 这个问题可以在一个更简单的情况下得到证明:说我会坚持使用可变方法,但是由于某种原因已经参数化了处理器从字符串中获取的类型:

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   
}

It doesn't work because f.get(s) returns Any . 它不起作用,因为f.get(s)返回Any Looks like I need to express in Scala's type system that List(new F1(), new F2()) contains F[?] that are different but consistent in that if I take an element of that list, it has some concrete value of its type parameter, and f.get(s) is of that type, which should be accepted by f.aggregate() . 看起来我需要在Scala的类型系统中表达List(new F1(), new F2())包含F[?] ,它们是不同的,但一致的是,如果我采用该列表的元素,它具有一些具体的价值它的类型参数,而f.get(s)就是该类型,应该由f.aggregate()接受。

In the end, I would like to have something like this (with omissions because I don't get how to do it): 最后,我想要这样的东西(由于我不知道该怎么做,所以省略了):

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

class F1 extends F[Int] // ...
class F2 extends F[HashMap[Int, Int]] // ...

// Phase 1
// datas --- List of f.initData, how to?
for (s <- List("string1", "string2")) {
  for (f <- List(new F1(), new F2()) {
    // let fdata be f's data
    // update fdata with f.aggregate(s, fdata)
  }
}

// Phase 2
for (s <- List("string1", "string2")) {
  for (f <- List(new F1(), new F2()) {
    // let fdata be f's data
    // for all fs, concatenate f.process(s, fdata) into an output string
  }
}

Questions: 问题:

  1. Is this task solvable in pure functional way in Scala? 这个任务可以在Scala中以纯功能方式解决吗?
  2. Is this task solvable in other functional languages? 该任务可以用其他功能语言解决吗?
  3. This situation looks like quite a general one. 这种情况看起来很普通。 Is there a name for it I could search? 我可以搜索的名字吗?
  4. Where is the best place to read about it, assuming little to no background on theory of types and functional programming languages? 假设几乎没有类型理论和函数式编程语言的背景,那么哪里是最好的阅读指南?

Also, you may use abstract types instead of generics, so: 另外,您可以使用抽象类型而不是泛型,因此:

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)
}

Don't sure, if I undrestood correct order of operation, but it may be a starting point. 不确定,如果我误解了正确的操作顺序,但这可能是一个起点。

Edit Just noticed, that my former solution was overly verbose, consing up a temporary data structure without any need. Edit Just注意到,我以前的解决方案过于冗长,不需要任何临时数据结构。

I am not sure, what you mean with "purely functional". 我不确定,您所说的“纯功能”是什么意思。 The following solution (if it is a solution to your problem) is "purely functional", as it has no side effects except the final println call in main . 以下解决方案(如果它是您的问题的解决方案)是“纯功能的”,因为它除了main的最终println调用外没有其他副作用。

Note, that the List[F[_]](...) is important, since otherwise, the compiler will infer a very specific internal type for the elements of the list, which doesn't go well with the aggregateAndProcess function. 请注意, List[F[_]](...)很重要,因为否则,编译器将为列表的元素推断出非常特定的内部类型,这与aggregateAndProcess函数配合得并不好。

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)
    }
}

Note, though, that I am still unsure as to what you actually want to accomplish. 但是请注意,我仍然不确定您实际想要完成什么。 The F interface doesn't really specify, which information flows from which method into whatever location at what time, so: this is still a best-guess efford. F接口并没有真正指定哪个信息在什么时候从哪种方法流到任何位置,因此:这仍然是最好的选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM