Buscar..


Introducción

TypeApplications son una alternativa a las anotaciones de tipo cuando el compilador TypeApplications inferir tipos para una expresión dada.

Esta serie de ejemplos explicará el propósito de la extensión TypeApplications y cómo usarla

No olvide habilitar la extensión colocando {-# LANGUAGE TypeApplications #-} en la parte superior de su archivo fuente.

Evitar anotaciones de tipo.

Utilizamos anotaciones de tipo para evitar la ambigüedad. Las aplicaciones de tipo se pueden utilizar para el mismo propósito. Por ejemplo

x :: Num a => a
x = 5

main :: IO ()
main = print x

Este código tiene un error de ambigüedad. Sabemos que a tiene una instancia de Num , y para imprimirla sabemos que necesita una instancia de Show . Esto podría funcionar si a era, por ejemplo, un Int , así que para corregir el error podemos agregar una anotación de tipo

main = print (x :: Int)

Otra solución usando aplicaciones de tipo se vería así

main = print @Int x

Para entender lo que esto significa, debemos observar el tipo de firma de print .

print :: Show a => a -> IO ()

La función toma un parámetro de tipo a , pero otra forma de verlo es que en realidad toma dos parámetros. El primero es un parámetro de tipo , el segundo es un valor cuyo tipo es el primer parámetro.

La principal diferencia entre los parámetros de valor y los parámetros de tipo es que los últimos se proporcionan de forma implícita a las funciones cuando los llamamos. ¿Quién los provee? El algoritmo de inferencia de tipos! Lo que TypeApplications nos permite hacer es dar a esos tipos de parámetros explícitamente. Esto es especialmente útil cuando la inferencia de tipo no puede determinar el tipo correcto.

Así que para romper el ejemplo anterior

print :: Show a => a -> IO ()
print @Int :: Int -> IO ()
print @Int x :: IO ()

Escriba aplicaciones en otros idiomas

Si está familiarizado con lenguajes como Java, C # o C ++ y el concepto de genéricos / plantillas, esta comparación puede ser útil para usted.

Digamos que tenemos una función genérica en C #

public static T DoNothing<T>(T in) { return in; }

Para llamar a esta función con un float podemos hacer DoNothing(5.0f) o si queremos ser explícitos podemos decir DoNothing<float>(5.0f) . Esa parte dentro de los soportes de ángulo es la aplicación de tipo.

En Haskell es lo mismo, excepto que los parámetros de tipo no solo son implícitos en los sitios de llamadas sino también en los sitios de definición.

doNothing :: a -> a
doNothing x = x

Esto también se puede hacer explícito usando las ScopedTypeVariables , Rank2Types o RankNTypes como esta.

doNothing :: forall a. a -> a
doNothing x = x

Luego, en el sitio de la llamada, podemos volver a escribir doNothing 5.0 o doNothing @Float 5.0

Orden de parametros

El problema de que los argumentos de tipo sean implícitos se vuelve obvio una vez que tenemos más de uno. ¿En qué orden entran?

const :: a -> b -> a

¿Escribir const @Int significa que a es igual a Int , o es b ? En caso de que indiquemos explícitamente los parámetros de tipo usando forall const :: forall a b. a -> b -> a forall como const :: forall a b. a -> b -> a entonces el orden es como está escrito: a , luego b .

Si no lo hacemos, entonces el orden de las variables es de izquierda a derecha. La primera variable que se debe mencionar es el primer parámetro de tipo, la segunda es el segundo parámetro de tipo y así sucesivamente.

¿Qué pasa si queremos especificar la segunda variable de tipo, pero no la primera? Podemos usar un comodín para la primera variable como esta

const @_ @Int

El tipo de esta expresión es

const @_ @Int :: a -> Int -> a

Interacción con tipos ambiguos.

Digamos que estás introduciendo una clase de tipos que tienen un tamaño en bytes.

class SizeOf a where
    sizeOf :: a -> Int

El problema es que el tamaño debe ser constante para cada valor de ese tipo. En realidad, no queremos que la función sizeOf dependa de a , sino solo de su tipo.

Sin aplicaciones de tipo, la mejor solución que tuvimos fue el tipo de Proxy definido de esta manera

data Proxy a = Proxy

El propósito de este tipo es llevar información de tipo, pero no información de valor. Entonces nuestra clase podría verse así.

class SizeOf a where
    sizeOf :: Proxy a -> Int

Ahora te estarás preguntando, ¿por qué no descartar el primer argumento por completo? El tipo de nuestra función sería entonces sizeOf :: Int o, para ser más precisos porque es un método de una clase, sizeOf :: SizeOf a => Int o para ser aún más explícito sizeOf :: forall a. SizeOf a => Int .

El problema es la inferencia de tipos. Si escribo sizeOf algún lugar, el algoritmo de inferencia solo sabe que espero un Int . No tiene idea de qué tipo quiero sustituir por a . Debido a esto, la definición es rechazada por el compilador a menos que tenga habilitada la extensión {-# LANGUAGE AllowAmbiguousTypes #-} . En ese caso, la definición compila, simplemente no puede usarse en ningún lugar sin un error de ambigüedad.

Afortunadamente, la introducción de aplicaciones de tipo salva el día! Ahora podemos escribir sizeOf @Int , diciendo explícitamente que a es Int . Las aplicaciones de tipo nos permiten proporcionar un parámetro de tipo, incluso si no aparece en los parámetros reales de la función .



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow