“抽象结束”是什么意思?
通常在斯卡拉文学中,我遇到了“抽象结束”这个短语,但我不明白这个意图。 例如 ,Martin Odersky写道
你可以传递方法(或“函数”)作为参数,或者你可以抽象它们。 你可以指定types作为参数,或者你可以抽象它们。
又如,在“弃用观察者模式”论文中,
我们事件stream成为一stream价值的结果是我们可以对它们进行抽象 。
我已经读过第一类generics“抽象types”,而monad“抽象types构造函数”。 而且我们也在蛋糕模式纸上看到这样的短语。 引用许多这样的例子之一:
抽象types成员提供了对具体types的组件进行抽象的灵活方法。
甚至相关的堆栈溢出问题也使用这个术语。 “不能存在抽象的参数化types…”
那么……“抽象结束”究竟是什么意思呢?
在代math中,与日常概念形成一样,抽象是由一些本质特征将单位分组而形成的,而忽略了其特殊性。 抽象是统一在一个单一的符号或文字表示相似之处。 我们说,我们抽象的差异,但这真的意味着我们正在融合的相似之处。
例如,考虑采用数字1
和3
和的程序:
val sumOfOneTwoThree = 1 + 2 + 3
这个程序不是很有趣,因为它不是很抽象。 所以我们可以通过在单个符号下集成所有数字列表来抽象特定的数字ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
而且我们也不特别在乎它是一个List。 List是一个特定types的构造函数(接受一个types并且返回一个types),但是我们可以通过指定我们想要的哪个基本特性(可以折叠)来抽象这个types的构造函数:
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))
而且,我们可以抽象出操作数的操作和types:
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]
因为我们可以certificateF
是可折叠的,而A
是一个monoid或者可以映射成一个。 例如:
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和foldables。
大致来说,能够“抽象出”某种东西,意味着不是直接使用某种东西,而是可以为其创build一个参数,或者以其他方式“匿名”使用它。
Scala允许你抽象types,允许类,方法和值有types参数,值有抽象(或匿名)types。
通过允许方法具有函数参数,Scala允许您抽象操作。
通过允许types在结构上定义,Scala允许您抽象出特征。
通过允许更高阶的types参数,Scala允许你抽象types参数。
通过允许您创build提取器,Scala允许您抽象数据访问模式。
通过允许隐式转换作为参数,Scala允许您抽象“可以用作其他东西的东西”。 Haskell与types类相似。
斯卡拉(还)不允许你抽象的类。 你不能将某个类传递给某个东西,然后使用该类来创build新的对象。 其他语言允许对类进行抽象。
(Monads abstract over type constructors)只是一种非常严格的方式,不要挂在上面,直到你有了“啊哈,我明白单子!”的时刻。
抽象计算的某个方面的能力基本上是允许代码重用的,并且允许创buildfunction库。 与其他主stream语言相比,Scala允许抽象出更多types的东西,而Scala中的库可以相应地更加强大。
抽象是一种泛化。
http://en.wikipedia.org/wiki/Abstraction
不仅在斯卡拉语言,但许多语言需要有这样的机制,以减less复杂性(或至less创build一个层次结构,将信息分成易于理解的部分)。
一个类是一个简单的数据types的抽象。 这有点像一个基本的types,但实际上概括了它们。 所以一个类不仅仅是一个简单的数据types,而是有许多共同的东西。
当他说“抽象”时,他指的是你概括的过程。 所以,如果你正在抽象方法作为参数,你正在推广这个过程。 例如,不是将方法传递给函数,而是创build一些types的通用方法来处理它(例如,不传递方法,而是build立一个特殊的系统来处理它)。
在这种情况下,他具体指的是抽象问题和创build类似问题的解决scheme。 C几乎没有抽象的能力(你可以这样做,但实际上很麻烦,语言不直接支持它)。 如果你用C ++编写的话,你可以使用oop概念来减less问题的复杂性(好吧,复杂度相同,但概念化通常更容易一些(至less一次你学会了抽象思维))。
例如,如果我需要一个特殊的数据types,就像一个int,但是,可以说限制我可以通过创build一个新的types,可以像int一样使用,但有我需要的属性。 我将用来做这样的事情的过程被称为“抽象”。
这是我的狭窄表演,并告诉解释。 这是不言自明的,并在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()
其他答案已经提供了一个很好的概念存在什么样的抽象。 让我们一个接一个的去引用,举个例子:
你可以传递方法(或“函数”)作为参数,或者你可以抽象它们。 你可以指定types作为参数,或者你可以抽象它们。
传递函数作为参数: List(1,-2,3).map(math.abs(x))
显然, abs
在这里作为parameter passing。 map
本身抽象了一个函数,每个列表元素都做了一些特殊的事情。 val list = List[String]()
指定一个types参数(String)。 您可以编写一个使用抽象types成员的集合types: val buffer = Buffer{ type Elem=String }
。 一个区别是你必须写def f(lis:List[String])...
但是def f(buffer:Buffer)...
,所以在第二种方法中元素types是“隐藏的”。
我们事件stream成为一stream价值的结果是我们可以对它们进行抽象。
在摆动事件只是“发生”的蓝色,你必须在这里处理它现在。 事件stream允许您以更具说明性的方式完成所有的接线工作。 例如,当你想改变Swing中负责任的监听器时,你必须注销旧的,注册新的,并了解所有的细节(例如线程问题)。 通过事件stream,事件的来源变成了一个可以简单传递的事物,与字节或字符stream没有太大区别,因此是一个更“抽象”的概念。
抽象types成员提供了对具体types的组件进行抽象的灵活方法。
上面的Buffer类已经是一个例子了。