2015-04-22 2 views
8

Zum Beispiel:Wo und warum sollte ich extra leere Muster verwenden?

intersectBy : (a -> a -> Bool) -> List a -> List a -> List a 
intersectBy _ [] _  = [] 
intersectBy _ _ [] = [] 
intersectBy eq xs ys = [x | x <- xs, any (eq x) ys] 

Es gibt zusätzliche Muster für [] und scheinen, wie sie in Haskell verwendet werden Data.List aber welche Art von Optimierung ist das? Und wo ist der Unterschied zu Idris?

Ich frage, weil ich hörte, dass "es die Argumentation darüber schwieriger machen wird" und Person, die mich sagte, die keine Zeit hatte, es vollständig zu erklären.

Ich bezweifle, wenn ich es verstehe, "den Beweis" der Funktion zu reduzieren.

Kann mir jemand die Politik der zusätzlichen Muster hier von Positionen von Haskell und Idris erklären, damit ich in der Lage sein werde, den Unterschied zu verstehen und zu sehen.

Antwort

13

semantisch zu sprechen, das Muster

intersectBy _ [] _  = [] 

sieht überflüssig, sogar von einer Performance-Sicht. Stattdessen in Haskell

intersectBy _ _ [] = [] 

ist nicht überflüssig, da sonst

intersectBy (==) [0..] [] 

würde auseinanderlaufen, da das Verständnis x <- [0..] alle Elemente, um zu versuchen versuchen würde.

Ich bin mir nicht sicher, dass ich diesen speziellen Fall mag, obwohl. Warum sollten wir keinen Sonderfall hinzufügen, der intersectBy (==) [0..] [2] abdeckt, so dass [2] zurückgegeben wird? Wenn es um Leistung geht, möchte ich in vielen Fällen einen O (n log n) -Ansatz durch Vorsortierung verwenden, auch wenn dies nicht für unendliche Listen funktioniert und Ord a erfordert.

+0

Schön, es macht jetzt Sinn! Aber wie würden Sie eigentlich einen speziellen Fall hinzufügen, der 'intersect (==) [0 ..] [2]' abdeckt? – Sibi

+0

Warum sieht '_ [] _' überflüssig aus? – Cynede

+1

@Heather weil '[x | x <- xs, ...] 'wird sofort zu' [] 'ausgewertet, wenn' xs' leer ist. – chi

5

@chi erklärt die _ _ [] Fall, aber _ [] _ dient einem Zweck auch: es gibt vor, wie intersectByhandles bottom. Mit der Definition wie folgt geschrieben:

λ. intersectBy undefined []  undefined 
[] 
λ. intersectBy (==) undefined [] 
*** Exception: Prelude.undefined 

Entfernen Sie das erste Muster und es wird:

λ. intersectBy undefined undefined [] 
[] 
λ. intersectBy (==)  []  undefined 
*** Exception: Prelude.undefined 

Ich bin nicht 100% sicher, aber ich glaube, es ist auch ein Performance-Vorteil zu nicht Bindung alles im ersten Muster. Das endgültige Muster ergibt das gleiche Ergebnis für xs == [] ohne Auswertung eq oder ys, aber AFAIK es immer noch allocates stack space für ihre Thunks.

+2

Nicht wahrscheinlich, nein. Der Compiler löscht nicht verwendete Bindungen. Es ist wahr, dass in einigen (relativ ungewöhnlichen) Umständen ein unbenutztes Argument als Thunk erzeugt wird, aber das wird unabhängig davon passieren, ob es gebunden ist. – dfeuer

+0

Interessant. Der allgemeine Rat, nicht zu binden, wenn Sie ihn nicht verwenden, ist nur für guten Stil, keine technische Überlegung? –

+2

Ja. Es macht den Lesern klar, dass es nicht verwendet wird. Es gibt eine Compiler-Warnung für nicht verwendete Bindungen (aktiviert durch "-Wall" oder "-fwarn-unused-binds" oder ähnliches). Es verhindert, dass Sie versehentlich vergessen, etwas zu verwenden, das Sie beabsichtigten, wobei diese Absicht signalisiert wird, indem Sie einen Namen binden, der nicht mit einem Unterstrich beginnt. – dfeuer

11

Es ist nicht notwendig zu erraten, wann Sie die Geschichte durch git blame, GHC Trac und die Bibliotheken Mailingliste nachschlagen können.

Ursprünglich war die Definition nur die dritte Gleichung,

intersectBy eq xs ys = [x | x <- xs, any (eq x) ys] 

In https://github.com/ghc/ghc/commit/8e8128252ee5d91a73526479f01ace8ba2537667 die zweite Gleichung als Strikt/Performance-Verbesserung, und zugleich die erste Gleichung hinzugefügt wurde zugegeben, um das zu machen, neue Definition immer mindestens so definiert wie das Original. Ansonsten wäre intersectBy f [] _|__|_ wenn es vorher [] war.

Es scheint mir, dass diese aktuelle Definition jetzt maximal faul ist: Es ist so definiert wie möglich für alle Eingaben, außer dass man wählen muss, ob man zuerst die linke oder die rechte Liste auf Leerheit prüft. (Und, wie ich oben erwähnte, wird diese Wahl getroffen, um mit der historischen Definition übereinzustimmen.)

+0

Würde nicht 'intersectBy eq xs [y] = [y | jedes (eq y) xs] macht es noch definierter, z.B. 'intersectBy (==) [0 ..] [2]'? – chi

+2

@chi, ja, ich glaube schon, und ich glaube, dass es möglich sein sollte, diesen Trick den ganzen Weg zu nehmen und ein endliches Ergebnis sicherzustellen, wenn eines der Argumente endlich ist. Die aktuelle Implementierung entfernt jedoch keine Duplikate und ändert nicht die Reihenfolge, während deins Duplikate entfernt und mein hypothetischer Dubletten entfernt und neu sortiert. – dfeuer

+0

@dfeuer Guter Punkt über die Duplikate - man könnte argumentieren, dass mein Zusatz die Semantik des Originals ändert. Über den Trick den ganzen Weg: es könnte möglich sein, aber es würde einige nicht triviale "faire Planung" erfordern, da Sie im Voraus nicht wissen, welche dieser Listen die endliche ist. Außerdem müssen Sie annehmen, dass die endliche Eins eine Teilmenge der anderen ist, da Sie nicht überprüfen können, ob etwas nicht ein Element einer unendlichen Liste ist. – chi

4

Dort ist ein großer Unterschied in Idris: Idris-Listen sind immer begrenzt! Darüber hinaus ist Idris eine meist strikte (call-by-value) Sprache und verwendet optional einen Totalitätsprüfer, so dass es ziemlich vernünftig anzunehmen ist, dass in den Argumentenlisten keine Bottoms versteckt sein werden. Die Bedeutung dieses Unterschieds besteht darin, dass die beiden Definitionen bei Idris wesentlich semantisch identischer sind als bei Haskell. Die Auswahl, die zu verwenden ist, kann basierend auf der Leichtigkeit des Nachweises von Eigenschaften der Funktion getroffen werden oder kann auf Einfachheit basieren.