2009-11-22 14 views
11

Ich bin völlig neu in Haskell und wenn ich kleine Programme schreibe, endet ich normalerweise mit zu vielen where-Klauseln, um viele Dinge in der Funktion zu überprüfen, also ist es gut, where-Klauseln zu schreiben Gibt es noch andere gute Alternativen dafür?Haskell zu viele where-Klauseln, irgendwelche alternativen Vorschläge

zum beispiel in dem code unten habe ich versucht zu finden, wenn es ameise doppelte elemente in jeder zeile der zweidimensionalen liste gibt, es funktioniert und jede sache ist zufrieden in der gleichen funktion aber ich bin nicht zufrieden mit wie code aussieht und ich finden Sie es mehr imperative Stil der Annäherung an das Problem, so dass ich für jeden Vorschlag oder Gedanken dazu von den erfahrenen Menschen da draußen suchen.

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 

erneut dieser Code ist nur ein Problem zu veranschaulichen, ich suche nach Ansatz Problem in funktionalem Stil zu formulieren. Ihre Vorschläge oder Artikel, Links wären sehr hilfreich.

Dank

Antwort

24

Try abstrakt, wiederverwendbare Funktionen schreiben Sie der Lage sein werden, sie zu komponieren viel einfacher

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 für den Vorschlag, in kleinere Funktionen zu teilen, anstatt zu versuchen, die ganze Komplexität auf einmal zu jonglieren. – 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

Obwohl es Ausnahmen gibt, definieren Sie können im allgemeinen " positive "Funktionen", dh in diesem Fall definieren Sie eine Funktion, die True zurückgibt, wenn das Argument einige doppelte Daten enthält. Sie könnten das so schreiben:

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 

Dieses verwendet Pattern-Matching, any, elem und (||). Um die Negation zu erhalten, verwenden not:

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

Du hast nicht die zweite müssen where-Klausel. Sie können mehrere Funktionen unter derselben where Klausel einfügen. Alle Funktionsnamen in derselben where-Klausel sind in den Körpern dieser Funktionen enthalten. Denken Sie darüber nach, wie Funktionen auf oberster Ebene funktionieren. So könnte man schreiben:

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 

In der Tat ist dies sehr viel klarer, weil in Ihrer Version, wenn Sie x in checkDup binden, dass x noch in der Sekundenwhere Klausel in ihrem Umfang ist, aber Sie sind verbindlich die Argumente von checkRow auf den gleichen Namen. Ich denke, dass GHC sich dadurch beschweren wird, und es ist sicherlich verwirrend.

4

einige der Details Ihres speziellen Beispiel (die Namen expecially gut gewählt sind nicht) Ende beiseite, ich bin ein großer Fan von where-Klauseln:

  • Eine Funktion in einer Klausel sein kann where definiert besser als eine Top-Level-Funktion, weil ein Leser weiß, dass der Umfang der Funktion begrenzt ist --- es kann nur an wenigen Stellen verwendet werden.

  • Eine Funktion in einer where Klausel definiert sind, können Parameter einer einschließenden Funktion erfassen, die einfacher ist es oft

In Ihrem speziellen Beispiel zu lesen, müssen Sie nicht Nest der where clauses- - Eine einzelne where-Klausel reicht aus, da die in derselben Klausel definierten Funktionen alle miteinander rekursiv sind. Es gibt andere Dinge über den Code, die verbessert werden könnten, aber mit der einzelnen Klausel mag ich die Großstruktur fein.

N.B. Es ist nicht notwendig, die where Klauseln so tief einzugravieren, wie Sie es tun.

1

Haskell erlaubt Ihnen, auf Dinge Bezug zu nehmen, die in einer where-Klausel innerhalb der where-Klausel definiert sind (dasselbe wie eine let binding). Tatsächlich ist eine Where-Klausel lediglich syntaktischer Zucker für eine Let-Bindung, die unter anderem mehrere Definitionen und gegenseitige Referenzen erlaubt.

ein Beispiel ist in Ordnung.

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 

wird

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 

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

wird fühle ich die gleiche wie Norman über den globalen Bereich sauber zu halten. Je mehr Funktionen Sie innerhalb Ihres Moduls verfügbar machen, desto ungeschickter wird der Namespace. Auf der anderen Seite macht es eine Funktion innerhalb des globalen Umfangs Ihres Moduls wiederverwendbar.

Ich denke, Sie können eine klare Unterscheidung machen. Einige Funktionen sind für ein Modul wichtig, sie tragen direkt zur API bei. Es gibt auch Funktionen, die, wenn sie in der Moduldokumentation erscheinen, den Leser fragen lassen, was diese bestimmte Funktion mit dem Zweck des Moduls zu tun hat. Das ist eindeutig eine Hilfsfunktion.

Ich würde sagen, dass eine solche Hilfsfunktion aus erster Hand eine untergeordnete Funktion der aufrufenden Funktion sein sollte. Wenn diese Hilfsfunktion innerhalb des Moduls wiederverwendet werden soll, entkoppeln Sie diese Hilfsfunktion von der aufrufenden Funktion, indem Sie sie zu einer direkt zugänglichen Funktion des Moduls machen. Sie werden diese Funktion jedoch wahrscheinlich nicht in die Moduldefinition exportieren.
Nennen wir dies ein Refaktorieren im FP-Stil.

Es ist schade, dass es kein "code complete" -ähnliches Buch für die funktionale Programmierung gibt. Ich denke der Grund ist, dass es zu wenig Industriepraxis gibt. Aber lassen Sie uns die Weisheit auf stackoverflow sammeln: D

Verwandte Themen