posterior - Tipo de printfn en F#, cadena estática vs dinámica
cadenas musculares pdf articulos (4)
Buena pregunta. Si observa el tipo de printfn
, que es Printf.TextWriterFormat<''a> -> ''a
, verá que el compilador coacciona automáticamente cadenas en objetos TextWriterFormat
en tiempo de compilación, infiriendo el parámetro de tipo apropiado ''a
. Si desea utilizar printfn
con una cadena dinámica, puede realizar esa conversión usted mismo:
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
Si la cadena es estáticamente conocida (como en los ejemplos anteriores), aún puede hacer que el compilador TextWriterFormat
argumento genérico correcto a TextWriterFormat
lugar de llamar al constructor:
let (s:Printf.TextWriterFormat<_>) = "hello"
let (s'':Printf.TextWriterFormat<_>) = "Here''s an integer: %i"
let (s'''':Printf.TextWriterFormat<_>) = "Float: %f; Bool: %b"
Si la cadena es realmente dinámica (p. Ej., Se lee de un archivo), deberá usar explícitamente los parámetros de tipo y llamar al constructor como lo hice en los ejemplos anteriores.
Empecé a jugar con F # en Mono y surgió el siguiente problema que no puedo entender del todo. Buscar información en printfn
y TextWriterFormat
tampoco aportó la iluminación, así que pensé que iba a preguntar aquí.
En FSI ejecuto lo siguiente:
> "hello";;
val it : string = "hello"
> printfn "hello";;
hello
val it : unit = ()
Solo una cadena normal e imprimiéndola. Multa. Ahora quería declarar una variable para contener la misma cadena e imprimirla también:
> 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>''
Entendí por mi lectura que printfn
requiere una cadena constante. También entiendo que puedo evitar este problema con algo como printfn "%s" v
.
Sin embargo, me gustaría entender qué está pasando con la escritura aquí. Claramente, "hello"
es de tipo string
así como v
es. ¿Por qué hay un problema tipo entonces? ¿ printfn
es algo especial? Según tengo entendido, el compilador ya realiza la verificación de tipo en los argumentos de la primera cadena, de modo que printfn "%s" 1
falla ... esto, por supuesto, no podría funcionar con cadenas dinámicas, pero asumí que era una mera conveniencia de el lado del compilador para el caso estático.
Como observa correctamente, la función printfn toma una "Printf.TextWriterFormat <''a>" no una cadena. El compilador sabe cómo convertir entre una cadena constante y un "Printf.TextWriterFormat <''a>", pero no entre una cadena dinámica y un "Printf.TextWriterFormat <'' a>".
Esto plantea la pregunta de por qué no se puede convertir entre una cadena dinámica y un "Printf.TextWriterFormat <''a>". Esto se debe a que el compilador debe mirar los contenidos de la cadena y determinar qué caracteres de control hay en ella (es decir,% s% i etc.), a partir de esto se resuelve el tipo del parámetro tipo de "Printf.TextWriterFormat <'' a> "(es decir, el ''un poco). Esta es una función que devuelve la función printfn y significa que los otros parámetros aceptados por printfn ahora están fuertemente tipados.
Para dejar esto un poco claro en su ejemplo "printfn"% s "", el "% s" se convierte en una "Printf.TextWriterFormat unit>", lo que significa que el tipo de "printfn"% s "" es string -> unit.
Esto solo está relacionado con tu pregunta, pero creo que es un truco útil. En C #, a menudo tengo cadenas de plantillas para usar con String.Format
almacenadas como constantes, ya que hace que el código sea más limpio:
String.Format(SomeConstant, arg1, arg2, arg3)
En lugar de...
String.Format("Some {0} really long {1} and distracting template that uglifies my code {2}...", arg1, arg2, arg3)
Pero dado que la familia de métodos printf
insiste en cadenas literales en lugar de valores, inicialmente pensé que no podría utilizar este enfoque en F # si quería usar printf
. Pero luego me di cuenta de que F # tiene algo mejor: aplicación de función parcial.
let formatFunction = sprintf "Some %s really long %i template %i"
Eso acaba de crear una función que toma una cadena y dos enteros como entrada, y devuelve una cadena. Es decir, string -> int -> int -> string
. Es incluso mejor que una plantilla String.Format constante, porque es un método muy tipado que me permite reutilizar la plantilla sin incluirla en línea.
let foo = formatFunction "test" 3 5
Cuanto más uso F #, más usos descubro para la aplicación de función parcial. Buena cosa.
No creo que sea correcto decir que el valor literal "hello" es de tipo String
cuando se utiliza en el contexto de printfn "hello"
. En este contexto, el compilador infiere el tipo del valor literal como Printf.TextWriterFormat<unit>
.
Al principio, me pareció extraño que un valor de cadena literal tuviera un tipo inferido diferente según el contexto en el que se usaba, pero, por supuesto, estamos acostumbrados a esto cuando tratamos con literales numéricos, que pueden representar enteros, decimales, flotadores. , etc., dependiendo de dónde aparecen.
Si desea declarar la variable antes de usarla a través de printfn, puede declararla con un tipo explícito ...
let v = "hello" : Printf.TextWriterFormat<unit> in printfn v
... o puede usar el constructor para Printf.TextWriterFormat
para convertir un valor de cadena normal al tipo necesario ...
let s = "foo" ;;
let v = new Printf.TextWriterFormat<unit>(s) in printfn v ;;