scala abstract over是什么意思

jpfvwuh4  于 2023-04-30  发布在  Scala
关注(0)|答案(7)|浏览(155)

在Scala文献中,我经常遇到“abstract over”这个短语,但我不理解其意图。For example,马丁·奥德斯基写道
你可以将方法(或“函数”)作为参数传递,或者你可以对它们进行抽象。你可以将类型指定为参数,或者你可以对它们进行抽象。
另一个例子,在"Deprecating the Observer Pattern"论文中,
我们的事件流是一级值的一个结果是,我们可以对它们进行抽象。
我读到过一阶泛型“抽象于类型”,而单子“抽象于类型构造器”。我们也在Cake Pattern paper中看到这样的短语。这里引用许多这样的例子中的一个:
抽象类型成员提供了灵活的方式来抽象具体类型的组件。
甚至相关的堆栈溢出问题也使用此术语。"can't existentially abstract over parameterized type..."
那么...“abstract over”到底是什么意思?

n3h0vuf2

n3h0vuf21#

在代数中,如同在日常概念形成中一样,抽象是通过将事物的某些本质特征分组而省略其特定的其他特征而形成的。抽象统一在表示相似性的单个符号或单词下。我们说我们“抽象”了差异,但这实际上意味着我们是在“整合”相似之处。
例如,考虑一个程序,它取数字123的和:

val sumOfOneTwoThree = 1 + 2 + 3

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

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

我们也不特别关心它是不是一个List。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是一个幺半群或者可以Map成幺半群。例如:

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)

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

xwmevbvl

xwmevbvl2#

首先,能够“抽象”某物意味着不是直接使用该某物,而是可以为其设置参数,或者以其他方式“匿名”使用它。
Scala允许对类型进行抽象,允许类、方法和值具有类型参数,允许值具有抽象(或匿名)类型。
Scala允许你抽象操作,允许方法有函数参数。
Scala允许您抽象特性,允许类型在结构上定义。
Scala允许您抽象类型参数,通过允许高阶类型参数。
Scala允许您创建提取器,从而对数据访问模式进行抽象。
Scala允许你抽象“可以用作其他东西的东西”,通过允许隐式转换作为参数。Haskell对类型类也是如此。
Scala还不允许抽象类。你不能将一个类传递给某个对象,然后用这个类来创建新对象。其他语言确实允许抽象类。
(“Monads abstract over type constructors”仅在非常严格的情况下为真。不要挂在上面,直到你有你的“啊哈!我理解单子!!”片刻。)
对计算的某些方面进行抽象的能力基本上是允许代码重用的能力,并且允许创建功能库。与主流语言相比,Scala允许更多种类的东西被抽象,并且Scala中的库可以相应地更强大。

72qzrwbm

72qzrwbm3#

抽象是一种概括。
http://en.wikipedia.org/wiki/Abstraction
不仅在Scala中,而且在许多语言中,都需要有这样的机制来降低复杂性(或者至少创建一个层次结构,将信息划分为更容易理解的部分)。
类是对简单数据类型的抽象。它有点像一个基本类型,但实际上是泛化它们。因此,类不仅仅是一个简单的数据类型,而且与它有许多共同之处。
当他说“抽象化”时,他指的是你概括的过程。因此,如果你把方法抽象为参数,你就是在泛化这样做的过程。例如,你可以创建某种类型的通用方法来处理它,而不是将方法传递给函数(例如根本不传递方法,而是构建一个特殊的系统来处理它)。
在这种情况下,他具体指的是抽象问题并创建问题的类似oop的解决方案的过程。C几乎没有抽象的能力(你可以做到,但它真实的混乱,语言不直接支持它)。如果你用C++编写,你可以使用oop概念来降低问题的复杂性(嗯,它的复杂性是一样的,但概念化通常更容易(至少在你学会用抽象来思考的时候))。
例如,如果我需要一个特殊的数据类型,它像一个int,但是,让我们说限制,我可以通过创建一个新的类型来抽象它,它可以像一个int一样使用,但具有我需要的那些属性。我用来做这样一件事的过程叫做“抽象”。

zsbz8rwp

zsbz8rwp4#

以下是我的狭义解释。它是不言自明的,在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()
hkmswyz6

hkmswyz65#

其他的答案已经给予了一个很好的概念,什么样的抽象存在。让我们一个接一个地回顾这些引用,并提供一个例子:
您可以将方法(或“函数”)作为参数传递,也可以对其进行抽象。您可以将类型指定为参数,也可以对其进行抽象。
将函数作为参数传递: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类已经是一个例子。

gr8qqesn

gr8qqesn6#

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

  • 抽象的东西 * 是非常相同的 * 忽略它无关 *。
8xiog9wr

8xiog9wr7#

抽象“over”意味着形成一个抽象,可以统一地应用于所考虑的某个事物集合中的每个元素。你要克服的是布景。这是非正式的数学术语。

相关问题