Reputation: 202
All of these start from a simple idea: How to write python-style formatted string in ocaml.
pythoners could init a string as:
str = "this var: %s" % this_var
str2 = "this: %s; that: %s" % (this_var, that_var)
but ocaml's formatted string code as :
let str = Printf.sprintf "this var: %s" this_var
let str2 = Printf.sprintf "this: %s; that: %s" this_var that_var
I believed I can do sth to make the ocaml string formating code python-like At first, I defined a function as below:
let (%) s x = Printf.sprintf s x
then, I can write directly as:
let str = "this: %s" % "sth"
but the simple function cannot handle more complex situations as two or more variables. so I wanted to write a little complex function to make it perfectly simulate the python way. I wrote it as below :
let (%) s li =
let split_list = Str.full_split (regexp "%[a-z]") s in
let rec fmt result_str s_list x_list = match s_list with
| [] -> result_str
| shd::stl -> match shd with
| Text t -> fmt (result_str^t) stl x_list
| Delim d -> match x_list with
| [] -> fmt result_str stl []
| xhd::xtl -> fmt (result_str^(Printf.sprintf d xhd)) stl xtl
in
fmt "" split_list li
But the function just CANNOT work, because the type error and also ocaml's list cannot contains multiple types.
if you write sth like: "name: %s; age: %d" % ["John"; 20]
the ocaml compiler world laugh at the code and tell you some type ERROR.
Obviously, I must use Tuple to replace List. but I just do NOT konw how to tail-recursive a variable-length tuple.
any suggestion is welcomed. I have two question indeed.
If Ocaml cannot dynamically generate some string as format6 str and pass it to sprintf? for code:
let s = "%s" in Printf.sprintf s "hello"
would generate ERROR info as:
Error: This expression has type string but an expression was expected of type ('a -> 'b, unit, string) format = ('a -> 'b, unit, string, string, string, string) format6
Upvotes: 2
Views: 2961
Reputation: 6144
This is actually doable if your operator starts with a #
character, since that character has a higher precedence than function application.
let (#%) = Printf.sprintf;;
val ( #% ) : ('a, unit, string) format -> 'a = <fun>
"Hello %s! Today's number is %d." #% "Pat" 42;;
- : string = "Hello Pat! Today's number is 42."
Upvotes: 6
Reputation: 4083
You should check out OCaml Batteries Included's extended/extensible printf
. I have the feeling that you can do what you want with it.
Upvotes: 1
Reputation: 11
If Ocaml cannot dynamically generate some string as format6 str and pass it to sprintf? for code:
let s = "%s" in Printf.sprintf s "hello"
What if we bypass the typing system...
external string_to_format :
string -> ('a, 'b, 'c, 'd, 'e, 'f) format6 = "%identity"
let s = string_to_format "%s" in (Printf.sprintf s "hello" : string);;
I don't claim this to be the ultimate solution, but that's as far as I got after looking at the mailing list, http://pauillac.inria.fr/caml/FAQ/FAQ_EXPERT-eng.html#printf and ocaml's src.
Upvotes: 0
Reputation: 6697
Why would one want to replace statically checked sprintf with some dynamic formatting? OCaml's Printf is both compact in usage and safe at runtime. Compare that to C printf which is compact but unsafe and C++ streams which are safe but verbose. Python's format is no way better than C printf (except that you get exception instead of crashdump).
The only imaginable use-case is format string coming from external source. And it is usually better to move it to compile-time. If it is not possible, then only one needs to fallback to manual dynamic formatting with error-handling (as already said you cannot use Printf with dynamic format string). BTW one such case - internationalization - is covered with existing libraries. Generally, if one wants to dynamically combine multiple values of different types, then one has to wrap them with variants (like ['S "hello"; 'I 20]
) and pattern-match at printing side.
Upvotes: 2
Reputation: 122429
(1) I don't think there's a good way to do it that is going to be nicer than using Printf.sprintf
directly. I mean, you can extend what you've already come up with:
let (%) = Printf.sprintf
let str3 = ("this: %s; that: %s; other: %s" % this_var) that_var other_var
which works, but is ugly because of the parentheses that are necessary for precedence.
(2) It's really hard to generate a format string at runtime because format strings are parsed at compile time. They may look like string literals, but they are actually a different type, which is "something format6". (it figures out whether you want a string or format string based on the inferred type) In fact, the exact type of a format string depends on what placeholders are in the format; that's the only way that it is able to type-check the number and types of format arguments. It's best not to mess with format strings because they are tied very heavily into the type system.
Upvotes: 5