2009-11-22 14 views
11

Soy totalmente nuevo en Haskell y al escribir pequeños programas normalmente termino con demasiadas cláusulas where para verificar muchas cosas en la función, por lo que es una buena práctica escribir donde cláusulas o ¿hay alguna otra buena alternativa para esto?Haskell demasiadas cláusulas where, cualquier sugerencia alternativa

por ejemplo en el siguiente código intenté encontrar si hay elementos duplicados de hormiga en cada fila de la lista de dos dimensiones, funciona y todo es contenido en la misma función pero no estoy satisfecho con cómo se ve el código yi encontrar un estilo más imperativo de abordar el problema, así que estoy buscando sugerencias o pensamientos sobre esto de parte de la gente con experiencia que existe.

noDups :: [[a]] -> Bool 
noDups du = and (checkSu du) 
     where 
     checkDup []  = [] 
     checkDup (x:xs) = checkRow x ++ checkDup xs 
      where 
      checkRow []  = [] 
      checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs 

una vez más este código es para ilustrar un problema, estoy buscando un enfoque para formular un problema en el estilo funcional. sus sugerencias o artículos, enlaces serían de mucha ayuda.

Gracias

Respuesta

24

tratar de escribir funciones abstractas, reutilizables podrás componer mucho más fáciles

isUnique :: Eq a => [a] -> Bool 
isUnique [] = True 
isUnique (x:xs) = all (/= x) xs && isUnique xs 

noDups :: Eq a => [[a]] -> Bool 
noDups = all isUnique 
+0

+1 por sugerir dividir en funciones más pequeñas en lugar de tratar de hacer malabares con toda la complejidad a la vez. – Joren

3
noDups :: [[a]] -> Bool 
noDups = and . checkDup 
    where 
    --checkDup 
    checkDup []  = [] 
    checkDup (x:xs) = checkRow x ++ checkDup xs 
    --alternatively 
    checkDup xs = concat $ map checkRow xs 
    --alternatively 
    checkDup = concat . map checkRow 
    --alternatively 
    checkDup = concatMap checkRow 
    --checkRow 
    checkRow []  = [] 
    checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs 
2

Aunque hay excepciones, en general, es posible que desee para definir " "funciones positivas", es decir, en este caso define una función que devuelve True si el argumento contiene contiene algunos datos duplicados. Se puede escribir que de esta manera:

has_nested_duplicate :: (Eq a) => [[a]] -> Bool 
has_nested_duplicate = any has_duplicate 
    where 
    has_duplicate []  = False 
    has_duplicate (x:xs) = x `elem` xs || has_duplicate xs 

Este utiliza la coincidencia de patrones, any, elem y (||). Para conseguir la negación, utilice not:

noDups :: (Eq a) => [[a]] -> Bool 
noDups = not . has_nested_duplicate 
6

No es necesario la segunda cláusula where. puede poner múltiples funciones bajo la misma cláusula where. Todos los nombres de funciones en la misma cláusula where están dentro del alcance de los cuerpos de esas funciones. Piensa en cómo funcionan las funciones de alto nivel. Por lo que podría escribir:

noDups :: [[a]] -> Bool 
noDups du = and (checkSu du) 
    where checkDup []  = [] 
     checkDup (x:xs) = checkRow x ++ checkDup xs 

     checkRow []  = [] 
     checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs 

De hecho esto es mucho más clara, ya que en su versión cuando se enlaza x en checkDup, que x todavía está en el alcance de la cláusula segunda where, sin embargo, que son vinculantes para la argumentos de checkRow con el mismo nombre. Creo que esto probablemente hará que GHC se queje, y ciertamente es confuso.

4

Dejando de lado algunos de los detalles de su caso particular (los nombres no están bien elegidos EXPECIALLY), soy un gran fan de cláusulas cuando:

  • una función definida en una cláusula puede ser where mejor que una función de nivel superior porque un lector sabe que el alcance de la función es limitado; puede usarse en unos pocos lugares.

  • una función definida en una cláusula where puede capturar los parámetros de una función de cerramiento, que a menudo hace que sea más fácil de leer

En el ejemplo particular, no es necesario acotar la where, las cláusulas --una sola cláusula where hará, porque las funciones definidas en la misma cláusula where son mutuamente recursivas entre sí. Hay otras cosas sobre el código que podrían mejorarse, pero con la cláusula where me gusta la estructura a gran escala.

N.B. No es necesario aplicar sangrías a las cláusulas where tan profundamente como lo hace.

1

Haskell le permite referirse a las cosas definidas en una cláusula where desde dentro de la cláusula where (lo mismo que una vinculación de let). De hecho, una cláusula where es meramente azúcar sintáctica para un enlace let que permite múltiples definiciones y referencias mutuas, entre otras cosas.

un ejemplo está en orden.

noDups :: [[a]] -> Bool 
noDups du = and (checkDup du) 
    where 
     checkDup []  = [] 
     checkDup (x:xs) = checkRow x ++ checkDup xs 
      where 
       checkRow []  = [] 
       checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs 

convierte

noDups :: [[a]] -> Bool 
noDups du = and (checkDup du) 
    where 
     checkDup []  = [] 
     checkDup (x:xs) = checkRow x ++ checkDup xs 
     --checkDup can refer to checkRow 
     checkRow []  = [] 
     checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs 

convierte

noDups :: [[a]] -> Bool 
noDups du = 
    let checkDup []  = [] 
     checkDup (x:xs) = checkRow x ++ checkDup xs 
     --checkDup can refer to checkRow 
     checkRow []  = [] 
     checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs 

    in and (checkDup du) 
1

me siento igual que Norman acerca de mantener limpio el ámbito global. Cuantas más funciones exponga dentro de su módulo, más torpe será el espacio de nombres. Por otro lado, tener una función dentro del alcance global de su módulo lo hace reutilizable.

Creo que se puede hacer una clara distinción. Algunas funciones son principales para un módulo, contribuyen directamente a la API. También hay funciones que cuando se muestran en la documentación del módulo dejan al lector preguntándose qué función particular tiene que ver con el propósito del módulo. Eso es claramente una función auxiliar.

Diría que una función de ayuda de este tipo debe ser de primera mano un subordinado de la función de llamada. Si esta función auxiliar debe reutilizarse dentro del módulo, desacople esta función auxiliar de la función de llamada haciendo que sea una función directamente accesible del módulo. Sin embargo, es probable que no exporte esta función en la definición del módulo.
Vamos a llamar a esto una refactorización de estilo FP.

Es una lástima que no exista un libro tipo "código completo" para programación funcional. Creo que la razón es que hay muy poca práctica en la industria. Pero recopilemos la sabiduría en stackoverflow: D

Cuestiones relacionadas