【问题标题】:Is it possible to work with a list of generic values with different type parameters in Scala?是否可以在 Scala 中使用具有不同类型参数的泛型值列表?
【发布时间】:2013-02-11 03:51:51
【问题描述】:

我想实现以下目标:

  1. 有一个我需要处理的字符串列表。
  2. 这些处理器有几种不同的种类,每一种都知道要读取字符串的哪一部分。
  3. 我需要分两个阶段工作:首先,处理器需要查看每个输入字符串以构建特定于处理器的数据;其次,每个输入字符串由每个处理器处理,并将生成的字符串合并为一个。

以可变的方式很容易做到这一点:所有处理器都有一个通用的基类,它们聚合的不同类型的数据被封装在具体的实现中;该界面仅包含 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[?] 是不同但一致的,如果我采用该列表的一个元素,它的类型参数有一些具体值,而 f.get(s) 是那种类型,应该被f.aggregate()接受。

最后,我想要这样的东西(因为我不知道怎么做所以有遗漏):

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

问题:

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

【问题讨论】:

  • 你能扩展你的第二个例子吗?您希望以何种顺序应用您的功能尚不清楚。据我了解,您希望您的项目特定数据计算如下: d1 = process(aggregate("s2", aggregate("s1", f1.initData)) ?但是如何使用此数据处理字符串? process(" s1", d1), process("s2", d2) ?还是别的什么?
  • 这听起来像是一个非常简单的两阶段mapping 管道。或者map 后跟reduce...我认为有一个名字...
  • @alno:抱歉,问题中的代码很不清楚。我澄清了一点。

标签: scala generics types functional-programming


【解决方案1】:

编辑刚刚注意到,我以前的解决方案过于冗长,在没有任何需要的情况下使用了一个临时数据结构。

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

请注意,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)
    }
}

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

【讨论】:

  • 我看不出它是如何概括整个问题的,也就是说,使F 不可变。为此,我需要将数据保留在 F 的每个实现之外。
  • 我编辑了问题以包含一些框架代码来说明我想要实现的目标。
  • 要么我不明白你更新的解决方案,要么它不是纯粹的功能性:def aggregate(s: String): Unit = origin.aggregate(origin.get(s), init) 我仍然不知道如何使它成为纯粹的功能性。
  • “纯功能”是指“不在F 的每个实现中存储数据并在aggregate 上对其进行变异”。
  • 您当前的答案纯粹是功能性的,但它在一个阶段有效。我需要先汇总通过线路读取的数据,然后在再次读取线路时进行处理。在您的情况下,当调用 F1.process 时,data 应该是行数。但就像现在一样,每个process 只接收当前行的编号。
【解决方案2】:

另外,你可以使用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 阶段和第 2 阶段遍历 strings 一次。你看,如果我要运行,我需要将我的数据存储在函数之外,或者更新函数本身;但为此,我需要有一个AnyF[Any] 的列表。如何使用这样的列表让我很感兴趣。
猜你喜欢
  • 1970-01-01
  • 2021-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-06
  • 1970-01-01
相关资源
最近更新 更多