“抽象”是什么意思?

时间:2011-01-22 01:19:59

标签: scala abstraction

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

  

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

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

  

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

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

  

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

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

那么......“抽象”究竟意味着什么?

6 个答案:

答案 0 :(得分:115)

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

例如,考虑一个采用数字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)

我们可以为Foldable提供隐式List个实例以及我们可以折叠的任何其他内容。

implicit val listFoldable = new Foldable[List] {
  def foldl[A, B](as: List[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(List(4,5,6), Product) 。例如:

{{1}}

我们已经抽象了 monoids和foldables。

答案 1 :(得分:10)

对于第一个近似,能够“抽象”某事意味着不是直接使用某个东西,而是可以创建它的参数,或者“匿名”使用它。

Scala允许您通过允许类,方法和值具有类型参数来抽象类型,并允许值具有抽象(或匿名)类型。

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

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

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

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

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

Scala(尚未)允许您对类进行抽象。您不能将类传递给某个东西,然后使用该类创建新对象。其他语言允许抽象类。

(“Monads abstract over type constructors”只是以非常严格的方式才是真的。在你拥有“啊哈!我理解monads !!”时,不要挂断它。)

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

答案 2 :(得分:6)

抽象是一种概括。

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

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

类是对简单数据类型的抽象。它有点像基本类型但实际上概括了它们。因此,类不仅仅是一种简单的数据类型,而且与它有许多共同之处。

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

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

例如,如果我需要一个类似于int的特殊数据类型,但是,让我说限制我可以通过创建一个可以像int一样使用但具有我需要的属性的新类型来对其进行抽象。我用来做这种事情的过程将被称为“抽象”。

答案 3 :(得分:5)

这是我的狭隘表演并讲述解释。它不言自明,在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()

答案 4 :(得分:2)

其他答案已经很好地了解了存在哪种抽象。让我们一个一个地翻看引号,并提供一个例子:

  

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

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

  

我们的事件流的结果   作为一流的价值观是我们   可以抽象出来。

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

  

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

上面的Buffer类就是一个例子。

答案 5 :(得分:0)

上面的答案提供了很好的解释,但总结起来,我会说:

  

对某事物进行抽象在无关紧要的地方忽略它非常相似。