¿Cómo puedo usar una lista de longitud mínima fija de una manera total y elegante?

10

Actualmente estoy lidiando con una función que va así:

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

En otras palabras, dada una lista, usa los primeros seis elementos para algo, y si la lista tiene menos de seis elementos, la usa defcomo un sustituto para los que faltan. Esto es total, pero las partes no son (solo como map fromJust . filter isJust), por lo que no me gusta. Traté de reescribir esto para que no necesite usar ninguna parcialidad, y obtuve esto:

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

Técnicamente hice lo que quería, pero ahora esto es un desastre gigantesco. ¿Cómo puedo hacer esto de una manera más elegante y menos repetitiva?

Joseph Sible-Reinstate a Monica
fuente
2
Tal vez, escriba un valor uncons :: Default a => [a] -> (a,[a])predeterminado def. O un incumplimiento takeWithDef. Y / o un patrón de vista / sinónimo de patrón. Sin embargo, esto requiere escribir un código auxiliar auxiliar.
chi
@chi Creo que con eso iré. Si le respondes, lo aceptaré.
Joseph Sible-Reinstate Monica
2
Por lo que vale, creo que el argumento de totalidad case xs ++ repeat def of a:b:c:d:e:f:_ -> ...es lo suficientemente local como para no pensarlo dos veces antes de usarlo y omitir toda la maquinaria adicional que las respuestas existentes están introduciendo. Generalmente son los argumentos de totalidad más globales (que involucran invariantes mantenidos a través de múltiples llamadas a funciones, por ejemplo) lo que me pone nervioso.
Daniel Wagner
En realidad, takeWithDefno es utilizable si devuelve una lista regular, ya que necesitamos un patrón que coincida con eso: - / La solución adecuada es lo que Daniel escribió a continuación en su segunda respuesta. unconssolo obtiene el primer elemento, por lo que no es tan útil.
chi

Respuestas:

8

Usando el paquete seguro , puede escribir, por ejemplo:

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

Esto es al menos más corto:

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

Puede ver fácilmente que los patrones son exhaustivos, pero ahora tiene que pensar un poco para ver que siempre termina. Entonces no sé si puedes considerarlo una mejora.

De lo contrario, podemos hacerlo con la mónada estatal, aunque es un poco pesado:

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

También podría imaginarme usando un tipo de flujo infinito como

data S a = S a (S a)

porque entonces se podría construir foofuera de repeat :: a -> S a, prepend :: [a] -> S a -> S ay take6 :: S a -> (a,a,a,a,a,a), todo lo cual podría ser total. Probablemente no valga la pena si aún no tiene un tipo tan útil.

David Fletcher
fuente
3
Oh, me gusta mucho la idea de la transmisión. Con un constructor infijo como data S a = a :- S a; infixr 5 :-se ve bastante limpio; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner
4

Solo por diversión (y no recomendado, esto es por diversión), aquí hay otra forma:

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

El tipo que usa en la coincidencia de patrones equivale a pasar un nivel de tipo natural a takeDefdecir cuántos elementos mirar.

Daniel Wagner
fuente
1
Este es mi enfoque preferido hasta ahora. Probablemente usaría un patrón de vista para complementarlo. (¿Por qué "no recomendado"? ¿Cuáles son las desventajas?)
chi
3
Representa exactamente lo que comienza a salir mal cuando invierte mucho en programación de nivel de tipo: lo que era un programa de una línea, entendible al instante, se expande en diez líneas que requieren que el lector se comprometa seriamente con su motor de inferencia de tipo mental.
Daniel Wagner
1
Entiendo tu argumento. Cuento foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fcomo una línea. No cuento el resto ya que es un código que debería estar en alguna biblioteca, para su reutilización. Si tiene que escribirse solo para este caso, es claramente excesivo como usted dice.
chi