28 Stimmen

Typ von printfn in F#, statischer vs. dynamischer String

Ich habe gerade angefangen, mit F# in Mono herumzuspielen, und es ist folgendes Problem aufgetreten, das ich nicht ganz verstehe. Nachschlagen von Informationen über printfn y TextWriterFormat hat auch keine Erleuchtung gebracht, also dachte ich, ich frage hier mal nach.

In FSI führe ich die folgenden Schritte aus:

> "hello";;
val it : string = "hello"
> printfn "hello";;
hello
val it : unit = ()

Einfach eine normale Zeichenkette und deren Ausdruck. Nun gut. Jetzt wollte ich eine Variable deklarieren, die dieselbe Zeichenkette enthält, und sie ebenfalls ausdrucken:

> let v = "hello" in printfn v ;;
let v = "hello" in printfn v ;;
---------------------------^
\...\stdin(22,28): error FS0001: The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>'

Nach meiner Lektüre habe ich verstanden, dass printfn erfordert eine konstante Zeichenfolge. Ich verstehe auch, dass ich dieses Problem mit etwas wie folgenden umgehen kann printfn "%s" v .

Ich würde aber gerne verstehen, was es mit dem Tippen auf sich hat. Offensichtlich, "hello" ist vom Typ string wie auch v ist. Warum gibt es dann ein Typenproblem? Ist printfn etwas Besonderes? So wie ich es verstehe, führt der Compiler bereits eine Typüberprüfung der Argumente der ersten Zeichenkette durch, so dass printfn "%s" 1 scheitert dies könnte natürlich nicht mit dynamischen Strings funktionieren, aber ich nahm an, dass dies eine reine Bequemlichkeit von der Compiler-Seite für den statischen Fall ist.

30voto

kvb Punkte 54045

Gute Frage. Wenn Sie sich die Art der printfn das ist Printf.TextWriterFormat<'a> -> 'a werden Sie sehen, dass der Compiler Zeichenketten automatisch in TextWriterFormat Objekte bei der Kompilierung, wobei der entsprechende Typparameter abgeleitet wird 'a . Wenn Sie Folgendes verwenden möchten printfn mit einer dynamischen Zeichenfolge, können Sie diese Konvertierung einfach selbst vornehmen:

let s = Printf.TextWriterFormat<unit>("hello")
printfn s

let s' = Printf.TextWriterFormat<int -> unit>("Here's an integer: %i")
printfn s' 10

let s'' = Printf.TextWriterFormat<float -> bool -> unit>("Float: %f; Bool: %b")
printfn s'' 1.0 true

Wenn die Zeichenkette statisch bekannt ist (wie in den obigen Beispielen), dann können Sie den Compiler immer noch das richtige generische Argument für TextWriterFormat anstatt den Konstruktor aufzurufen:

let (s:Printf.TextWriterFormat<_>) = "hello"
let (s':Printf.TextWriterFormat<_>) = "Here's an integer: %i"
let (s'':Printf.TextWriterFormat<_>) = "Float: %f; Bool: %b"

Wenn die Zeichenkette wirklich dynamisch ist (z. B. wenn sie aus einer Datei gelesen wird), müssen Sie explizit die Typparameter verwenden und den Konstruktor aufrufen, wie ich es in den vorherigen Beispielen getan habe.

8voto

Joel Mueller Punkte 27700

Dies ist nur etwas im Zusammenhang mit Ihrer Frage, aber ich denke, es ist ein praktischer Trick. In C# habe ich oft Template-Strings für die Verwendung mit String.Format als Konstanten gespeichert werden, da dies zu einem saubereren Code führt:

String.Format(SomeConstant, arg1, arg2, arg3)

Stattdessen...

String.Format("Some {0} really long {1} and distracting template that uglifies my code {2}...", arg1, arg2, arg3)

Da aber die printf Methodenfamilie auf Zeichenketten anstelle von Werten besteht, dachte ich zunächst, dass ich diesen Ansatz in F# nicht verwenden könnte, wenn ich die printf . Aber dann habe ich erkannt, dass F# etwas Besseres hat - die Anwendung von Teilfunktionen.

let formatFunction = sprintf "Some %s really long %i template %i"

Damit wurde gerade eine Funktion erstellt, die eine Zeichenkette und zwei Ganzzahlen als Eingabe annimmt und eine Zeichenkette zurückgibt. Mit anderen Worten, string -> int -> int -> string . Es ist sogar besser als eine konstante String.Format-Vorlage, weil es eine stark typisierte Methode ist, mit der ich die Vorlage wiederverwenden kann, ohne sie inline einzubinden.

let foo = formatFunction "test" 3 5

Je mehr ich F# benutze, desto mehr Einsatzmöglichkeiten entdecke ich für partielle Funktionsanwendungen. Tolles Zeug.

7voto

Daniel Pratt Punkte 11859

Ich glaube nicht, dass es korrekt ist, zu sagen, dass der literale Wert "Hallo" vom Typ String bei Verwendung im Zusammenhang mit printfn "hello" . In diesem Zusammenhang schließt der Compiler auf den Typ des Literalwerts als Printf.TextWriterFormat<unit> .

Zunächst erschien es mir seltsam, dass ein Zeichenkettenliteral je nach dem Kontext, in dem es verwendet wird, einen anderen abgeleiteten Typ hat, aber das sind wir ja von numerischen Literalen gewohnt, die je nachdem, wo sie auftauchen, Ganzzahlen, Dezimalzahlen, Gleitkommazahlen usw. darstellen können.

Wenn Sie die Variable deklarieren möchten, bevor Sie sie über printfn verwenden, können Sie sie mit einem expliziten Typ deklarieren...

let v = "hello" : Printf.TextWriterFormat<unit> in printfn v

...oder Sie können den Konstruktor für Printf.TextWriterFormat zur Umwandlung einer normal String-Wert auf den erforderlichen Typ...

let s = "foo" ;;
let v = new Printf.TextWriterFormat<unit>(s) in printfn v ;;

4voto

Robert Punkte 6358

Wie Sie richtig bemerken, nimmt die Funktion printfn ein "Printf.TextWriterFormat<'a>" an, nicht eine Zeichenkette. Der Compiler kann zwischen einer konstanten Zeichenkette und einem "Printf.TextWriterFormat<'a>" konvertieren, aber nicht zwischen einer dynamischen Zeichenkette und einem "Printf.TextWriterFormat<'a>".

Das wirft die Frage auf, warum es nicht zwischen einer dynamischen Zeichenkette und einem "Printf.TextWriterFormat<'a>" konvertieren kann. Der Grund dafür ist, dass der Compiler den Inhalt der Zeichenkette betrachten und feststellen muss, welche Steuerzeichen darin enthalten sind (z. B. %s %i usw.), und daraus den Typ des Typparameters von "Printf.TextWriterFormat<'a>" (d. h. das 'a-Bit) ermitteln muss. Dies ist eine Funktion, die von der Funktion printfn zurückgegeben wird und bedeutet, dass die anderen von printfn akzeptierten Parameter nun stark typisiert sind.

Um dies etwas zu verdeutlichen, wird in Ihrem Beispiel "printfn "%s"" das "%s" in ein "Printf.TextWriterFormat unit>" umgewandelt, was bedeutet, dass der Typ von "printfn "%s"" String -> unit ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X