2015-10-28 9 views
5

Hier einfachen Code:Kompilation Problem mit Split/Splitter

import std.algorithm; 
import std.array; 
import std.file; 

void main(string[] args) 
{ 
    auto t = args[1].readText() 
     .splitter('\n') 
     .split("---") 
    ; 
} 

Sieht aus wie es funktionieren sollte, aber es wird nicht kompiliert. DMD 2.068.2 nicht mit diesem Fehler:

Error: template std.algorithm.iteration.splitter cannot deduce function from 
argument types !()(Result, string), candidates are: 
... 
Error: template instance std.array.split!(Result, string) error instantiating 

Es kompiliert, wenn ich .array vor .split einfügen.

Fehle ich etwas? Oder ist es ein Fehler? Ich habe versucht, im Bugtracker eine kurze Suche durchzuführen, habe aber nichts gefunden.

Antwort

19

Fazit: Probleme wie diese können oft behoben werden, indem man einen .array Aufruf direkt vor der fehlerhaften Funktion festhält. Dies stellt einen Puffer mit genügend Funktionalität bereit, um den Algorithmus auszuführen.

Was folgt, ist die Argumentation hinter der Bibliothek und ein paar andere Ideen, die Sie diese verwenden können, zu implementieren:

Der Grund dafür ist nicht kompiliert mit der Philosophie hinter std.algorithm zu tun hat, und reicht: dass Sie sind so günstig wie möglich, um Kostenentscheidungen auf die oberste Ebene zu bringen.

Im std.algorithm (und den meisten gut geschriebenen Bereichen und ressourcenintensiven Algorithmen) werden Template-Constraints alle Eingaben ablehnen, die nicht das bieten, was sie kostenlos benötigen. Auf ähnliche Weise geben Transformationsbereiche wie Filter, Splitter usw. nur die Funktionen zurück, die sie zu minimalen Kosten bieten können.

Indem sie sie zur Kompilierungszeit zurückweisen, zwingen sie den Programmierer, die Entscheidung auf höchster Ebene zu treffen, wie sie diese Kosten bezahlen wollen. Sie könnten die Funktion neu schreiben, um anders zu arbeiten, Sie könnten sie selbst mit einer Vielzahl von Techniken puffern, um die Kosten im Voraus zu bezahlen, oder was auch immer Sie finden können, das funktioniert.

