Was bedeutet "abstrakt über"?

95

In der Scala-Literatur stoße ich oft auf den Ausdruck "abstrakt über", aber ich verstehe die Absicht nicht. Zum Beispiel schreibt Martin Odersky

Sie können Methoden (oder "Funktionen") als Parameter übergeben oder über diese abstrahieren . Sie können Typen als Parameter angeben oder über sie abstrahieren .

Als ein weiteres Beispiel in dem „ironischen das Observer Pattern“ Papier,

Eine Konsequenz daraus, dass unsere Ereignisströme erstklassige Werte sind, ist, dass wir über sie abstrahieren können.

Ich habe gelesen, dass Generika erster Ordnung "abstrakt über Typen" sind, während Monaden "abstrakt über Typkonstruktoren". Und wir sehen auch solche Sätze im Kuchenmusterpapier . Um eines von vielen solchen Beispielen zu zitieren:

Abstrakte Typelemente bieten eine flexible Möglichkeit, über konkrete Komponententypen zu abstrahieren .

Selbst relevante Fragen zum Stapelüberlauf verwenden diese Terminologie. "kann nicht existenziell über parametrisierten Typ abstrahieren ..."

Also ... was bedeutet eigentlich "abstrakt über"?

Morgan Creighton
quelle

Antworten:

124

In der Algebra wie in der alltäglichen Konzeptbildung werden Abstraktionen gebildet, indem Dinge nach bestimmten wesentlichen Merkmalen gruppiert und ihre spezifischen anderen Merkmale weggelassen werden. Die Abstraktion wird unter einem einzigen Symbol oder Wort vereinheitlicht, das die Ähnlichkeiten bezeichnet. Wir sagen , dass wir abstrakt über die Unterschiede, aber das wirklich bedeutet , dass wir die Integration von den Ähnlichkeiten.

Betrachten wir zum Beispiel ein Programm, das die Summe der Zahlen nimmt 1, 2und 3:

val sumOfOneTwoThree = 1 + 2 + 3

Dieses Programm ist nicht sehr interessant, da es nicht sehr abstrakt ist. Wir können über die Zahlen, die wir summieren, abstrahieren , indem wir alle Listen von Zahlen unter einem einzigen Symbol integrieren ns:

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

Und es ist uns auch nicht besonders wichtig, dass es sich um eine Liste handelt. List ist ein bestimmter Typkonstruktor (nimmt einen Typ und gibt einen Typ zurück), aber wir können über den Typkonstruktor abstrahieren, indem wir angeben, welches wesentliche Merkmal wir wollen (dass es gefaltet werden kann):

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)

Und wir können implizite FoldableInstanzen für Listund alles andere haben, was wir folden können.

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))

Darüber hinaus können wir sowohl die Operation als auch den Typ der Operanden abstrahieren :

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)

Jetzt haben wir etwas ganz Allgemeines. Die Methode mapReducewird alles falten, F[A]vorausgesetzt, wir können beweisen, dass Fes faltbar ist und dass Aes sich um ein Monoid handelt oder dass es in eines abgebildet werden kann. Beispielsweise:

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)

Wir haben über Monoide und Faltblätter abstrahiert .

Apocalisp
quelle
@coubeatczech Der Code läuft auf REPL gut. Welche Version von Scala verwenden Sie und welchen Fehler haben Sie erhalten?
Daniel C. Sobral
1
@Apocalisp Es wäre interessant, wenn Sie eines der beiden letzten Beispiele zu einem Setoder einem anderen faltbaren Typ machen würden. Ein Beispiel mit a Stringund Verkettung wäre auch ziemlich cool.
Daniel C. Sobral
1
Schöne Antwort, Runar. Vielen Dank! Ich folgte Daniels Vorschlag und erstellte implizite setFoldable und concatMonoid, ohne mapReduce überhaupt zu verändern. Ich bin auf dem besten Weg, dies zu tun.
Morgan Creighton
6
Es hat einen Moment gedauert, bis Sie festgestellt haben, dass Sie in den letzten beiden Zeilen die Tatsache ausnutzen, dass die Begleitobjekte Summe und Produkt, da sie gelten (Int) definieren, von der Scala als Int => Summe und Int => Produkt behandelt werden Compiler. Sehr schön!
Kris Nuttycombe
Netter Post :)! In Ihrem letzten Beispiel scheint die implizite Monoid-Logik unnötig zu sein. Dies ist einfacher: gist.github.com/cvogt/9716490
cvogt
11

In erster Näherung bedeutet die Möglichkeit, etwas zu "abstrahieren", dass Sie, anstatt dieses Objekt direkt zu verwenden, einen Parameter daraus erstellen oder es auf andere Weise "anonym" verwenden können.

Mit Scala können Sie über Typen abstrahieren, indem Klassen, Methoden und Werte Typparameter und Werte abstrakte (oder anonyme) Typen haben.

Mit Scala können Sie über Aktionen abstrahieren, indem Methoden Funktionsparameter haben.

Mit Scala können Sie Features abstrahieren, indem Sie Typen strukturell definieren.

Mit Scala können Sie über Typparameter abstrahieren, indem Sie Typparameter höherer Ordnung zulassen.

Mit Scala können Sie Datenzugriffsmuster abstrahieren, indem Sie Extraktoren erstellen.

Mit Scala können Sie über "Dinge, die als etwas anderes verwendet werden können" abstrahieren, indem Sie implizite Konvertierungen als Parameter zulassen. Ähnlich verhält es sich mit Typklassen mit Haskell.

Mit Scala können Sie (noch) nicht über Klassen abstrahieren. Sie können eine Klasse nicht an etwas übergeben und dann mit dieser Klasse neue Objekte erstellen. Andere Sprachen erlauben die Abstraktion über Klassen.

