Los dos conceptos son muy, muy similares. En los lenguajes normales de OOP, adjuntamos una vtable (o para interfaces: itable) a cada objeto:
| this
v
+---+---+---+
| V | a | b | the object with fields a, b
+---+---+---+
|
v
+---+---+---+
| o | p | q | the vtable with method slots o(), p(), q()
+---+---+---+
Esto nos permite invocar métodos similares a this->vtable.p(this)
.
En Haskell, la tabla de métodos es más como un argumento oculto implícito:
method :: Class a => a -> a -> Int
se vería como la función C ++
template<typename A>
int method(Class<A>*, A*, A*)
donde Class<A>
es una instancia de typeclass Class
para type A
. Se invocaría un método como
typeclass_instance->p(value_ptr);
La instancia está separada de los valores. Los valores aún conservan su tipo real. Si bien las clases de tipos permiten cierto polimorfismo, esto no es subtipo de polimorfismo. Eso hace que sea imposible hacer una lista de valores que satisfagan a Class
. Por ejemplo, suponiendo que tenemos instance Class Int ...
y instance Class String ...
no podemos crear un tipo de lista heterogénea como [Class]
esa tiene valores como [42, "foo"]
. (Esto es posible cuando utiliza la extensión "tipos existenciales", que efectivamente cambia al enfoque Ir).
En Go, un valor no implementa un conjunto fijo de interfaces. En consecuencia, no puede tener un puntero vtable. En su lugar, los punteros a los tipos de interfaz se implementan como punteros gordos que incluyen un puntero a los datos, otro puntero a lo factible:
`this` fat pointer
+---+---+
| | |
+---+---+
____/ \_________
v v
+---+---+---+ +---+---+
| o | p | q | | a | b | the data with
+---+---+---+ +---+---+ fields a, b
itable with method
slots o(), p(), q()
this.itable->p(this.data_ptr)
El itable se combina con los datos en un puntero grueso cuando se convierte desde un valor ordinario a un tipo de interfaz. Una vez que tiene un tipo de interfaz, el tipo real de los datos se vuelve irrelevante. De hecho, no puede acceder a los campos directamente sin pasar por métodos o desactivar la interfaz (que puede fallar).
El enfoque de Go para el envío de la interfaz tiene un costo: cada puntero polimórfico es dos veces más grande que un puntero normal. Además, la transmisión de una interfaz a otra implica copiar los punteros del método a una nueva vtable. Pero una vez que hemos construido el itable, esto nos permite enviar llamadas de método a muchas interfaces a bajo costo, algo que sufren los lenguajes tradicionales de OOP. Aquí, m es el número de métodos en la interfaz de destino, yb es el número de clases base:
- C ++ hace el corte de objetos o necesita perseguir punteros de herencia virtual cuando se convierte, pero luego tiene acceso simple a vtable. O (1) u O (b) costo de conversión, pero envío de método O (1).
- La máquina virtual de Java Hotspot no tiene que hacer nada al realizar la conversión, pero en la búsqueda del método de interfaz realiza una búsqueda lineal a través de todos los elementos ejecutables implementados por esa clase. O (1) conversión ascendente, pero O (b) envío del método.
- Python no tiene que hacer nada cuando realiza la conversión, pero utiliza una búsqueda lineal a través de una lista de clases base linealizadas en C3. O (1) upcasting, pero el envío del método O (b²)? No estoy seguro de cuál es la complejidad algorítmica de C3.
- .NET CLR utiliza un enfoque similar a Hotspot pero agrega otro nivel de indirección en un intento de optimizar el uso de la memoria. O (1) conversión ascendente, pero O (b) envío del método.
La complejidad típica para el envío de métodos es mucho mejor ya que la búsqueda de métodos a menudo se puede almacenar en caché, pero las peores complejidades son bastante horribles.
En comparación, Go tiene conversión ascendente O (1) u O (m), y el envío del método O (1). Haskell no tiene conversión (restringir un tipo con una clase de tipo es un efecto de tiempo de compilación) y el envío del método O (1).
[42, "foo"]
. Es un ejemplo vívido.Hay varias diferencias
Maybe
es aMonad
. Las interfaces Go están tipificadas estructuralmente: sicircle
declaraarea() float64
y también lo hacesquare
, ambas están debajo de la interfazshape
automáticamente.Maybe a
ejemplo) clases de tipo para tipos de tipo superior. Go no tiene equivalente para estos.+ :: Num a => a -> a -> a
, lo que garantiza que no intente agregar coma flotante y cuaterniones, es inexpresable en Ir.fuente
Son completamente diferentes Las interfaces Go definen un protocolo para los valores, las clases de tipo Haskell definen un protocolo para los tipos. (Por eso se les llama "clases de tipos", después de todo. Clasifican los tipos, a diferencia de las clases OO (o las interfaces de Go), que clasifican los valores).
Las interfaces Go son simplemente aburridas tipologías estructurales, nada más.
fuente