【问题标题】:What does "abstract over" mean?“抽象”是什么意思?
【发布时间】:2011-06-13 12:37:01
【问题描述】:

在 Scala 文献中,我经常遇到“abstract over”这个短语,但我不明白其意图。 For example,Martin Odersky 写道

您可以将方法(或“函数”)作为参数传递,也可以抽象它们。您可以将类型指定为参数,也可以抽象它们。

另一个例子,在"Deprecating the Observer Pattern"论文中,

我们的事件流是一流值的一个结果是我们可以抽象它们。

我读过一阶泛型“抽象于类型”,而 monads“抽象于类型构造函数”。我们还在Cake Pattern paper 中看到类似这样的短语。引用许多这样的例子之一:

抽象类型成员提供灵活的方式来抽象具体类型的组件。

即使是相关的堆栈溢出问题也使用此术语。 "can't existentially abstract over parameterized type..."

那么...“抽象”到底是什么意思?

【问题讨论】:

    标签: scala abstraction


    【解决方案1】:

    在代数中,就像在日常概念形成中一样,抽象是通过按一些基本特征对事物进行分组并省略其特定的其他特征而形成的。抽象统一在一个表示相似性的符号或单词下。我们说我们抽象差异,但这实际上意味着我们正在整合相似之处。

    例如,考虑一个程序,它采用数字 123 的总和:

    val sumOfOneTwoThree = 1 + 2 + 3
    

    这个程序不是很有趣,因为它不是很抽象。我们可以抽象我们正在求和的数字,通过在单个符号ns 下整合所有数字列表:

    def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
    

    而且我们也不特别关心它是否是一个列表。 List 是一个特定的类型构造函数(接受一个类型并返回一个类型),但我们可以通过指定我们想要的基本特征(它可以折叠)来抽象类型构造函数:

    trait Foldable[F[_]] {
      def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
    }
    
    def sumOf[F[_]](ns: F[Int])(implicit ff: Foldable[F]) =
      ff.foldl(ns, 0, (x: Int, y: Int) => x + y)
    

    我们可以为List 和任何其他我们可以折叠的东西提供隐含的Foldable 实例。

    implicit val listFoldable = new Foldable[List] {
      def foldl[A, B](as: List[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)
    }
    
    implicit val setFoldable = new Foldable[Set] {
      def foldl[A, B](as: Set[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)
    }
    
    val sumOfOneTwoThree = sumOf(List(1,2,3))
    

    更重要的是,我们可以抽象操作和操作数的类型:

    trait Monoid[M] {
      def zero: M
      def add(m1: M, m2: M): M
    }
    
    trait Foldable[F[_]] {
      def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
      def foldMap[A, B](as: F[A], f: A => B)(implicit m: Monoid[B]): B =
        foldl(as, m.zero, (b: B, a: A) => m.add(b, f(a)))
    }
    
    def mapReduce[F[_], A, B](as: F[A], f: A => B)
                             (implicit ff: Foldable[F], m: Monoid[B]) =
      ff.foldMap(as, f)
    

    现在我们有了一些非常普遍的东西。方法mapReduce 将折叠任何F[A],因为我们可以证明F 是可折叠的并且A 是一个幺半群或可以映射成一个。例如:

    case class Sum(value: Int)
    case class Product(value: Int)
    
    implicit val sumMonoid = new Monoid[Sum] {
      def zero = Sum(0)
      def add(a: Sum, b: Sum) = Sum(a.value + b.value)
    }
    
    implicit val productMonoid = new Monoid[Product] {
      def zero = Product(1)
      def add(a: Product, b: Product) = Product(a.value * b.value)
    }
    
    val sumOf123 = mapReduce(List(1,2,3), Sum)
    val productOf456 = mapReduce(Set(4,5,6), Product)
    

    我们已经抽象了个幺半群和可折叠。

    【讨论】:

    • @coubeatczech 代码在 REPL 上运行良好。您使用的是哪个版本的 Scala,遇到了什么错误?
    • @Apocalisp 如果您将最后两个示例之一设为Set 或其他可折叠类型,将会很有趣。带有String 和串联的示例也很酷。
    • 美丽的答案,Runar。谢谢!我按照 Daniel 的建议,创建了隐式 setFoldable 和 concatMonoid,根本没有改变 mapReduce。我正在努力解决这个问题。
    • 我花了一点时间才明白,在最后两行中,您利用 Sum 和 Product 伴随对象这一事实,因为它们定义了 apply(Int),因此被视为 Int => Sum和 Scala 编译器的 Int => Product。非常好!
    • 好帖子:)!在您的最后一个示例中, Monoid 隐式逻辑似乎是不必要的。这更简单:gist.github.com/cvogt/9716490
    【解决方案2】:

    大致而言,能够“抽象”某事物意味着您可以为其创建一个参数,而不是直接使用该事物,或者以其他方式“匿名”使用它。

    Scala 允许您对类型进行抽象,方法是允许类、方法和值具有类型参数,并允许值具有抽象(或匿名)类型。

    Scala 允许您通过允许方法具有函数参数来抽象操作。

    Scala 允许您通过允许在结构上定义类型来抽象特征。

    Scala 允许您通过允许高阶类型参数来抽象类型参数。

    Scala 允许您通过创建提取器来抽象数据访问模式。

    Scala 允许您通过允许隐式转换作为参数来抽象“可以用作其他东西的东西”。 Haskell 对类型类也是如此。

    Scala (还)不允许您对类进行抽象。您不能将类传递给某物,然后使用该类创建新对象。其他语言确实允许对类进行抽象。

    (“Monads abstract over type constructors”仅在非常严格的情况下才成立。在你有“啊哈!我理解 monads !!”时刻之前不要挂断它。)

    抽象计算的某些方面的能力基本上允许代码重用,并支持创建功能库。与更主流的语言相比,Scala 允许抽象更多种类的东西,Scala 中的库也可以相应地更强大。

    【讨论】:

    • 您可以传递Manifest,甚至是Class,并使用反射来实例化该类的新对象。
    【解决方案3】:

    抽象是一种概括。

    http://en.wikipedia.org/wiki/Abstraction

    不仅在 Scala 中,而且在许多语言中,都需要有这样的机制来降低复杂性(或者至少创建一个层次结构,将信息划分为更易于理解的部分)。

    类是对简单数据类型的抽象。它有点像基本类型,但实际上概括了它们。所以一个类不仅仅是一个简单的数据类型,而是有很多共同点。

    当他说“抽象”时,他指的是你概括的过程。因此,如果您将方法抽象为参数,那么您就是在概括执行此操作的过程。例如,与其将方法传递给函数,不如创建某种通用的方法来处理它(例如根本不传递方法,而是建立一个特殊的系统来处理它)。

    在这种情况下,他特指抽象问题并为问题创建类似 oop 的解决方案的过程。 C 几乎没有抽象能力(你可以做到,但它很快就会变得一团糟,而且语言不直接支持它)。如果你用 C++ 编写它,你可以使用 oop 概念来降低问题的复杂性(嗯,它是相同的复杂性,但概念化通常更容易(至少一旦你学会用抽象来思考)。

    例如,如果我需要一个类似于 int 的特殊数据类型,但假设是受限的,我可以通过创建一个可以像 int 一样使用但具有我需要的属性的新类型来抽象它。我用来做这种事情的过程称为“抽象”。

    【讨论】:

      【解决方案4】:

      这是我狭隘的表演和讲述的解释。它是不言自明的,并且在 REPL 中运行。

      class Parameterized[T] { // type as a parameter
        def call(func: (Int) => Int) = func(1)  // function as a parameter
        def use(l: Long) { println(l) } // value as a parameter
      }
      
      val p = new Parameterized[String] // pass type String as a parameter
      p.call((i:Int) => i + 1) // pass function increment as a parameter
      p.use(1L) // pass value 1L as a parameter
      
      
      abstract class Abstracted { 
        type T // abstract over a type
        def call(i: Int): Int // abstract over a function
        val l: Long // abstract over value
        def use() { println(l) }
      }
      
      class Concrete extends Abstracted { 
        type T = String // specialize type as String
        def call(i:Int): Int = i + 1 // specialize function as increment function
        val l = 1L // specialize value as 1L
      }
      
      val a: Abstracted = new Concrete
      a.call(1)
      a.use()
      

      【讨论】:

      • 几乎是代码中的“抽象”思想-强大而简短,将尝试这种语言+1
      【解决方案5】:

      其他答案已经很好地说明了存在哪些抽象。让我们一一回顾引号,并提供一个例子:

      您可以传递方法(或“函数”) 作为参数,或者你可以抽象 在他们之上。您可以将类型指定为 参数,或者你可以抽象 他们。

      将函数作为参数传递:List(1,-2,3).map(math.abs(x)) 显然abs 在这里作为参数传递。 map 本身抽象了一个函数,该函数对每个列表元素执行特定的特殊操作。 val list = List[String]() 指定类型参数(字符串)。您可以编写一个使用抽象类型成员的集合类型:val buffer = Buffer{ type Elem=String }。一个区别是你必须写def f(lis:List[String])...def f(buffer:Buffer)...,所以元素类型在第二种方法中是一种“隐藏”。

      我们的事件流的结果 成为一流的价值观是我们 可以抽象它们。

      在 Swing 中,事件只是突然“发生”,你必须在此时此地处理它。事件流允许您以更具声明性的方式完成所有管道连接。例如。当您想在 Swing 中更改负责的侦听器时,您必须取消注册旧的并注册新的,并了解所有血腥细节(例如线程问题)。对于事件流,事件的变成了你可以简单地传递的东西,使它与字节或字符流没有太大区别,因此是一个更“抽象”的概念。

      抽象类型成员提供灵活的 抽象具体类型的方法 组件。

      上面的 Buffer 类已经是一个例子。

      【讨论】:

        【解决方案6】:

        上面的答案提供了一个很好的解释,但是用一句话来概括它,我会说:

        对某事进行抽象在不相关的地方忽略它是一样的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-04-07
          • 2014-08-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多