2011-01-22 10 views
84

A menudo en la literatura de Scala, encuentro la frase "resumen sobre", pero no entiendo la intención. For example, Martin Odersky escribe¿Qué significa "resumen sobre"?

Puede pasar métodos (o "funciones") como parámetros, o puede abstracto sobre ellos. Puede especificar tipos como parámetros, o puede abstract over ellos.

Como otro ejemplo, en el documento "Deprecating the Observer Pattern",

Una consecuencia de nuestros flujos de eventos siendo valores de primera clase es que podemos abstracto sobre ellos.

He leído los genéricos de primer orden "abstract over types", mientras que las mónadas "resumen sobre constructores de tipo". Y también vemos frases como esta en el Cake Pattern paper. Para citar a uno de los muchos ejemplos de este tipo:

miembros de tipo abstracto proporcionan de manera flexible a abstracta más tipos concretos de los componentes.

Incluso las preguntas relevantes sobre desbordamiento de pila utilizan esta terminología. "can't existentially abstract over parameterized type..."

Entonces ... ¿qué significa "abstract over" en realidad?

Respuesta

103

En álgebra, como en la formación de conceptos cotidianos, 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 tenemos resumen sobre las diferencias, pero esto realmente significa que 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. Así podemos abstracta más los números específicos, mediante la integración de todas las listas de números bajo un único símbolo ns:

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

Y no me importa mucho que se trata de una lista tampoco.Lista es un constructor de tipo específico (toma un tipo y devuelve un tipo), pero podemos abstracto sobre el constructor de tipo especificando qué característica esencial que queremos (que puede ser doblada):

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 nosotros puede tener instancias Foldable implícitas para List y cualquier otra cosa que podamos plegar.

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

Lo que es más, podemos abstracto sobre tanto la operación como el tipo de los 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 doblará cualquier F[A] dado que podemos probar que F es plegable y que A es un monoide o se puede mapear 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) 

Tenemos abstraído sobre monoides y Plegables.

+0

@coubeatczech El código se ejecuta en REPL fine. ¿Qué versión de Scala estás usando y qué error obtuviste? –

+1

@Apocalisp Sería interesante si hiciera uno de los dos ejemplos finales un 'conjunto 'o algún otro tipo plegable. Un ejemplo con un 'String' y concatenación también sería genial. –

+1

Hermosa respuesta, Runar. ¡Gracias! Seguí la sugerencia de Daniel y creé implícitos setFoldable y concatMonoid, sin alterar mapReduce. Estoy en camino de asimilar esto. –

6

Una abstracción es una especie de generalización.

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

No sólo en Scala, pero muchos idiomas, existe la necesidad de tener este tipo de mecanismos para reducir la complejidad (o al menos crear una jerarquía que divide la información en pedazos más fácil de entender).

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 que se generaliza. 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 abstraer al crear un nuevo tipo que podría usarse como un int pero tenía esas propiedades que necesitaba. El proceso que usaría para hacer tal cosa se llamaría "abstracción".

10

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 sobre 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 datos, permitiéndole 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) le permite abstraer sobre las 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.

("mónadas abstractas sobre constructores de tipo" sólo es cierto en una forma muy restrictiva. No se colgó en ella hasta que tenga su "Aha! Entiendo mónadas !!" momento.)

El 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.

+1

Puede pasar un 'Manifiesto', o incluso una' Clase', y usar el reflejo para crear instancias de objetos nuevos de esa clase. –

5

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() 
+1

casi la idea de "resumen sobre" en código poderoso pero corto, intentará este idioma +1 – user44298

2

Las otras respuestas ya dan una buena idea de qué tipo de abstracciones existen. Vamos a repasar la cotizaciones de uno en uno, y proporcionar un ejemplo:

Puede pasar métodos (o "funciones") como parámetros, o se puede abstraer sobre ellos. Puede especificar tipos como parámetros , o puede abstraerlos por encima de .

función de paso como 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 especializada con cada elemento de la lista. val list = List[String]() especifica un tipo de parámetro (Cadena). Puede escribir un tipo de colección que use miembros de tipo abstracto en su lugar: val buffer = Buffer{ type Elem=String }. Una diferencia es que debe 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 nuestro evento corrientes siendo valores de primera clase es que puede abstraer sobre ellos.

En Columpio un evento sólo "pasa" de la nada, y usted tiene que tratar con él aquí y ahora. Las transmisiones de eventos le permiten hacer todo el trabajo de fontanería y cableado de una manera más declarativa. P.ej. 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 enhebrado). 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".

miembros flexibles proporcionan tipo abstracto forma abstracta sobre a tipos concretos de componentes.

La clase de memoria intermedia anterior ya es un ejemplo de esto.