98 votes

Que signifie "abstrait sur" ?

Souvent dans la littérature Scala, je rencontre l'expression "abstract over", mais je ne comprends pas l'intention. Par exemple Martin Odersky écrit

Vous pouvez passer des méthodes (ou "fonctions") en tant que paramètres, ou vous pouvez abstrait sur les. Vous pouvez spécifier les types en tant que paramètres, ou vous pouvez abstrait sur les.

Autre exemple, dans le "Dépréciation du modèle de l'observateur" papier,

Une conséquence de nos flux d'événements étant des valeurs de première classe est que nous pouvons abstrait sur les.

J'ai lu que les génériques de premier ordre "s'abstraient des types", tandis que les monades "s'abstraient des constructeurs de types". Et nous voyons également des phrases comme celle-ci dans le Papier à motifs pour gâteaux . Pour citer l'un de ces nombreux exemples :

Les membres de type abstrait fournissent un moyen flexible de abstrait sur les types de composants en béton.

Même les questions pertinentes de stack overflow utilisent cette terminologie. "On ne peut pas abstraire existentiellement un type paramétré..."

Alors... que signifie réellement "abstraire" ?

129voto

Apocalisp Points 22526

En algèbre, comme dans la formation des concepts quotidiens, les abstractions sont formées en regroupant les choses par certaines caractéristiques essentielles et en omettant leurs autres caractéristiques spécifiques. L'abstraction est unifiée sous un seul symbole ou mot dénotant les similitudes. Nous disons que nous abstrait sur les différences, mais cela signifie vraiment que nous sommes intégrant par les similitudes.

Par exemple, considérons un programme qui prend la somme des nombres 1 , 2 et 3 :

val sumOfOneTwoThree = 1 + 2 + 3

Ce programme n'est pas très intéressant, car il n'est pas très abstrait. On peut abstrait sur les nombres que l'on additionne, en intégrant toutes les listes de nombres sous un seul symbole ns :

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

Et nous ne nous soucions pas particulièrement du fait qu'il s'agisse d'une liste. List est un constructeur de type spécifique (prend un type et retourne un type), mais nous pouvons abstrait sur le constructeur de type en spécifiant la caractéristique essentielle que nous voulons (qu'il puisse être plié) :

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)

Et nous pouvons avoir des implicites Foldable instances pour List et toute autre chose que nous pouvons plier.

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

De plus, nous pouvons abstrait sur à la fois l'opération et le type des opérandes :

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)

Maintenant, nous avons quelque chose d'assez général. La méthode mapReduce pliera tout F[A] étant donné que nous pouvons prouver que F est pliable et que A est un monoïde ou peut être transformé en un monoïde. Par exemple :

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)

Nous avons Abstraction faite de monoïdes et pliables.

0 votes

@coubeatczech Le code fonctionne bien sur REPL. Quelle version de Scala utilisez-vous, et quelle erreur avez-vous obtenue ?

1 votes

@Apocalisp Il serait intéressant que vous fassiez de l'un des deux derniers exemples un Set ou un autre type de pliage. Un exemple avec un String et la concaténation serait également assez cool.

1 votes

Belle réponse, Runar. Merci ! J'ai suivi la suggestion de Daniel et créé des setFoldable et concatMonoid implicites, sans modifier mapReduce du tout. Je suis en bonne voie pour comprendre tout cela.

12voto

Dave Griffith Points 12923

En première approximation, être capable de "faire abstraction" de quelque chose signifie qu'au lieu d'utiliser cette chose directement, vous pouvez en faire un paramètre ou l'utiliser de manière "anonyme".

Scala vous permet de faire abstraction des types, en autorisant les classes, les méthodes et les valeurs à avoir des paramètres de type, et les valeurs à avoir des types abstraits (ou anonymes).

Scala permet de s'abstraire des actions, en autorisant les méthodes à avoir des paramètres de fonction.

Scala vous permet de faire abstraction des caractéristiques, en permettant aux types d'être définis de manière structurelle.

Scala vous permet d'abstraire les paramètres de type, en autorisant les paramètres de type d'ordre supérieur.

Scala vous permet d'abstraire les modèles d'accès aux données, en vous permettant de créer des extracteurs.

Scala vous permet d'abstraire les "choses qui peuvent être utilisées comme autre chose", en autorisant les conversions implicites comme paramètres. Haskell fait de même avec les classes de types.

Scala ne permet pas (encore) d'abstraire des classes. Vous ne pouvez pas passer une classe à quelque chose, et ensuite utiliser cette classe pour créer de nouveaux objets. D'autres langages permettent l'abstraction sur les classes.

