2010-01-29 9 views
24

Właśnie zacząłem bawić się z F # w Mono i pojawił się następujący problem, którego nie mogę całkiem zrozumieć. Wyszukiwanie informacji na temat printfn i TextWriterFormat również nie przyniosło oświecenia, więc pomyślałem, że zapytam tutaj.Typ printfn w F #, static vs dynamiczny ciąg

W FSI uruchomić następujące:

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

prostu normalny ciąg i drukuje go. W porządku. Teraz chciałem zadeklarować zmienną zawierają ten sam ciąg znaków i wydrukować, a także:

> 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>' 

zrozumiałem z lektury, że printfn wymaga stałej ciąg. Rozumiem również, że mogę obejść ten problem z czymś takim jak printfn "%s" v.

Chciałbym jednak zrozumieć, co się dzieje z pisaniem tutaj. Oczywiście, "hello" jest typu string, jak również v. Dlaczego istnieje wtedy problem z typem? Czy printfn jest czymś wyjątkowym? Jak rozumiem, kompilator wykonuje już sprawdzanie typów na argumentach pierwszego ciągu, tak, że printfn "%s" 1 zawiedzie ... to oczywiście nie działałoby z dynamicznymi łańcuchami, ale założyłem, że jest to po prostu wygoda od kompilatora dla przypadek statyczny.

Odpowiedz

25

Dobre pytanie. Jeśli spojrzysz na typ printfn, który jest Printf.TextWriterFormat<'a> -> 'a, zobaczysz, że kompilator automatycznie wymusza łańcuchy w obiektach TextWriterFormat podczas kompilacji, wywodząc odpowiedni parametr typu 'a. Jeśli chcesz użyć printfn z dynamicznym ciągiem, można po prostu wykonać, że konwersja siebie:

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 

Jeśli łańcuch jest statycznie znane (jak w powyższych przykładach), wówczas można jeszcze kompilator wywnioskować prawo generic argument TextWriterFormat zamiast wywołanie konstruktora:

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

Jeśli łańcuch jest naprawdę dynamiczny (np to odczytać z pliku), a następnie trzeba jawnie użyć parametrów typu i wywołać konstruktora jak ja w poprzednich przykładach.

7

Nie sądzę, że słusznie jest mówić, że dosłowna wartość "cześć" jest typu String, gdy jest używana w kontekście printfn "hello". W tym kontekście kompilator podaje typ wartości literalnej jako Printf.TextWriterFormat<unit>.

Początkowo wydawało mi się dziwne, że literalna wartość ciągu będzie miała inny wywnioskowany typ w zależności od kontekstu, w którym był używany, ale oczywiście jesteśmy do tego przyzwyczajeni, gdy mamy do czynienia z literałami liczbowymi, które mogą reprezentować liczby całkowite , miejsca dziesiętne, pływaki itp. w zależności od miejsca, w którym się pojawiają.

Jeśli chcesz zadeklarować zmienną przed użyciem go przez printfn można deklarować go z wyraźną typu ...

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

... czy można użyć konstruktora dla Printf.TextWriterFormat konwertować a normalna Wartość ciągu do wymaganego typu ...

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

Jak prawidłowo obserwować, funkcja printfn wykonuje "Printf.TextWriterFormat <„a>", a nie ciąg. Kompilator wie, jak konwertować ciąg stały i "Printf.TextWriterFormat <" a> ", ale nie między ciągiem dynamicznym a" Printf.TextWriterFormat < "a>".

To nasuwa pytanie, dlaczego nie można przekonwertować łańcucha dynamicznego na "Printf.TextWriterFormat <" a> ". Dzieje się tak dlatego, że kompilator musi sprawdzić zawartość łańcucha i określić, jakie znaki kontrolne są w nim (tj.% S% i itd.), Z tego wywodzi się typ parametru typu "Printf.TextWriterFormat <" a> "(czyli" trochę "). Jest to funkcja zwracana przez funkcję printfn i oznacza, że ​​inne parametry akceptowane przez printfn są teraz mocno wpisane.

Aby to trochę wyjaśnić w twoim przykładzie "printfn"% s "" "% s" jest konwertowane na "Printf.TextWriterFormat unit>", co oznacza, że ​​typ "printfn"% s "" jest ciągiem -> jednostka.

7

Jest to tylko trochę związane z twoim pytaniem, ale myślę, że to poręczna sztuczka. W języku C#, często mają ciągi szablonów do użytku z String.Format przechowywane jako stałe, a to sprawia, że ​​dla czystszego kodu:

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

Zamiast ...

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

Ale ponieważ rodziny metod printf nalegają na literalnych ciągach zamiast wartości, początkowo myślałem, że nie mógłbym użyć tego podejścia w F #, jeśli chciałem użyć printf. Ale wtedy zdałem sobie sprawę, że F # ma coś lepszego - zastosowanie funkcji częściowej.

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

To właśnie utworzyło funkcję, która pobiera łańcuch i dwie liczby całkowite jako dane wejściowe i zwraca ciąg znaków. To znaczy string -> int -> int -> string. Jest nawet lepszy niż stały szablon String.Format, ponieważ jest to silnie typowana metoda, która umożliwia ponowne wykorzystanie szablonu bez uwzględniania go w linii.

let foo = formatFunction "test" 3 5 

Im więcej używam F #, tym więcej zastosowań odkrywam dla częściowego zastosowania funkcji. Świetna sprawa.