2015-02-24 7 views
7

Ich bin neu in Scala und funktionale Programmierung im Allgemeinen. Also hier sind meine Zweifel.Was ist der geeignete Rückgabewert, wenn das Muster mit Nil übereinstimmt und wir Nil zurückgeben wollen?

In einer Funktion mit Pattern-Matching, wenn case Nil abgestimmt ist, und wir wollen Nil zurückkehren, sollten wir zurückkehren Nil oder der Datentyp selbst? Zum Beispiel

def drop[A](l: List[A], n: Int): List[A] = { 
    if (n <= 0) l 
    else l match { 
     case Nil => Nil 
     case Cons(_, t) => drop(t, n - 1) 
    } 
} 

Dies ist eine Funktion, die die ersten n Kopfelemente aus einer einfach verketteten Liste fällt. Hier, für den ersten Fall, sollte ich Nil (vielleicht als eine gute Praxis) zurückgeben oder sollte ich l zurückgeben (weil es dann müssen wir nicht das Objekt Nil konstruieren)?

Antwort

8

Es gibt nur eine Singleton-Instanz des Objekts Nil. Wenn Sie Nil schreiben, erstellen Sie nicht jedes Mal ein neues, Sie verwenden nur das einzige existierende.

Es ist normalerweise am besten, Nil zu schreiben, weil es besser lesbar ist. Zumindest habe ich das immer gelesen und geschrieben.

+0

Danke! Das macht Sinn. – aa8y

1

Da es nur eine Instanz von Nil gibt, spielt es keine Rolle, da es sich um das gleiche Objekt handelt.


Nun ist die eigentliche Frage ist: was besser lesbar ist?

  • Wenn Sie es machen wollen klar, dass, wenn Sie Nil bekommen, dann wird Nil zurückgegeben, dann Nil => Nil
  • schreiben Wenn es logischer scheint l zurückzukehren, auch wenn es eigentlich Nil ist, dann Nil => l schreiben

IMHO, in Ihrem Fall, Nil => Nil ist mir klarer.

+1

Können Sie bitte ein Beispiel für den Fall geben, in dem der Rückgabewert "Nil" ist, aber das Schreiben von "l" den Code lesbarer macht? Ich habe Instanzen gesehen, bei denen ein Akkumulator auf "Nil" initialisiert wurde, der zurückgegeben wird, weil er mehr Werte akkumulieren kann oder nicht. Aber ich habe den Fall, den Sie erwähnt haben, noch nicht gesehen. – aa8y

0

Wenn die Nil => Nil wirklich Ihre Gefühle in Ihrem Haupt-Logikfluss zu verletzen, könnte man erwägen, bereichern-my-Bibliothek-Stil implizite Klasse innerhalb einer neuen Methode zu verstecken:

implicit class ListWithIfNonEmpty[A](list: List[A]) { 
    /** 
    * Replaces a list with a new one in the case that it is 
    * not empty. newList is probably a conversion of the original 
    * list, but the original list is available in the caller's 
    * scope, so no need to pass it as a parameter. 
    */ 
    def ifNonEmpty[B](newList: => List[B]): List[B] = list match { 
    case Nil => Nil 
    case _ => newList 
    } 
} 

Und dann drop Verfahren wird ein etwas saubere:

def drop[A](l: List[A], n: Int): List[A] = l.ifNonEmpty { 
    if (n <= 0) l else drop(l.tail, n - 1) 
} 

Vielleicht ein weniger sperrigen Namen für die neue Methode gibt. Diese Art von Sache ist nur ein großer Gewinn für Ihren Hauptlogikfluss in jenen Fällen, in denen der Methodenname die Semantik, für die Sie sich entscheiden, wirklich offensichtlich macht. Aber du bekommst den Drift.

Verwandte Themen