Como posso usar uma lista de tamanho mínimo fixo de maneira total e elegante?

10

Atualmente, estou lidando com uma função que é assim:

foo = (\(a:b:c:d:e:f:_) -> foobar a b c d e f) . (++ repeat def)

Em outras palavras, dada uma lista, ela usa os seis primeiros elementos para algo e, se a lista tiver menos de seis elementos, será usada defcomo substituta dos que estão faltando. Isso é total, mas as partes não são (exatamente como map fromJust . filter isJust), então eu não gosto. Tentei reescrever isso para que ele não precisasse de parcialidade, e consegui:

foo [] = foobar def def def def def def
foo [a] = foobar a def def def def def
foo [a,b] = foobar a b def def def def
foo [a,b,c] = foobar a b c def def def
foo [a,b,c,d] = foobar a b c d def def
foo [a,b,c,d,e] = foobar a b c d e def
foo (a:b:c:d:e:f:_) = foobar a b c d e f

Eu tecnicamente fiz o que eu quero, mas agora isso é uma bagunça gigantesca. Como posso fazer isso de uma maneira mais elegante e menos repetitiva?

Joseph Sible-Restabelecer Monica
fonte
2
Talvez, escreva um uncons :: Default a => [a] -> (a,[a])padrão def. Ou um padrão takeWithDef. E / ou um padrão de visualização / sinônimo de padrão. Isso requer a escrita de algum código auxiliar auxiliar, no entanto.
chi
@chi Acho que é com isso que vou. Se você der uma resposta, eu aceito.
Joseph Sible-Reinstate Monica
2
Pelo que vale, acho que o argumento da totalidade case xs ++ repeat def of a:b:c:d:e:f:_ -> ...é local o suficiente para que eu não pense duas vezes apenas em usá-lo e pular toda a maquinaria extra que as respostas existentes estão introduzindo. Geralmente, são os argumentos de globalidade mais globais (que envolvem invariantes mantidos em várias chamadas de função, por exemplo) que me deixam nervosa.
Daniel Wagner
Na verdade, takeWithDefnão é utilizável se retornar uma lista regular, pois precisamos padronizar a correspondência com a seguinte: - / A solução adequada é o que Daniel escreveu abaixo em sua segunda resposta. unconssó recebe o primeiro elemento, então não é tão útil.
chi

Respostas:

8

Usando o pacote seguro , você pode escrever, por exemplo:

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)
Daniel Wagner
fonte
6

Isso é pelo menos mais curto:

foo (a:b:c:d:e:f:_) = foobar a b c d e f
foo xs = foo (xs ++ repeat def)

Você pode ver facilmente que os padrões são exaustivos, mas agora você precisa pensar um pouco para ver que sempre termina. Então, eu não sei se você pode considerar isso uma melhoria.

Caso contrário, podemos fazê-lo com a mônada estadual, embora seja um pouco pesada:

foo = evalState (foobar <$> pop <*> pop <*> pop <*> pop <*> pop <*> pop)
  where
    pop = do xs <- get
             case xs of [] -> pure def
                        y:ys -> put ys >> pure y

Eu também poderia imaginar usando um tipo de fluxo infinito como

data S a = S a (S a)

porque então você pode construir foofora repeat :: a -> S a, prepend :: [a] -> S a -> S ae take6 :: S a -> (a,a,a,a,a,a), tudo o que poderia ser total. Provavelmente não vale a pena se você ainda não tem esse tipo de ferramenta.

David Fletcher
fonte
3
Ah, eu gosto muito da ideia de stream. Com um construtor de infix data S a = a :- S a; infixr 5 :-, parece bastante limpo; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner
4

Apenas por diversão (e não recomendado, isso é para brincadeiras), aqui está outra maneira:

import Data.Default

data Cons f a = a :- f a
infixr 5 :-

data Nil a = Nil -- or use Proxy

class TakeDef f where takeDef :: Default a => [a] -> f a
instance TakeDef Nil where takeDef _ = Nil
instance TakeDef f => TakeDef (Cons f) where
    takeDef (x:xs) = x :- takeDef xs
    takeDef xs = def :- takeDef xs

foo xs = case takeDef xs of
    a:-b:-c:-d:-e:-f:-Nil -> foobar a b c d e f

O tipo que você usa na correspondência de padrão equivale a passar um natural no nível de tipo para takeDefdizer quantos elementos examinar.

Daniel Wagner
fonte
11
Esta é a minha abordagem preferida até agora. Eu provavelmente usaria um padrão de exibição para complementá-lo. (Por que a "não recomendado" Quais são os contras?)
chi
3
Ele incorpora exatamente o que começa a dar errado quando você investe pesadamente em programação em nível de tipo: o que era um programa de uma linha, instantaneamente compreensível, se divide em dez linhas que exigem que o leitor envolva seriamente seu mecanismo de inferência de tipo mental.
Daniel Wagner
11
Eu entendo o seu ponto. Eu conto foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fcomo uma linha. Não conto o resto, pois é um código que deve estar em alguma biblioteca, para reutilização. Se tiver que ser escrito apenas para este caso, é claramente um exagero, como você diz.
chi