Personalmente, no puedo comprender cómo un descriptor de acceso en el índice que no devuelve un tipo Maybe es aceptable como Haskell idiomático. [1,2,3]!!6le dará un error de tiempo de ejecución. Podría evitarse muy fácilmente si !!tuviera el tipo [a] -> Int -> Maybe a. ¡La verdadera razón por la que tenemos Haskell es para evitar tales errores de tiempo de ejecución!
worldsayshi
9
Es una compensación. El símbolo que eligieron es probablemente el símbolo más alarmante que podrían tener. Entonces creo que la idea era permitirlo para casos extremos, pero hacer que se destaque como no idiomático.
cdosborn
3
itemOf :: Int -> [a] -> Maybe a; x `itemOf` xs = let xslen = length xs in if ((abs x) > xslen) then Nothing else Just (xs !! (x `mod` xslen)). Tenga en cuenta que esto fallará catastróficamente en una lista infinita.
No estoy diciendo que haya nada malo con su pregunta o la respuesta dada, pero tal vez le gustaría saber acerca de la maravillosa herramienta que es Hoogle para ahorrar tiempo en el futuro: con Hoogle, puede buscar funciones de biblioteca estándar que coinciden con una firma determinada. Por lo tanto, sin saber nada sobre !!, en su caso, podría buscar "algo que tome una Inty una lista de lo que sea y devuelva un solo lo que sea", es decir
Int ->[a]-> a
He aquí , con !!como primer resultado (aunque la firma de tipo en realidad tiene los dos argumentos al revés en comparación con lo que buscamos). Limpio, ¿eh?
Además, si su código se basa en la indexación (en lugar de consumir desde el principio de la lista), es posible que las listas no tengan la estructura de datos adecuada. Para el acceso basado en índices O (1) hay alternativas más eficientes, como matrices o vectores .
Hoogle es absolutamente genial. Todo programador de Haskell debería saberlo. Existe una alternativa llamada Hayoo ( holumbus.fh-wedel.de/hayoo/hayoo.html ). Busca mientras escribe, pero no parece ser tan inteligente como Hoogle.
musiKk
61
Una alternativa al uso (!!)es usar el
paquete de lentes y su elementfunción y operadores asociados. La
lente proporciona una interfaz uniforme para acceder a una amplia variedad de estructuras y estructuras anidadas por encima y más allá de las listas. A continuación, me centraré en proporcionar ejemplos y pasaré por alto tanto las firmas de tipos como la teoría detrás del
paquete de lentes . Si desea saber más sobre la teoría, un buen lugar para comenzar es el archivo Léame en el repositorio de github .
Accediendo a listas y otros tipos de datos
Obtener acceso al paquete de lentes
En la línea de comando:
$ cabal install lens
$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/:? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.>import Control.Lens
Accediendo a listas
Para acceder a una lista con el operador infijo
>[1,2,3,4,5]^? element 2-- 0 based indexing
Just 3
A diferencia de (!!)esto, esto no generará una excepción al acceder a un elemento fuera de los límites y en su Nothinglugar regresará . A menudo se recomienda evitar funciones parciales como (!!)o, headya que tienen más casos de esquina y es más probable que causen un error de tiempo de ejecución. Puede leer un poco más sobre por qué evitar funciones parciales en esta página wiki .
>[1,2,3]!!9*** Exception: Prelude.(!!): index too large
>[1,2,3]^? element 9
Nothing
Puede forzar la técnica de la lente para que sea una función parcial y lanzar una excepción cuando esté fuera de los límites utilizando el (^?!)operador en lugar del (^?)operador.
>[1,2,3]^?! element 12>[1,2,3]^?! element 9*** Exception:(^?!): empty Fold
Trabajar con tipos distintos a las listas
Sin embargo, esto no se limita a las listas. Por ejemplo, la misma técnica funciona en árboles del paquete de contenedores estándar
.
Ahora podemos acceder a los elementos del árbol en orden de profundidad:
> tree ^? element 0
Just 1> tree ^? element 1
Just 2> tree ^? element 2
Just 4> tree ^? element 3
Just 5> tree ^? element 4
Just 3> tree ^? element 5
Just 6> tree ^? element 6
Just 7
>import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4]^? element 3
Just 4
Podemos acceder a las matrices indexadas int estándar desde el
paquete de vectores , el texto del paquete de texto estándar
, las cadenas de bytes del paquete de cadenas de bytes estándar
y muchas otras estructuras de datos estándar. Este método estándar de acceso puede extenderse a sus estructuras de datos personales convirtiéndolas en una instancia de la clase de tipos Taversable , consulte una lista más larga de ejemplos de Traversables en la documentación de Lens. .
Estructuras anidadas
Profundizar en estructuras anidadas es simple con el truco de la lente . Por ejemplo, acceder a un elemento en una lista de listas:
>[[1,2,3],[4,5,6]]^? element 0. element 1
Just 2>[[1,2,3],[4,5,6]]^? element 1. element 2
Just 6
Esta composición funciona incluso cuando las estructuras de datos anidadas son de diferentes tipos. Entonces, por ejemplo, si tuviera una lista de árboles:
>:{let
tree = Node 1[
Node 2[], Node 3[]]:}> putStrLn . drawTree . fmap show $ tree
1|+-2|`-3>:{let
listOfTrees =[ tree
, fmap (*2) tree -- All tree elements times 2, fmap (*3) tree -- All tree elements times 3]:}> listOfTrees ^? element 1. element 0
Just 2> listOfTrees ^? element 1. element 1
Just 4
Puede anidar arbitrariamente profundamente con tipos arbitrarios siempre que cumplan con el Traversablerequisito. Así que acceder a una lista de árboles de secuencias de texto es fácil.
Cambiar el enésimo elemento
Una operación común en muchos idiomas es asignar a una posición indexada en una matriz. En Python podrías:
>>> a =[1,2,3,4,5]>>> a[3]=9>>> a
[1,2,3,9,5]
El
paquete de lentes brinda esta funcionalidad con el (.~)operador. Aunque a diferencia de Python, la lista original no está mutada, sino que se devuelve una nueva lista.
>let a =[1,2,3,4,5]> a & element 3.~9[1,2,3,9,5]> a
[1,2,3,4,5]
element 3 .~ 9es solo una función y el (&)operador, parte del
paquete de lentes , es solo una aplicación de función inversa. Aquí está con la aplicación de función más común.
>(element 3.~9)[1,2,3,4,5][1,2,3,9,5]
La asignación nuevamente funciona perfectamente bien con anidamientos arbitrarios de Traversables.
>[[1,2,3],[4,5,6]]& element 0. element 1.~9[[1,9,3],[4,5,6]]
¿Puedo sugerir un enlace en Data.Traversablelugar de la reexportación en lens?
dfeuer
@dfeuer: agregué un enlace a Data.Traversable en la base. También mantuve el enlace anterior y señalé que había una lista más larga de ejemplos de traverables en la documentación de Lens. Gracias por la sugerencia.
Davorak
11
Ya se dio la respuesta directa: Use !!.
Sin embargo, los novatos a menudo tienden a abusar de este operador, que es caro en Haskell (porque trabaja en listas vinculadas individuales, no en matrices). Existen varias técnicas útiles para evitar esto, la más sencilla es usar zip. Si escribe zip ["foo","bar","baz"] [0..], obtiene una nueva lista con los índices "adjuntos" a cada elemento de un par:, [("foo",0),("bar",1),("baz",2)]que a menudo es exactamente lo que necesita.
También debe tener cuidado con sus tipos allí. La mayoría de las veces no querrá terminar con índices enteros lentos en lugar de Ints de máquina rápidos. Dependiendo de lo que haga exactamente su función y cuán explícito sea su escritura, Haskell podría inferir que el tipo de [0 ..] es [Integer] en lugar de [Int].
chrisdb
4
Puede usar !!, pero si desea hacerlo de forma recursiva, a continuación se muestra una forma de hacerlo:
dataAt :: Int ->[a]-> a
dataAt _[]= error "Empty List!"
dataAt y (x:xs)| y <=0= x
| otherwise = dataAt (y-1) xs
Tipo de datos de lista estándar de Haskell forall t. [t] en implementación se parece mucho a una lista enlazada de C canónica y comparte sus propiedades esenciales. Las listas enlazadas son muy diferentes de las matrices. En particular, el acceso por índice es una operación de tiempo constante O (n) lineal, en lugar de O (1).
Si necesita un acceso aleatorio frecuente, considere el Data.Arrayestándar.
!!es una función insegura parcialmente definida, que provoca un bloqueo de los índices fuera de rango. Tenga en cuenta que la biblioteca estándar contiene algunas de estas funciones parciales ( head, last, etc.). Por seguridad, utilice un tipo de opción Maybeo elSafe módulo.
Ejemplo de una función de indexación total robusta y razonablemente eficiente (para índices ≥ 0):
data Maybe a = Nothing | Just a
lookup :: Int ->[a]-> Maybe a
lookup _[]= Nothing
lookup 0(x :_)= Just x
lookup i (_: xs)= lookup (i -1) xs
Al trabajar con listas vinculadas, a menudo los ordinales son convenientes:
nth :: Int ->[a]-> Maybe a
nth _[]= Nothing
nth 1(x :_)= Just x
nth n (_: xs)= nth (n -1) xs
[1,2,3]!!6
le dará un error de tiempo de ejecución. Podría evitarse muy fácilmente si!!
tuviera el tipo[a] -> Int -> Maybe a
. ¡La verdadera razón por la que tenemos Haskell es para evitar tales errores de tiempo de ejecución!itemOf :: Int -> [a] -> Maybe a; x `itemOf` xs = let xslen = length xs in if ((abs x) > xslen) then Nothing else Just (xs !! (x `mod` xslen))
. Tenga en cuenta que esto fallará catastróficamente en una lista infinita.!!
es una función parcial y por lo tanto insegura. Eche un vistazo al comentario a continuación y uselens
stackoverflow.com/a/23627631/2574719No estoy diciendo que haya nada malo con su pregunta o la respuesta dada, pero tal vez le gustaría saber acerca de la maravillosa herramienta que es Hoogle para ahorrar tiempo en el futuro: con Hoogle, puede buscar funciones de biblioteca estándar que coinciden con una firma determinada. Por lo tanto, sin saber nada sobre
!!
, en su caso, podría buscar "algo que tome unaInt
y una lista de lo que sea y devuelva un solo lo que sea", es decirHe aquí , con
!!
como primer resultado (aunque la firma de tipo en realidad tiene los dos argumentos al revés en comparación con lo que buscamos). Limpio, ¿eh?Además, si su código se basa en la indexación (en lugar de consumir desde el principio de la lista), es posible que las listas no tengan la estructura de datos adecuada. Para el acceso basado en índices O (1) hay alternativas más eficientes, como matrices o vectores .
fuente
Una alternativa al uso
(!!)
es usar el paquete de lentes y suelement
función y operadores asociados. La lente proporciona una interfaz uniforme para acceder a una amplia variedad de estructuras y estructuras anidadas por encima y más allá de las listas. A continuación, me centraré en proporcionar ejemplos y pasaré por alto tanto las firmas de tipos como la teoría detrás del paquete de lentes . Si desea saber más sobre la teoría, un buen lugar para comenzar es el archivo Léame en el repositorio de github .Accediendo a listas y otros tipos de datos
Obtener acceso al paquete de lentes
En la línea de comando:
Accediendo a listas
Para acceder a una lista con el operador infijo
A diferencia de
(!!)
esto, esto no generará una excepción al acceder a un elemento fuera de los límites y en suNothing
lugar regresará . A menudo se recomienda evitar funciones parciales como(!!)
o,head
ya que tienen más casos de esquina y es más probable que causen un error de tiempo de ejecución. Puede leer un poco más sobre por qué evitar funciones parciales en esta página wiki .Puede forzar la técnica de la lente para que sea una función parcial y lanzar una excepción cuando esté fuera de los límites utilizando el
(^?!)
operador en lugar del(^?)
operador.Trabajar con tipos distintos a las listas
Sin embargo, esto no se limita a las listas. Por ejemplo, la misma técnica funciona en árboles del paquete de contenedores estándar .
Ahora podemos acceder a los elementos del árbol en orden de profundidad:
También podemos acceder a secuencias desde el paquete de contenedores :
Podemos acceder a las matrices indexadas int estándar desde el paquete de vectores , el texto del paquete de texto estándar , las cadenas de bytes del paquete de cadenas de bytes estándar y muchas otras estructuras de datos estándar. Este método estándar de acceso puede extenderse a sus estructuras de datos personales convirtiéndolas en una instancia de la clase de tipos Taversable , consulte una lista más larga de ejemplos de Traversables en la documentación de Lens. .
Estructuras anidadas
Profundizar en estructuras anidadas es simple con el truco de la lente . Por ejemplo, acceder a un elemento en una lista de listas:
Esta composición funciona incluso cuando las estructuras de datos anidadas son de diferentes tipos. Entonces, por ejemplo, si tuviera una lista de árboles:
Puede anidar arbitrariamente profundamente con tipos arbitrarios siempre que cumplan con el
Traversable
requisito. Así que acceder a una lista de árboles de secuencias de texto es fácil.Cambiar el enésimo elemento
Una operación común en muchos idiomas es asignar a una posición indexada en una matriz. En Python podrías:
El paquete de lentes brinda esta funcionalidad con el
(.~)
operador. Aunque a diferencia de Python, la lista original no está mutada, sino que se devuelve una nueva lista.element 3 .~ 9
es solo una función y el(&)
operador, parte del paquete de lentes , es solo una aplicación de función inversa. Aquí está con la aplicación de función más común.La asignación nuevamente funciona perfectamente bien con anidamientos arbitrarios de
Traversable
s.fuente
Data.Traversable
lugar de la reexportación enlens
?Ya se dio la respuesta directa: Use
!!
.Sin embargo, los novatos a menudo tienden a abusar de este operador, que es caro en Haskell (porque trabaja en listas vinculadas individuales, no en matrices). Existen varias técnicas útiles para evitar esto, la más sencilla es usar zip. Si escribe
zip ["foo","bar","baz"] [0..]
, obtiene una nueva lista con los índices "adjuntos" a cada elemento de un par:,[("foo",0),("bar",1),("baz",2)]
que a menudo es exactamente lo que necesita.fuente
Puede usar
!!
, pero si desea hacerlo de forma recursiva, a continuación se muestra una forma de hacerlo:fuente
Tipo de datos de lista estándar de Haskell
forall t. [t]
en implementación se parece mucho a una lista enlazada de C canónica y comparte sus propiedades esenciales. Las listas enlazadas son muy diferentes de las matrices. En particular, el acceso por índice es una operación de tiempo constante O (n) lineal, en lugar de O (1).Si necesita un acceso aleatorio frecuente, considere el
Data.Array
estándar.!!
es una función insegura parcialmente definida, que provoca un bloqueo de los índices fuera de rango. Tenga en cuenta que la biblioteca estándar contiene algunas de estas funciones parciales (head
,last
, etc.). Por seguridad, utilice un tipo de opciónMaybe
o elSafe
módulo.Ejemplo de una función de indexación total robusta y razonablemente eficiente (para índices ≥ 0):
Al trabajar con listas vinculadas, a menudo los ordinales son convenientes:
fuente