("Les monades s'abstraient des constructeurs de types" n'est vrai que de manière très restrictive. Ne vous y attardez pas jusqu'à ce que vous ayez votre moment "Aha ! je comprends les monades !").

La capacité d'abstraire certains aspects du calcul est essentiellement ce qui permet la réutilisation du code et la création de bibliothèques de fonctionnalités. Scala permet de faire abstraction de beaucoup plus de choses que les langages plus courants, et les bibliothèques en Scala peuvent être plus puissantes en conséquence.

1 votes

Vous pouvez passer un Manifest ou même un Class et utiliser la réflexion pour instancier de nouveaux objets de cette classe.

6voto

Une abstraction est une sorte de généralisation.

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

Non seulement en Scala, mais dans de nombreux langages, il est nécessaire de disposer de tels mécanismes pour réduire la complexité (ou au moins créer une hiérarchie qui divise les informations en éléments plus faciles à comprendre).

Une classe est une abstraction sur un type de données simple. Elle est en quelque sorte comme un type de base, mais elle les généralise. Une classe est donc plus qu'un simple type de données, mais elle a beaucoup de choses en commun avec lui.

Quand il dit "faire abstraction", il veut dire le processus par lequel on généralise. Par exemple, au lieu de passer des méthodes aux fonctions, vous pourriez créer une sorte de méthode généralisée pour les gérer (comme ne pas passer de méthodes du tout mais construire un système spécial pour les gérer).

Dans ce cas, il s'agit spécifiquement du processus d'abstraction d'un problème et de la création d'une solution de type "oop" au problème. Le C a très peu de capacité d'abstraction (vous pouvez le faire mais cela devient vite compliqué et le langage ne le supporte pas directement). Si vous l'écriviez en C++, vous pourriez utiliser les concepts d'OOP pour réduire la complexité du problème (en fait, c'est la même complexité mais la conceptualisation est généralement plus facile (du moins une fois que vous avez appris à penser en termes d'abstractions)).

Par exemple, si j'avais besoin d'un type de données spécial semblable à un int mais, disons, restreint, je pourrais l'abstraire en créant un nouveau type qui pourrait être utilisé comme un int mais qui aurait les propriétés dont j'ai besoin. Le processus que j'utiliserais pour faire une telle chose s'appellerait une "abstraction".

5voto

huynhjl Points 26045

Voici mon interprétation de l'étroitesse du spectacle. C'est auto-explicatif et fonctionne dans le 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 votes

À peu près l'idée de "abstract over" en code - puissant mais court, j'essaierai ce langage +1

2voto

Landei Points 30509

Les autres réponses donnent déjà une bonne idée des types d'abstractions qui existent. Reprenons les citations une par une, et donnons un exemple :

Vous pouvez passer des méthodes (ou "fonctions") comme paramètres, ou vous pouvez faire abstraction au-dessus d'elles. Vous pouvez spécifier des types en tant que ou vous pouvez faire abstraction des sur eux.

Passer la fonction en tant que paramètre : List(1,-2,3).map(math.abs(x)) Clairement abs est passé comme paramètre ici. map elle-même fait abstraction d'une fonction qui fait une certaine chose particulière avec chaque élément de la liste. val list = List[String]() spécifie un paramètre de type (String). Vous pouvez écrire un type de collection qui utilise des membres de type abstrait à la place : val buffer = Buffer{ type Elem=String } . Une différence est que vous devez écrire def f(lis:List[String])... mais def f(buffer:Buffer)... Le type d'élément est donc en quelque sorte "caché" dans la deuxième méthode.

Une conséquence de nos flux d'événements étant des valeurs de première classe, nous pouvons les abstraire.

Dans Swing, un événement se produit tout d'un coup, et vous devez le gérer ici et maintenant. Les flux d'événements vous permettent de faire toute la plomberie et le câblage d'une manière plus déclarative. Par exemple, lorsque vous voulez changer l'écouteur responsable dans Swing, vous devez désenregistrer l'ancien et enregistrer le nouveau, et connaître tous les détails sanglants (par exemple les problèmes de threading). Avec les flux d'événements, le source des événements devient un objet que l'on peut simplement faire circuler, ce qui ne le rend pas très différent d'un flux d'octets ou de caractères, d'où un concept plus "abstrait".

Les membres de type abstrait fournissent un moyen flexible flexible pour s'abstraire des types concrets de composants.

La classe Buffer ci-dessus en est déjà un exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X