“抽象”是什么意思?


95

在Scala文献中,我经常遇到“抽象”一词,但我不明白其意图。 例如,马丁·奥德斯基(Martin Odersky)写道

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

再举一个例子,在“弃用观察者模式”一文中,

事件流是一流值的结果是我们可以对其进行抽象

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

抽象类型成员提供了一种灵活的方式来对组件的具体类型进行抽象

甚至相关的堆栈溢出问题也使用此术语。 “不能在参数化类型上存在抽象……”

那么...“抽象”实际上是什么意思?

Answers:


124

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

例如,考虑一个程序,它的数字的总和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)

现在我们有了一些通用的东西。只要我们可以证明它是可折叠的并且是一个monoid或可以映射为一个,就可以mapReduce折叠任何方法。例如:F[A]FA

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)

我们已经 monoid和可折叠对象进行了抽象


@coubeatczech代码在REPL上运行良好。您使用的是什么版本的Scala,并且遇到了什么错误?
Daniel C. Sobral

1
@Apocalisp如果您将两个最终示例之一Set或其他可折叠类型制成,这将很有趣。带String和串联的示例也很酷。
Daniel C. Sobral

1
美丽的答案,露娜 谢谢!我遵循了Daniel的建议,并创建了隐式setFoldable和concatMonoid,而完全没有更改mapReduce。我正在努力解决这个问题。
Morgan Creighton

6
我花了一点时间在最后两行中利用了以下事实:Sum和Product随行对象(因为它们定义了apply(Int))被Scala视为Int => Sum和Int => Product编译器。非常好!
Kris Nuttycombe 2011年

好贴 :)!在您的最后一个示例中,似乎没有必要使用Monoid隐式逻辑。这更简单:gist.github.com/cvogt/9716490
cvogt 2014年

11

初步近似,能够“抽象化”某事物意味着您可以直接为其设置参数,或者以其他方式“匿名”使用它,而不是直接使用该事物。

Scala通过允许类,方法和值具有类型参数,而值具有抽象(或匿名)类型,使您可以对类型进行抽象。

Scala通过允许方法具有函数参数,使您可以抽象动作。

Scala通过允许在结构上定义类型,使您可以对功能进行抽象。

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

Scala通过允许您创建提取器,使您可以抽象化数据访问模式。

通过使用隐式转换作为参数,Scala允许您抽象化“可以用作其他东西的东西”。Haskell对类型类也做了类似的处理。

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

(“ Monad在类型构造函数上抽象”仅以非常严格的方式适用。在您遇到“啊哈!我理解monads!”的那一刻之前,请不要挂断电话。)

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


1
您可以传递Manifest,甚至传递Class,并使用反射实例化该类的新对象。
Daniel C. Sobral

6

抽象是一种概括。

http://en.wikipedia.org/wiki/抽象

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

类是对简单数据类型的抽象。它有点像基本类型,但实际上将其概括化。因此,一个类不仅仅是一个简单的数据类型,但还有很多共同点。

当他说“抽象”时,他表示概括的过程。因此,如果您将方法抽象为参数,则可以概括该过程。例如,您可以创建某种通用的方式来处理它,而不是将方法传递给函数(例如,根本不传递方法,而是建立一个特殊的系统来处理它)。

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

例如,如果我需要一个像int这样的特殊数据类型,但是可以说,受限的话,我可以通过创建一个可以像int一样使用但具有我需要的属性的新类型对其进行抽象。我用于执行此类操作的过程称为“抽象”。


5

这是我的狭show表演和讲解。它是不言自明的,并且在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
功能强大但简短的代码中的“抽象”概念几乎可以尝试使用该语言+1
user44298 2011年

2

其他答案已经很好地说明了存在哪种抽象。让我们逐个检查报价,并提供一个示例:

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

将函数作为参数传递: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])...but def f(buffer:Buffer)...,因此元素类型在第二种方法中有点“隐藏”。

事件流是一流值的结果是我们可以对其进行抽象。

在Swing中,事件只是突然发生的,您必须在此时此地处理它。事件流使您能够以更具声明性的方式完成所有布线。例如,当您想在Swing中更改负责的侦听器时,您必须注销旧的并注册新的监听器,并知道所有棘手的细节(例如,线程问题)。有了事件流,事件的来源就变成了一件事情,您可以简单地传递它,使其与字节流或char流没有太大区别,因此是一个更为“抽象”的概念。

抽象类型成员提供了一种灵活的方式来对组件的具体类型进行抽象。

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


0

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

对某事物进行抽象在无关紧要的地方忽略它是一样的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.