rich - ¿Cuál es la razón detrás de los registros cerrados en Clojure?
clojurescript (1)
Tengo la opción de implementar directamente un protocolo en el cuerpo de un defrecord en lugar de usar extend-protocol / extend-type
(defprotocol Fly
(fly [this]))
(defrecord Bird [name]
Fly
(fly [this] (format "%s flies" name)))
=>(fly (Bird. "crow"))
"crow flies"
Si ahora trato de anular el protocolo Fly, obtengo un error
(extend-type Bird
Fly
(fly [this] (format "%s flies away (:name this)))
class user.Bird already directly implements interface user.Fly for protocol:#''user/Fly
Por otro lado si en su lugar utilizo extend-type inicialmente
(defrecord Dragon [color])
(extend-type Dragon
Fly
(fly [this] (format "%s dragon flies" (:color this))))
=>(fly (Dragon. "Red"))
"Red dragon flies"
Entonces puedo "anular" la función de volar
(extend-type Dragon
Fly
(fly [this] (format "%s dragon flies away" (:color this))))
=>(fly (Dragon. "Blue"))
"Blue dragon flies away"
Mi pregunta es, ¿por qué no permitir la extensión en ambos casos? ¿Es esta una limitación de JVM debido a la relación de Clase de registro <-> o hay un caso de uso para un protocolo no reemplazable?
En un nivel, es un problema de la JVM que no permite el intercambio de implementaciones de métodos dentro y fuera de las clases, ya que la implementación de un protocolo en línea equivale a que la clase creada por defrecord
implemente una interfaz correspondiente al protocolo. Tenga en cuenta que, si bien opta por hacerlo, sacrifica cierta flexibilidad, también compra velocidad, y de hecho la velocidad es la principal consideración de diseño aquí.
En otro nivel, generalmente es una muy mala idea que las implementaciones de protocolo en tipos sean proporcionadas por un código que no es propietario del tipo ni del protocolo en cuestión. Consulte, por ejemplo, este hilo en el grupo de Google Clojure para una discusión relacionada (incluida una declaración de Rich Hickey); También hay una entrada relevante en los Estándares de Codificación de la Biblioteca Clojure :
Protocolos:
- Uno solo debe extender un protocolo a un tipo si es dueño del tipo o del protocolo.
- Si se rompe la regla anterior, debe estar preparado para retirarse, si el implementador proporciona una definición
- Si un protocolo viene con Clojure, evite extenderlo a tipos que no posee, especialmente, por ejemplo, java.lang.String y otras interfaces Java principales. Tenga la seguridad de que si un protocolo se extiende a él, lo hará, de lo contrario, lo presionará.
- El motivo es, según lo declarado por Rich Hickey, [para prevenir] "las personas extienden los protocolos a tipos para los que no tienen sentido, por ejemplo, para los que los autores del protocolo consideraron pero rechazaron una implementación debido a un desajuste semántico". "No habrá ninguna extensión (por diseño), y las personas sin suficiente comprensión / habilidades podrían llenar el vacío con ideas rotas".
Esto también se ha discutido mucho en la comunidad de Haskell en relación con las clases de tipos ("instancias huérfanas" de google; también hay algunas publicaciones buenas sobre el tema aquí).
Ahora claramente, el propietario del tipo siempre proporciona una implementación en línea, por lo que no debe ser reemplazado por el código del cliente. Por lo tanto, puedo ver que quedan dos casos de uso válidos para el reemplazo del método de protocolo:
probando cosas en el REPL y aplicando retoques rápidos;
Modificación de implementaciones de métodos de protocolo en una imagen de Clojure en ejecución.
(1) podría ser un problema menor si uno usa extend
& Co. en el tiempo de desarrollo y solo cambia a implementaciones en línea en alguna etapa tardía de ajuste del rendimiento; (2) es algo que uno debe sacrificar si se requiere la velocidad máxima.