scala - ¿Qué significa "abstracto sobre"?
abstraction (5)
A menudo en la literatura de Scala, encuentro la frase "resumen sobre", pero no entiendo la intención. Por ejemplo , Martin Odersky escribe
Puede pasar métodos (o "funciones") como parámetros, o puede abstraerlos . Puede especificar tipos como parámetros, o puede abstraer sobre ellos.
Como otro ejemplo, en el documento "Deprecating the Observer Pattern" ,
Una consecuencia de que nuestras transmisiones de eventos sean valores de primera clase es que podemos abstraerlas .
He leído que los genéricos de primer orden "resumen sobre tipos", mientras que las mónadas "resumen sobre constructores de tipo". Y también vemos frases como esta en el papel Cake Pattern . Para citar uno de muchos ejemplos de este tipo:
Los miembros de tipo abstracto proporcionan una forma flexible de abstraer sobre tipos concretos de componentes.
Incluso las preguntas relevantes de desbordamiento de pila utilizan esta terminología. "no puede abstraerse existencialmente sobre el tipo parametrizado ..."
Entonces ... ¿qué significa realmente "abstracto"?
Aquí está mi estrecho espectáculo y digo la interpretación. Es autoexplicativo y se ejecuta en 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()
En álgebra, como en la formación del concepto cotidiano, las abstracciones se forman agrupando unidades por algunas características esenciales y omitiendo sus otras características específicas. La abstracción está unificada bajo un único símbolo o palabra que denota sus similitudes. Decimos que abstraemos las diferencias, pero esto realmente significa que nos estamos integrando por las similitudes.
Por ejemplo, considere un programa que toma la suma de los números 1
, 2
y 3
:
val sumOfOneTwoThree = 1 + 2 + 3
Este programa no es muy interesante, ya que no es muy abstracto. Entonces podemos abstraernos sobre los números específicos, integrando todas las listas de números bajo un solo símbolo ns
:
def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)
Y tampoco nos importa particularmente que sea una lista. List es un constructor de tipos específico (toma un tipo y devuelve un tipo), pero podemos abstraer sobre el constructor de tipos especificando qué característica esencial queremos (que se puede plegar):
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)
Y podemos tener instancias Foldable
implícitas para List
y cualquier otra cosa que podamos doblar.
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))
Además, podemos abstraer tanto la operación como el tipo de operandos:
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)
Ahora tenemos algo bastante general. El método mapReduce
cualquier F[A]
dado que podemos probar que F
es plegable y que A
es un monoide o puede mapearse en uno. Por ejemplo:
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)
Hemos abstraído sobre monoides y plegables.
Las otras respuestas ya dan una buena idea de qué tipo de abstracciones existen. Vamos a repasar las citas una a una, y proporcionamos un ejemplo:
Puede pasar métodos (o "funciones") como parámetros, o puede abstraerlos. Puede especificar tipos como parámetros, o puede abstraer sobre ellos.
Pase la función como un parámetro: List(1,-2,3).map(math.abs(x))
Claramente, abs
se pasa como parámetro aquí. map
se abstrae sobre una función que hace una cierta cosa especial con cada elemento de la lista. val list = List[String]()
especifica un tipo paramter (String). En su lugar, podría escribir un tipo de colección que use miembros de tipo abstracto: val buffer = Buffer{ type Elem=String }
. Una diferencia es que tiene que escribir def f(lis:List[String])...
pero def f(buffer:Buffer)...
, por lo que el tipo de elemento está algo "oculto" en el segundo método.
Una consecuencia de que nuestras transmisiones de eventos sean valores de primera clase es que podemos abstraerlas.
En Swing, un evento simplemente "sucede" de la nada, y tienes que lidiar con esto aquí y ahora. Las transmisiones de eventos le permiten hacer todo el trabajo de fontanería y cableado de una manera más declarativa. Por ejemplo, cuando desee cambiar el oyente responsable en Swing, debe anular el registro de lo viejo y registrar el nuevo, y conocer todos los detalles sangrientos (por ejemplo, problemas de subprocesamiento). Con las secuencias de eventos, la fuente de los eventos se convierte en algo que simplemente puede pasar, por lo que no es muy diferente de una secuencia de bytes o char, por lo tanto, un concepto más "abstracto".
Los miembros de tipo abstracto proporcionan una forma flexible de abstraer sobre tipos concretos de componentes.
La clase anterior de Buffer ya es un ejemplo para esto.
Para una primera aproximación, ser capaz de "abstraer" algo significa que en lugar de usar ese elemento directamente, puede hacer un parámetro del mismo, o usarlo "anónimamente".
Scala le permite abstraer los tipos, permitiendo que las clases, los métodos y los valores tengan parámetros de tipo y los valores tengan tipos abstractos (o anónimos).
Scala le permite abstraer sobre acciones, permitiendo que los métodos tengan parámetros de función.
Scala le permite abstraer las características, permitiendo que los tipos se definan estructuralmente.
Scala le permite abstraer sobre los parámetros de tipo, permitiendo parámetros de tipo de orden superior.
Scala le permite abstraer los patrones de acceso a los datos, al permitirle crear extractores.
Scala le permite abstraer sobre "cosas que pueden usarse como otra cosa", al permitir conversiones implícitas como parámetros. Haskell lo hace de manera similar con las clases de tipo.
Scala no (todavía) te permite abstraer sobre clases. No puede pasar una clase a algo, y luego usar esa clase para crear nuevos objetos. Otros idiomas permiten la abstracción sobre las clases.
("Las mónadas resumen sobre los constructores de tipos" solo es cierto de una manera muy restrictiva. No te obsesiones hasta que tengas tu momento "¡Aha! ¡Entiendo mónadas!").
La capacidad de abstraerse sobre algún aspecto de la computación es básicamente lo que permite la reutilización del código, y permite la creación de bibliotecas de funcionalidad. Scala permite que se abstraigan muchos más tipos de cosas que los lenguajes más comunes, y las bibliotecas en Scala pueden ser correspondientemente más potentes.
Una abstracción es una especie de generalización.
http://en.wikipedia.org/wiki/Abstraction
No solo en Scala, sino en muchos idiomas, existe la necesidad de tener tales mecanismos para reducir la complejidad (o al menos crear una jerarquía que divida la información en partes más fáciles de comprender).
Una clase es una abstracción sobre un tipo de datos simple. Es algo así como un tipo básico, pero en realidad los generaliza. Entonces, una clase es más que un simple tipo de datos, pero tiene muchas cosas en común.
Cuando dice "abstraerse" se refiere al proceso por el cual generalizas. Entonces, si estás abstrayendo los métodos como parámetros, estás generalizando el proceso de hacerlo. por ejemplo, en lugar de pasar los métodos a las funciones, puede crear algún tipo de forma generalizada para manejarlo (como no pasar ningún método, sino crear un sistema especial para manejarlo).
En este caso, se refiere específicamente al proceso de abstraer un problema y crear una solución similar al problema. C tiene muy poca capacidad para abstraerse (puede hacerlo, pero se vuelve muy rápido y el lenguaje no lo admite directamente). Si lo escribió en C ++ podría usar los conceptos de oop para reducir la complejidad del problema (bueno, es la misma complejidad pero la conceptualización es generalmente más fácil (al menos una vez que aprende a pensar en términos de abstracciones)).
por ejemplo, si necesitaba un tipo de datos especial que fuera como un int pero, digamos restringido, podría abstraerlo creando un nuevo tipo que se pudiera usar como un int pero que tuviera esas propiedades que necesitaba. El proceso que usaría para hacer tal cosa se llamaría "abstracción".