So, was passiert mit Ihrem Code: readText gibt ein Array zurück, das ist ein fast voller Funktionsumfang. (Da es eine string aus UTF-8 zurückgibt, bietet es tatsächlich keinen wahlfreien Zugriff, soweit Phobos betroffen ist (obwohl, verwirrend, die Sprache selbst sieht es anders, suchen Sie die D-Foren für die "Autodecode" Kontroverse wenn Sie möchten mehr erfahren) seit das Finden eines Unicode-Codepunkts in einer Liste von utf-8-Zeichen mit variabler Länge erfordert, alles zu scannen. Alles zu scannen ist keine minimalen Kosten, daher wird Phobos es niemals versuchen, es sei denn, Sie fragen ausdrücklich danach.

Wie auch immer, readText gibt eine Reihe mit vielen Funktionen, einschließlich der Ersparnis, die splitter benötigt. Warum muss splitter gespeichert werden? Betrachten Sie das Ergebnis, das es verspricht: eine Reihe von Strings, die am letzten Splitpunkt beginnen und bis zum nächsten Splitpunkt weitergehen. Wie sieht die Implementierung aus, wenn Sie dies für einen allgemeineren Bereich schreiben, den es möglicherweise billig macht?

Etwas in diese Richtung: zuerst, save Ihre Ausgangsposition, damit Sie es später zurückgeben können. Fahren Sie dann mit popFront fort, bis Sie den Splitpunkt gefunden haben. Wenn dies der Fall ist, geben Sie den gespeicherten Bereich bis zum Punkt des Splitpunkts zurück. Dann, popFront nach dem Split-Punkt und wiederholen Sie den Vorgang, bis Sie die ganze Sache verbraucht haben (while(!input.empty)).

So, da splitter Implementierung erfordert die Fähigkeit, save den Ausgangspunkt, erfordert es mindestens einen Vorwärtsbereich (das ist nur ein Speicherbereich.Andrei empfindet es jetzt als etwas albern, solche Namen zu nennen, weil es so viele Namen gibt, aber zu dieser Zeit schrieb er noch std.algorithm. Er glaubte immer noch daran, ihnen alle Namen zu geben.

Nicht alle Bereiche sind Vorwärtsbereiche! Arrays sind, das Speichern ist so einfach wie das Zurückgeben eines Slices von der aktuellen Position. Viele numerische Algorithmen sind auch, speichern sie nur eine Kopie des aktuellen Zustands zu halten. Die meisten Transformationsbereiche sind speicherbar, wenn der Bereich, den sie transformieren, gespeichert werden kann - sie müssen lediglich den aktuellen Status zurückgeben.

...... wie ich dies schreibe, eigentlich, ich denke, Ihr Beispiel sollte sein sollte. Und tatsächlich gibt es eine Überladung, die ein Prädikat nimmt und kompiliert!

http://dlang.org/phobos/std_algorithm_iteration.html#.splitter.3

import std.algorithm; 
    import std.array; 
    import std.stdio; 

    void main(string[] args) 
    { 
      auto t = "foo\n---\nbar" 
        .splitter('\n') 
        .filter!(e => e.length) 
        .splitter!(a => a == "---") 
      ; 
      writeln(t); 
    } 

Ausgang: [["foo"], ["bar"]]

Ja, es kompiliert und Split auf den Leitungen gleich eine bestimmte Sache. Die andere Überladung, .splitter("---"), kann nicht kompiliert werden, da diese Überladung eine Slice-Funktionalität erfordert (oder eine schmale Zeichenkette, die Phobos generell nicht trennt), aber weiß, dass sie tatsächlich trotzdem sein kann, also ist die Funktion speziell Überall in der Bibliothek.)

Aber warum muss man schneiden anstatt nur zu speichern? Ehrlich gesagt, ich weiß es nicht. Vielleicht verpasse ich auch etwas, aber die Existenz der Überlastung, die funktioniert, bedeutet für mich, dass meine Vorstellung des Algorithmus richtig ist; Es kann auf diese Weise erfolgen. Ich glaube, Slicing ist ein bisschen billiger, aber die sichere Version ist auch billig genug (Sie würden zählen, wie viele Gegenstände Sie vorbeikletterten, um zum Splitter zu kommen, dann geben Sie saved.take(that_count) zurück .... vielleicht ist das der Grund genau dort : Sie würden zweimal über die Elemente iterieren, einmal innerhalb des Algorithmus, dann wieder außerhalb, und die Bibliothek hält das für ausreichend kostspielig, um eine Ebene hochzustufen. (Die Prädikat-Version umgeht dies, indem sie Ihre-Funktion zum Scannen und damit Phobos macht es ist nicht mehr sein Problem, es ist dir bewusst, was deine eigene Funktion macht.)

Ich kann die Logik darin sehen. Ich könnte jedoch beide Wege darauf gehen, weil die Entscheidung, es tatsächlich wieder zu überfahren, ist immer noch auf der Außenseite, aber ich verstehe nicht, warum das nicht wünschenswert sein sollte, ohne etwas nachzudenken.

Schließlich, warum bietet splitter Indexierung oder Slicing auf seinem Ausgang? Warum bietet filter es entweder nicht an? Warum bietet map es an?

Nun, es hat wieder mit dieser Low-Cost-Philosophie zu tun. map kann es anbieten (unter der Annahme seiner Eingabe), weil map nicht tatsächlich die Anzahl der Elemente ändert: das erste Element in der Ausgabe ist auch das erste Element in der Eingabe, nur mit einer bestimmten Funktion auf dem Ergebnis ausgeführt. Dito für den letzten und alle anderen dazwischen.

filter ändert das aber. Das Herausfiltern der ungeraden Zahlen von [1,2,3] ergibt nur [2]: Die Länge ist anders und 2 wird jetzt am Anfang anstatt in der Mitte gefunden. Aber Sie können nicht wissen wo es ist, bis Sie tatsächlich den Filter anwenden - Sie können nicht herumspringen, ohne das Ergebnis zu puffern.

splitter ist Filter ähnlich. Es ändert die Platzierung der Elemente, und der Algorithmus weiß nicht wo es teilt, bis es tatsächlich durch die Elemente läuft.So kann es sagen, wie Sie iterieren, aber nicht vor der Iteration, so wäre die Indexierung O(n) Geschwindigkeit - zu teuer. Indexierung soll extrem billig sein.


Wie auch immer, jetzt, da wir verstehen, warum das Prinzip gibt es - lassen Sie, das Ende Programmierer Entscheidungen über teure Dinge wie Pufferung machen (was erfordert mehr Speicher als frei ist) oder zusätzliche Iteration (die mehr CPU benötigt Zeit, als ist kostenlos für den Algorithmus), und haben eine Idee, warum splitter es braucht, über seine Implementierung nachzudenken, können wir Wege zur Erfüllung des Algorithmus suchen: Wir müssen entweder die Version verwenden, die ein paar mehr CPU isst Zyklen und schreiben Sie es mit unserer benutzerdefinierten Vergleichsfunktion (siehe Beispiel oben), oder stellen Sie irgendwie schneiden. Der einfachste Weg besteht darin, das Ergebnis in einem Array zu puffern.

import std.algorithm; 
import std.array; 
import std.file; 

void main(string[] args) 
{ 
    auto t = args[1].readText() 
     .splitter('\n') 
     .array // add an explicit buffering call, understanding this will cost us some memory and cpu time 
     .split("---") 
    ; 
} 

Sie können auch lokal puffern oder etwas selbst die Kosten für die Zuweisung zu reduzieren, aber wie Sie es tun, hat die Kosten irgendwo bezahlt werden und Phobos vorzieht Sie den Programmierer, der die Bedürfnisse Ihrer versteht Programm und wenn Sie bereit sind, diese Kosten zu bezahlen oder nicht, um diese Entscheidung zu treffen, anstatt sie in Ihrem Namen zu bezahlen, ohne es Ihnen zu sagen.

+0

Große Antwort. Vielen Dank. – sigod

Verwandte Themen