No conozco ningún tutorial, pero creo que es más fácil comprender las flechas si miras algunos ejemplos concretos. El mayor problema que tuve al aprender a usar las flechas fue que ninguno de los tutoriales o ejemplos realmente muestran cómo usar las flechas, solo cómo componerlas. Entonces, con eso en mente, aquí está mi mini-tutorial. Examinaré dos flechas diferentes: funciones y una flecha definida por el usuario tipo MyArr
.
-- type representing a computation
data MyArr b c = MyArr (b -> (c,MyArr b c))
1) Una flecha es un cálculo desde la entrada de un tipo especificado a la salida de un tipo especificado. La clase de letra de flecha toma tres argumentos de tipo: el tipo de flecha, el tipo de entrada y el tipo de salida. En cuanto a la cabeza de instancia para casos de flecha encontramos:
instance Arrow (->) b c where
instance Arrow MyArr b c where
La Flecha (ya sea (->)
o MyArr
) es una abstracción de un cálculo. Para obtener una función b -> c
, b
es la entrada y c
es la salida.
Para un MyArr b c
, b
es la entrada y c
es la salida.
2) Para ejecutar realmente un cálculo de flecha, utiliza una función específica para su tipo de flecha. Para las funciones, simplemente aplica la función a un argumento. Para otras flechas, debe haber una función separada (como runIdentity
, runState
, etc. para mónadas).
-- run a function arrow
runF :: (b -> c) -> b -> c
runF = id
-- run a MyArr arrow, discarding the remaining computation
runMyArr :: MyArr b c -> b -> c
runMyArr (MyArr step) = fst . step
3) Las flechas se utilizan con frecuencia para procesar una lista de entradas. Para las funciones, estas pueden realizarse en paralelo, pero para algunas flechas, la salida en cualquier paso dado depende de las entradas previas (p.mantener un total acumulado de entradas).
-- run a function arrow over multiple inputs
runFList :: (b -> c) -> [b] -> [c]
runFList f = map f
-- run a MyArr over multiple inputs.
-- Each step of the computation gives the next step to use
runMyArrList :: MyArr b c -> [b] -> [c]
runMyArrList _ [] = []
runMyArrList (MyArr step) (b:bs) = let (this, step') = step b
in this : runMyArrList step' bs
Este es un motivo por el que las flechas son útiles. Proporcionan un modelo de cálculo que puede hacer uso implícito del estado sin exponer nunca ese estado al programador. El programador puede usar cómputos con flechas y combinarlos para crear sistemas sofisticados.
Aquí hay una myArr que lleva la cuenta del número de entradas que ha recibido:
-- count the number of inputs received:
count :: MyArr b Int
count = count' 0
where
count' n = MyArr (\_ -> (n+1, count' (n+1)))
Ahora la función runMyArrList count
tendrá una lista de longitud n como entrada y devolver una lista de enteros de 1 a n.
Tenga en cuenta que todavía no hemos utilizado ninguna función de "flecha", es decir, métodos de clase de flecha o funciones escritas en términos de ellos.
4) La mayor parte del código anterior es específico de cada instancia de Arrow [1]. Todo en Control.Arrow
(y Control.Category
) se trata de componer flechas para hacer nuevas flechas. Si se pretende que la categoría es parte de Flecha en lugar de una clase separada:
-- combine two arrows in sequence
>>> :: Arrow a => a b c -> a c d -> a b d
-- the function arrow instance
-- >>> :: (b -> c) -> (c -> d) -> (b -> d)
-- this is just flip (.)
-- MyArr instance
-- >>> :: MyArr b c -> MyArr c d -> MyArr b d
La función >>>
lleva dos flechas y utiliza la salida de la primera como entrada a la segunda.
Aquí hay otro operador, comúnmente llamado "despliegue en abanico":
-- &&& applies two arrows to a single input in parallel
&&& :: Arrow a => a b c -> a b c' -> a b (c,c')
-- function instance type
-- &&& :: (b -> c) -> (b -> c') -> (b -> (c,c'))
-- MyArr instance type
-- &&& :: MyArr b c -> MyArr b c' -> MyArr b (c,c')
-- first and second omitted for brevity, see the accepted answer from KennyTM's link
-- for further details.
Desde Control.Arrow
proporciona un medio para combinar los cálculos, aquí está un ejemplo:
-- function that, given an input n, returns "n+1" and "n*2"
calc1 :: Int -> (Int,Int)
calc1 = (+1) &&& (*2)
he encontrado con frecuencia funciones como calc1
útil en pliegues complicados o funciones que funcionan con punteros, por ejemplo.
La clase de tipo Monad
nos proporciona un medio para combinar cálculos monádicos en un solo nuevo cálculo monádico utilizando la función >>=
. Del mismo modo, la clase Arrow
nos proporciona medios para combinar los cálculos arrowized en un único nuevo cómputo arrowized usando unas pocas funciones primitivas (first
, arr
y ***
, con >>>
y id
de Control.Category). También similar a Mónadas, la pregunta de "¿Qué hace una flecha?" no puede ser generalmente respondida. Depende de la flecha
Lamentablemente no conozco muchos ejemplos de instancias de flechas en la naturaleza. Las funciones y FRP parecen ser las aplicaciones más comunes. HXT es el único otro uso significativo que viene a la mente.
[1] Excepto count
. Es posible escribir una función de conteo que haga lo mismo para cualquier instancia de ArrowLoop
.
Relacionado: http://stackoverflow.com/questions/3154701/help-understanding-arrows-in-haskell – kennytm
@KennyTM: Su respuesta es más o menos la explicación de la mayoría de las cosas que me preguntaba. Ahora entiendo algunas razones. – fuz
@FUZxxl Hace poco también quise asimilar seriamente flechas y encontré tu misma frustración. ¿Tienes una lista de algún tipo que crees que fue más útil en tu búsqueda? – kizzx2