Pessoalmente, não consigo compreender como um acessador no índice que não retorna um tipo Maybe é aceitável como Haskell idiomático. [1,2,3]!!6apresentará um erro de tempo de execução. Isso poderia ser facilmente evitado se !!tivesse o tipo [a] -> Int -> Maybe a. A própria razão de termos Haskell é evitar esses erros de tempo de execução!
worldsayshi
9
É uma troca. O símbolo que escolheram é provavelmente o símbolo mais alarmante que poderiam ter. Acho que a ideia era permitir isso para casos extremos, mas fazer com que se destacasse como não 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)). Observe que isso falhará catastroficamente em uma lista infinita.
Não estou dizendo que haja algo errado com sua pergunta ou com a resposta dada, mas talvez você gostaria de saber sobre a ferramenta maravilhosa que é o Hoogle para economizar tempo no futuro: Com o Hoogle, você pode pesquisar funções de biblioteca padrão que correspondem a uma determinada assinatura. Portanto, não sabendo nada sobre !!, no seu caso, você pode pesquisar "algo que pega uma Inte uma lista de qualquer coisa e retorna um único tal qualquer", a saber
Int ->[a]-> a
Lo e eis que , com !!como o primeiro resultado (embora a assinatura de tipo na verdade tem dois argumentos na ordem inversa em relação ao que buscamos). Legal, hein?
Além disso, se o seu código depende da indexação (em vez de consumir do início da lista), as listas podem na verdade não ser a estrutura de dados adequada. Para acesso baseado em índice O (1), existem alternativas mais eficientes, como matrizes ou vetores .
Hoogle é absolutamente fantástico. Todo programador Haskell deve saber disso. Existe uma alternativa chamada Hayoo ( holumbus.fh-wedel.de/hayoo/hayoo.html ). Ele pesquisa enquanto você digita, mas não parece tão inteligente quanto o Hoogle.
musiKk
61
Uma alternativa ao uso (!!)é usar o
pacote de lentes e sua elementfunção e operadores associados. A
lente fornece uma interface uniforme para acessar uma ampla variedade de estruturas e estruturas aninhadas acima e além das listas. Abaixo, vou me concentrar em fornecer exemplos e ignorar tanto as assinaturas de tipo quanto a teoria por trás do
pacote de lentes . Se você quiser saber mais sobre a teoria, um bom lugar para começar é o arquivo leia-me no repositório github .
Acessando listas e outros tipos de dados
Obtendo acesso ao pacote de lentes
Na linha 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
Acessando listas
Para acessar uma lista com o operador infixo
>[1,2,3,4,5]^? element 2-- 0 based indexing
Just 3
Ao contrário de, (!!)isso não lançará uma exceção ao acessar um elemento fora dos limites e, em Nothingvez disso, retornará . Geralmente, é recomendável evitar funções parciais como (!!)ou, headpois elas têm mais casos extremos e são mais propensas a causar um erro de tempo de execução. Você pode ler um pouco mais sobre por que evitar funções parciais nesta página wiki .
>[1,2,3]!!9*** Exception: Prelude.(!!): index too large
>[1,2,3]^? element 9
Nothing
Você pode forçar a técnica da lente a ser uma função parcial e lançar uma exceção quando estiver fora dos limites usando o (^?!)operador em vez do (^?)operador.
>[1,2,3]^?! element 12>[1,2,3]^?! element 9*** Exception:(^?!): empty Fold
Trabalhar com tipos diferentes de listas
No entanto, isso não se limita apenas a listas. Por exemplo, a mesma técnica funciona em árvores do pacote de contêineres padrão
.
Agora podemos acessar os elementos da árvore em ordem de profundidade:
> 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 acessar os arrays indexados int padrão do
pacote vetorial , texto do pacote de texto padrão
, cadeias de bytes do pacote de cadeias de bytes padrão
e muitas outras estruturas de dados padrão. Este método padrão de acesso pode ser estendido às suas estruturas de dados pessoais, tornando-as uma instância da typeclass Taversable ; consulte uma lista mais longa de exemplos de Traversables na documentação do Lens. .
Estruturas aninhadas
Cavar em estruturas aninhadas é simples com o hackage da lente . Por exemplo, acessando um elemento em uma 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
Essa composição funciona mesmo quando as estruturas de dados aninhadas são de tipos diferentes. Por exemplo, se eu tivesse uma lista de árvores:
>:{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
Você pode aninhar profundamente com tipos arbitrários, desde que atendam ao Traversablerequisito. Portanto, acessar uma lista de árvores de sequências de texto é fácil.
Alterar o enésimo elemento
Uma operação comum em muitos idiomas é atribuir a uma posição indexada em uma matriz. Em python, você pode:
>>> a =[1,2,3,4,5]>>> a[3]=9>>> a
[1,2,3,9,5]
O
pacote de lentes oferece essa funcionalidade com o (.~)operador. Embora ao contrário do python, a lista original não sofra mutação, mas uma nova lista é retornada.
>let a =[1,2,3,4,5]> a & element 3.~9[1,2,3,9,5]> a
[1,2,3,4,5]
element 3 .~ 9é apenas uma função e o (&)operador, parte do
pacote de lentes , é apenas a aplicação da função reversa. Aqui está a aplicação de função mais comum.
>(element 3.~9)[1,2,3,4,5][1,2,3,9,5]
A atribuição novamente funciona perfeitamente bem com aninhamento arbitrário de Traversables.
>[[1,2,3],[4,5,6]]& element 0. element 1.~9[[1,9,3],[4,5,6]]
Posso sugerir um link para em Data.Traversablevez de reexportar em lens?
dfeuer
@dfeuer - adicionei um link para Data.Traversable na base. Eu também mantive o link antigo e indiquei que havia uma lista mais longa de traversíveis de exemplo na documentação do Lens. Obrigado pela sugestão.
Davorak
11
A resposta direta já foi dada: Use !!.
No entanto, os novatos geralmente tendem a usar esse operador em excesso, o que é caro em Haskell (porque você trabalha com listas de links simples, não com matrizes). Existem várias técnicas úteis para evitar isso, a mais fácil é usar o zip. Se você escrever zip ["foo","bar","baz"] [0..], obterá uma nova lista com os índices "anexados" a cada elemento em um par:, [("foo",0),("bar",1),("baz",2)]que geralmente é exatamente o que você precisa.
Você também precisa ter cuidado com seus tipos lá. Na maioria das vezes, você não deseja que os índices sejam inteiros lentos, em vez de Ints de máquina rápidos. Dependendo do que exatamente sua função faz e quão explícita sua digitação é, Haskell pode inferir o tipo de [0 ..] como [Integer] em vez de [Int].
chrisdb
4
Você pode usar !!, mas se quiser fazer isso recursivamente, a seguir está uma maneira de fazer:
dataAt :: Int ->[a]-> a
dataAt _[]= error "Empty List!"
dataAt y (x:xs)| y <=0= x
| otherwise = dataAt (y-1) xs
O tipo de dados de lista padrão de Haskell forall t. [t]na implementação se assemelha muito a uma lista vinculada C canônica e compartilha suas propriedades essenciais. Listas vinculadas são muito diferentes de arrays. Mais notavelmente, o acesso por índice é um O (n) linear-, em vez de uma O (1) operação de tempo constante.
Se você precisar de acesso aleatório frequente, considere o Data.Arraypadrão.
!!é uma função parcialmente definida insegura, provocando um travamento para índices fora do intervalo. Esteja ciente de que a biblioteca padrão contém algumas dessas funções parciais ( head, last, etc.). Por segurança, use um tipo de opção Maybeou o Safemódulo.
Exemplo de uma função de indexação total robusta e razoavelmente 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
Trabalhando com listas vinculadas, geralmente os ordinais são convenientes:
nth :: Int ->[a]-> Maybe a
nth _[]= Nothing
nth 1(x :_)= Just x
nth n (_: xs)= nth (n -1) xs
[1,2,3]!!6
apresentará um erro de tempo de execução. Isso poderia ser facilmente evitado se!!
tivesse o tipo[a] -> Int -> Maybe a
. A própria razão de termos Haskell é evitar esses erros de tempo de execução!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))
. Observe que isso falhará catastroficamente em uma lista infinita.!!
é uma função parcial e, portanto, insegura. Dê uma olhada no comentário abaixo e uselens
stackoverflow.com/a/23627631/2574719Não estou dizendo que haja algo errado com sua pergunta ou com a resposta dada, mas talvez você gostaria de saber sobre a ferramenta maravilhosa que é o Hoogle para economizar tempo no futuro: Com o Hoogle, você pode pesquisar funções de biblioteca padrão que correspondem a uma determinada assinatura. Portanto, não sabendo nada sobre
!!
, no seu caso, você pode pesquisar "algo que pega umaInt
e uma lista de qualquer coisa e retorna um único tal qualquer", a saberLo e eis que , com
!!
como o primeiro resultado (embora a assinatura de tipo na verdade tem dois argumentos na ordem inversa em relação ao que buscamos). Legal, hein?Além disso, se o seu código depende da indexação (em vez de consumir do início da lista), as listas podem na verdade não ser a estrutura de dados adequada. Para acesso baseado em índice O (1), existem alternativas mais eficientes, como matrizes ou vetores .
fonte
Uma alternativa ao uso
(!!)
é usar o pacote de lentes e suaelement
função e operadores associados. A lente fornece uma interface uniforme para acessar uma ampla variedade de estruturas e estruturas aninhadas acima e além das listas. Abaixo, vou me concentrar em fornecer exemplos e ignorar tanto as assinaturas de tipo quanto a teoria por trás do pacote de lentes . Se você quiser saber mais sobre a teoria, um bom lugar para começar é o arquivo leia-me no repositório github .Acessando listas e outros tipos de dados
Obtendo acesso ao pacote de lentes
Na linha de comando:
Acessando listas
Para acessar uma lista com o operador infixo
Ao contrário de,
(!!)
isso não lançará uma exceção ao acessar um elemento fora dos limites e, emNothing
vez disso, retornará . Geralmente, é recomendável evitar funções parciais como(!!)
ou,head
pois elas têm mais casos extremos e são mais propensas a causar um erro de tempo de execução. Você pode ler um pouco mais sobre por que evitar funções parciais nesta página wiki .Você pode forçar a técnica da lente a ser uma função parcial e lançar uma exceção quando estiver fora dos limites usando o
(^?!)
operador em vez do(^?)
operador.Trabalhar com tipos diferentes de listas
No entanto, isso não se limita apenas a listas. Por exemplo, a mesma técnica funciona em árvores do pacote de contêineres padrão .
Agora podemos acessar os elementos da árvore em ordem de profundidade:
Também podemos acessar sequências do pacote de contêineres :
Podemos acessar os arrays indexados int padrão do pacote vetorial , texto do pacote de texto padrão , cadeias de bytes do pacote de cadeias de bytes padrão e muitas outras estruturas de dados padrão. Este método padrão de acesso pode ser estendido às suas estruturas de dados pessoais, tornando-as uma instância da typeclass Taversable ; consulte uma lista mais longa de exemplos de Traversables na documentação do Lens. .
Estruturas aninhadas
Cavar em estruturas aninhadas é simples com o hackage da lente . Por exemplo, acessando um elemento em uma lista de listas:
Essa composição funciona mesmo quando as estruturas de dados aninhadas são de tipos diferentes. Por exemplo, se eu tivesse uma lista de árvores:
Você pode aninhar profundamente com tipos arbitrários, desde que atendam ao
Traversable
requisito. Portanto, acessar uma lista de árvores de sequências de texto é fácil.Alterar o enésimo elemento
Uma operação comum em muitos idiomas é atribuir a uma posição indexada em uma matriz. Em python, você pode:
O pacote de lentes oferece essa funcionalidade com o
(.~)
operador. Embora ao contrário do python, a lista original não sofra mutação, mas uma nova lista é retornada.element 3 .~ 9
é apenas uma função e o(&)
operador, parte do pacote de lentes , é apenas a aplicação da função reversa. Aqui está a aplicação de função mais comum.A atribuição novamente funciona perfeitamente bem com aninhamento arbitrário de
Traversable
s.fonte
Data.Traversable
vez de reexportar emlens
?A resposta direta já foi dada: Use
!!
.No entanto, os novatos geralmente tendem a usar esse operador em excesso, o que é caro em Haskell (porque você trabalha com listas de links simples, não com matrizes). Existem várias técnicas úteis para evitar isso, a mais fácil é usar o zip. Se você escrever
zip ["foo","bar","baz"] [0..]
, obterá uma nova lista com os índices "anexados" a cada elemento em um par:,[("foo",0),("bar",1),("baz",2)]
que geralmente é exatamente o que você precisa.fonte
Você pode usar
!!
, mas se quiser fazer isso recursivamente, a seguir está uma maneira de fazer:fonte
O tipo de dados de lista padrão de Haskell
forall t. [t]
na implementação se assemelha muito a uma lista vinculada C canônica e compartilha suas propriedades essenciais. Listas vinculadas são muito diferentes de arrays. Mais notavelmente, o acesso por índice é um O (n) linear-, em vez de uma O (1) operação de tempo constante.Se você precisar de acesso aleatório frequente, considere o
Data.Array
padrão.!!
é uma função parcialmente definida insegura, provocando um travamento para índices fora do intervalo. Esteja ciente de que a biblioteca padrão contém algumas dessas funções parciais (head
,last
, etc.). Por segurança, use um tipo de opçãoMaybe
ou oSafe
módulo.Exemplo de uma função de indexação total robusta e razoavelmente eficiente (para índices ≥ 0):
Trabalhando com listas vinculadas, geralmente os ordinais são convenientes:
fonte