("Monaden abstrakt über Typkonstruktoren" ist nur auf sehr restriktive Weise wahr. Lassen Sie sich nicht aufhängen, bis Sie Ihren "Aha! Ich verstehe Monaden !!" - Moment haben.)

Die Fähigkeit, über einen Aspekt der Berechnung zu abstrahieren, ermöglicht im Grunde die Wiederverwendung von Code und die Erstellung von Funktionsbibliotheken. Mit Scala können viel mehr Dinge abstrahiert werden als mit mehr Mainstream-Sprachen, und Bibliotheken in Scala können entsprechend leistungsfähiger sein.

Dave Griffith
quelle
1
Sie können ein Manifestoder sogar ein übergeben Classund mithilfe der Reflexion neue Objekte dieser Klasse instanziieren.
Daniel C. Sobral
6

Eine Abstraktion ist eine Art Verallgemeinerung.

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

Nicht nur in Scala, sondern in vielen Sprachen sind solche Mechanismen erforderlich, um die Komplexität zu verringern (oder zumindest eine Hierarchie zu erstellen, die Informationen in leichter verständliche Teile aufteilt).

Eine Klasse ist eine Abstraktion über einen einfachen Datentyp. Es ist wie ein Basistyp, verallgemeinert sie jedoch tatsächlich. Eine Klasse ist also mehr als ein einfacher Datentyp, hat aber viele Gemeinsamkeiten.

Wenn er "abstrahieren über" sagt, meint er den Prozess, durch den Sie verallgemeinern. Wenn Sie also Methoden als Parameter abstrahieren, verallgemeinern Sie den Vorgang. Anstatt Methoden an Funktionen zu übergeben, können Sie beispielsweise eine verallgemeinerte Methode erstellen, um damit umzugehen (z. B. Methoden überhaupt nicht übergeben, sondern ein spezielles System aufbauen, um damit umzugehen).

In diesem Fall meint er speziell den Prozess der Abstraktion eines Problems und der Schaffung einer oop-ähnlichen Lösung für das Problem. C kann nur sehr wenig abstrahieren (Sie können es tun, aber es wird sehr schnell chaotisch und die Sprache unterstützt es nicht direkt). Wenn Sie es in C ++ geschrieben haben, können Sie oop-Konzepte verwenden, um die Komplexität des Problems zu reduzieren (nun, es ist dieselbe Komplexität, aber die Konzeptualisierung ist im Allgemeinen einfacher (mindestens wenn Sie lernen, in Abstraktionen zu denken)).

Beispiel: Wenn ich einen speziellen Datentyp benötigte, der einem Int ähnelte, aber beispielsweise eingeschränkt war, konnte ich ihn abstrahieren, indem ich einen neuen Typ erstellte, der wie ein Int verwendet werden konnte, aber die Eigenschaften hatte, die ich brauchte. Der Prozess, den ich verwenden würde, um so etwas zu tun, würde als "Abstraktion" bezeichnet werden.

AbstractDissonance
quelle
5

Hier ist meine enge Show und Tell Interpretation. Es ist selbsterklärend und läuft in der 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()
huynhjl
quelle
1
So ziemlich die Idee "abstrakt über" in Code-mächtig, aber kurz, wird diese Sprache +1
user44298
2

Die anderen Antworten geben bereits eine gute Vorstellung davon, welche Arten von Abstraktionen existieren. Lassen Sie uns die Anführungszeichen einzeln durchgehen und ein Beispiel geben:

Sie können Methoden (oder "Funktionen") als Parameter übergeben oder über diese abstrahieren. Sie können Typen als Parameter angeben oder über sie abstrahieren.

Funktion als Parameter übergeben: List(1,-2,3).map(math.abs(x))Wird abshier eindeutig als Parameter übergeben. mapselbst abstrahiert über eine Funktion, die mit jedem Listenelement eine bestimmte Spezialität ausführt. val list = List[String]()Gibt einen Typparameter (String) an. Sie können einen Sammlungstyp schreiben, der stattdessen abstrakte Typelemente verwendet : val buffer = Buffer{ type Elem=String }. Ein Unterschied besteht darin , dass Sie schreiben müssen , def f(lis:List[String])...aber def f(buffer:Buffer)..., so der Elementtyp Art „versteckt“ in der zweiten Methode ist.

Eine Konsequenz daraus, dass unsere Ereignisströme erstklassige Werte sind, ist, dass wir über sie abstrahieren können.

In Swing "passiert" ein Ereignis einfach aus heiterem Himmel, und Sie müssen sich hier und jetzt damit befassen. Mit Ereignisströmen können Sie die gesamte Installation und Verkabelung deklarativer ausführen. Wenn Sie beispielsweise den verantwortlichen Listener in Swing ändern möchten, müssen Sie die Registrierung des alten und des neuen Listeners aufheben und alle wichtigen Details kennen (z. B. Threading-Probleme). Bei Ereignisströmen wird die Quelle der Ereignisse zu einer Sache, die Sie einfach weitergeben können, wodurch sie sich nicht sehr von einem Byte- oder Zeichenstrom unterscheidet und daher ein "abstrakteres" Konzept darstellt.

Abstrakte Typelemente bieten eine flexible Möglichkeit, über konkrete Komponententypen zu abstrahieren.

Die obige Pufferklasse ist bereits ein Beispiel dafür.

Landei
quelle
0

Die obigen Antworten bieten eine hervorragende Erklärung, aber um sie in einem einzigen Satz zusammenzufassen, würde ich sagen:

Über etwas zu abstrahieren ist dasselbe wie es zu vernachlässigen, wo es irrelevant ist .

Michał Kaczanowicz
quelle