No hay forma de observar el valor del puntero dentro del ForeignPtr
desde fuera del módulo Data.ByteString
; su implementación es internamente impura, pero externamente pura, porque asegura que las invariantes requeridas para ser puras se mantienen mientras no se pueda ver dentro del constructor ByteString
, lo que no se puede hacer, porque no se exporta.
Esta es una técnica común en Haskell: implementar algo con técnicas inseguras bajo el capó, pero exponiendo una interfaz pura; obtienes tanto el rendimiento como la potencia que aportan las técnicas inseguras, sin comprometer la seguridad de Haskell. (Por supuesto, los módulos de aplicación pueden tener errores, pero ¿cree que sería ByteString
menos probable escaparse su abstracción si se ha escrito en C? :))
En cuanto a los puntos sutiles van, si Desde la perspectiva de un usuario, no se preocupe: puede usar cualquier función que las bibliotecas ByteString y Vector exporten sin preocuparse, siempre que no comiencen con unsafe
. Son bibliotecas muy maduras y bien probadas, por lo que no debería encontrarse con ningún problema de pureza, y si hace, eso es un error en la biblioteca, y debe informarlo.
En cuanto a escribir su propio código que proporciona seguridad externa con una implementación interna insegura, la regla es muy simple: mantener la transparencia referencial.
Tomando ByteString como ejemplo, las funciones para construir ByteStrings usan unsafePerformIO
para asignar bloques de datos, que luego mutan y ponen en el constructor. Si exportamos el constructor, entonces el código de usuario podría obtener el ForeignPtr
. ¿Esto es problemático? Para determinar si lo es, necesitamos encontrar una función pure (es decir, no en IO
) que nos permita distinguir dos ForeignPtrs asignados de esta manera. Un rápido vistazo al the documentation muestra que existe tal función: instance Eq (ForeignPtr a)
nos permite distinguirlos. Por lo tanto, no debemos permitir que el código de usuario acceda al ForeignPtr
. La forma más fácil de hacerlo es no exportar el constructor.
En resumen: Cuando utiliza un mecanismo inseguro para implementar algo, verifique que la impureza que introduce no se puede filtrar fuera del módulo, p. inspeccionando los valores que produces con él.
En lo que respecta a los problemas del compilador, realmente no deberías preocuparte por ellos; mientras que las funciones son inseguras, no deberían permitirle hacer algo más peligroso, más allá de violar la pureza, que lo que puede hacer en la mónada IO
para comenzar. Generalmente, si desea hacer algo que pueda producir realmente resultados inesperados, tendrá que hacer todo lo posible para hacerlo: por ejemplo, puede usar unsafeDupablePerformIO
si puede tratar con la posibilidad de que dos hilos evalúen la mismo golpe de la forma unsafeDupablePerformIO m
simultáneamente. unsafePerformIO
es un poco más lento que unsafeDupablePerformIO
porque evita que esto suceda. (Los zumbidos en su programa pueden evaluarse mediante dos subprocesos simultáneamente durante la ejecución normal con GHC, esto normalmente no es un problema, ya que evaluar el mismo valor puro dos veces no debería tener efectos secundarios adversos (por definición), pero al escribir código no seguro, es algo que tiene que tener en cuenta.)
El GHC documentation for unsafePerformIO
(y unsafeDupablePerformIO
, como he vinculado anteriormente) detalla algunos errores que puede encontrar; de manera similar, la documentación para unsafeCoerce#
(que debe usarse a través de su nombre portátil, Unsafe.Coerce.unsafeCoerce).
Bueno, planeo usar operaciones inseguras. Por lo tanto, esta pregunta :) Me gustaría aprender de los problemas que debería tener en cuenta, como lo fueron los escritores de la biblioteca. Esas ideas serán muy útiles al escribir nuestro propio código que necesita ser rápido, pero aún externo, puro, para extensiones paralelas y simultáneas. – Sal
Ah, OK; eso no estaba claro para mí de la pregunta. Trataré de incorporar parte de esa información en mi respuesta, aunque es complicado ya que la regla básica es simplemente "garantizar la transparencia referencial desde fuera del módulo". – ehird
Lo he ampliado un poco más, espero que esto ayude :) – ehird