tutorial online funciones empresa descargar constructora company haskell

online - haskell pdf



¿Hay alguna buena razón para usar unsafePerformIO? (6)

De la forma en que lo veo, las distintas unsafe* no unsafe* solo deberían usarse en los casos en que quiera hacer algo que respete la transparencia referencial pero cuya implementación de otro modo requeriría aumentar el compilador o el sistema de tiempo de ejecución para agregar una nueva capacidad primitiva. Es más fácil, más modular, legible, fácil de mantener y ágil usar las cosas inseguras que tener que modificar la implementación del lenguaje para cosas como esas.

El trabajo de FFI a menudo intrínsecamente requiere que hagas este tipo de cosas.

La pregunta lo dice todo. Más específicamente, estoy escribiendo enlaces a una biblioteca C, y me pregunto con qué funciones c puedo usar unsafePerformIO . Supongo que el uso de unsafePerformIO con cualquier cosa que incluya punteros es un gran no-no.

Sería genial ver otros casos en los que es aceptable usar unsafePerformIO también.


El truco estándar para instanciar variables mutables globales en haskell:

{-# NOINLINE bla #-} bla :: IORef Int bla = unsafePerformIO (newIORef 10)

También lo uso para cerrar sobre la variable global si quiero evitar el acceso a ella fuera de las funciones que proporciono:

{-# NOINLINE printJob #-} printJob :: String -> Bool -> IO () printJob = unsafePerformIO $ do p <- newEmptyMVar return $ /a b -> do -- here''s the function code doing something -- with variable p, no one else can access.


En el caso específico de la FFI, unsafePerformIO está destinado a usarse para llamar a funciones que son funciones matemáticas, es decir, la salida depende únicamente de los parámetros de entrada, y cada vez que se llama a la función con las mismas entradas, devolverá la misma salida . Además, la función no debe tener efectos secundarios, como modificar datos en el disco o mutar la memoria.

La mayoría de las funciones de <math.h> podrían llamarse con unsafePerformIO , por ejemplo.

Tienes razón en que unsafePerformIO y los punteros no suelen mezclarse. Por ejemplo, supongamos que tienes

p_sin(double *p) { return sin(*p); }

Aunque solo esté leyendo un valor desde un puntero, no es seguro usar unsafePerformIO . Si p_sin , varias llamadas pueden usar el argumento del puntero, pero obtener resultados diferentes. Es necesario mantener la función en IO para garantizar que se secuencia correctamente en relación con las actualizaciones de puntero.

Este ejemplo debe aclarar una razón por la que esto no es seguro:

# file export.c #include <math.h> double p_sin(double *p) { return sin(*p); } # file main.hs {-# LANGUAGE ForeignFunctionInterface #-} import Foreign.Ptr import Foreign.Marshal.Alloc import Foreign.Storable foreign import ccall "p_sin" p_sin :: Ptr Double -> Double foreign import ccall "p_sin" safeSin :: Ptr Double -> IO Double main :: IO () main = do p <- malloc let sin1 = p_sin p sin2 = safeSin p poke p 0 putStrLn $ "unsafe: " ++ show sin1 sin2 >>= /x -> putStrLn $ "safe: " ++ show x poke p 1 putStrLn $ "unsafe: " ++ show sin1 sin2 >>= /x -> putStrLn $ "safe: " ++ show x

Cuando se compila, este programa produce

$ ./main unsafe: 0.0 safe: 0.0 unsafe: 0.0 safe: 0.8414709848078965

Aunque el valor al que hace referencia el puntero ha cambiado entre las dos referencias a "sin1", la expresión no se vuelve a evaluar, lo que lleva a que se utilicen datos obsoletos. Dado que safeSin (y, por sin2 tanto, sin2 ) está en IO, el programa se ve obligado a volver a evaluar la expresión, por lo que en su lugar se utilizan los datos del puntero actualizados.


No hay necesidad de involucrar a C aquí. La función unsafePerformIO se puede utilizar en cualquier situación en la que,

  1. Usted sabe que su uso es seguro, y

  2. No puede probar su seguridad utilizando el sistema de tipo Haskell.

Por ejemplo, puede hacer una función de unsafePerformIO usando unsafePerformIO :

memoize :: Ord a => (a -> b) -> a -> b memoize f = unsafePerformIO $ do memo <- newMVar $ Map.empty return $ /x -> unsafePerformIO $ modifyMVar memo $ /memov -> return $ case Map.lookup x memov of Just y -> (memov, y) Nothing -> let y = f x in (Map.insert x y memov, y)

(Esto está fuera de mi cabeza, así que no tengo idea si hay errores flagrantes en el código).

La función memoize utiliza y modifica un diccionario de memoización, pero como la función en su conjunto es segura, puede asignarle un tipo puro (sin utilizar la mónada IO ). Sin embargo, tienes que usar unsafePerformIO para hacer eso.

Nota al pie: Cuando se trata de la FFI, usted es responsable de proporcionar los tipos de funciones C al sistema Haskell. Puede lograr el efecto de unsafePerformIO simplemente omitiendo IO del tipo. El sistema FFI es intrínsecamente inseguro, por lo que el uso de unsafePerformIO no hace mucha diferencia.

Nota al pie 2: a menudo hay errores muy sutiles en el código que utiliza unsafePerformIO , el ejemplo es solo un esbozo de un posible uso. En particular, unsafePerformIO puede interactuar pobremente con el optimizador.


Obviamente, si nunca se usara, no estaría en las bibliotecas estándar. ;-)

Hay una serie de razones por las que podría usarlo. Ejemplos incluyen:

  • Inicializando estado mutable global. (Si deberías tener algo así en primer lugar es otra discusión ...)

  • Lazy I / O se implementa usando este truco. (De nuevo, si la E / S perezosa es una buena idea en primer lugar es discutible).

  • La función de trace utiliza. (Una vez más, resulta que el trace es bastante menos útil de lo que podrías imaginar).

  • Quizás lo más importante es que puede usarlo para implementar estructuras de datos que son referencialmente transparentes, pero implementadas internamente utilizando código impuro. A menudo, la mónada ST te permitirá hacerlo, pero a veces necesitas un poco de unsafePerformIO .

La perezosa E / S puede verse como un caso especial del último punto. Lo mismo ocurre con la memoisación.

Considere, por ejemplo, una matriz "inmutable" que se puede cultivar. Internamente, podrías implementar eso como un "manejador" puro que apunta a una matriz mutable . El identificador mantiene el tamaño visible para el usuario de la matriz, pero la matriz mutable subyacente real es más grande que eso. Cuando el usuario "anexa" a la matriz, se devuelve un nuevo identificador, con un nuevo tamaño más grande, pero la anexión se realiza mediante la mutación de la matriz mutable subyacente.

No puedes hacer esto con la mónada ST . (O más bien, puede hacerlo, pero todavía requiere unsafePerformIO ).

Tenga en cuenta que es muy difícil conseguir este tipo de cosas. Y el comprobador de tipos no se enganchará si te equivocas. (¿Qué hace unsafePerformIO Hace que el comprobador de tipos no compruebe que lo está haciendo correctamente!) Por ejemplo, si agrega un identificador "antiguo", lo correcto sería copiar la matriz mutable subyacente. Olvida esto, y tu código se comportará de manera muy extraña .

Ahora, para responder a su verdadera pregunta: no hay ninguna razón en particular por la que "cualquier cosa sin punteros" deba ser un no-no para unsafePerformIO . Cuando se pregunta si se debe usar esta función o no, la única pregunta importante es esta: ¿puede el usuario final observar algún efecto secundario al hacer esto?

Si lo único que hace es crear un búfer en algún lugar que el usuario no pueda "ver" desde código puro, está bien. Si se escribe en un archivo en el disco ... no tan bien.

HTH.


Por supuesto. Puede ver un ejemplo real here pero en general, unsafePerformIO se puede usar en cualquier función pura que tenga unsafePerformIO secundarios. La mónada IO aún puede ser necesaria para realizar un seguimiento de los efectos (por ejemplo, liberar memoria después de calcular el valor) incluso cuando la función es pura (por ejemplo, calcular un factorial).

Me pregunto con qué funciones c puedo usar unsafePerformIO. Supongo que el uso de unsafePerformIO con cualquier cosa que incluya punteros es un gran no-no.

¡Depende! unsafePerformIO realizará acciones completamente y forzará toda la pereza, pero eso no significa que interrumpirá su programa. En general, los Haskeller prefieren que unsafePerformIO aparezca solo en funciones puras, por lo que puede usarlo en resultados de, por ejemplo, cálculos científicos, pero tal vez no en lecturas de archivos.