F# Interactive技巧:在sprintf/printf/fprintf中使用AddPrinter,addPrintTransformer和%A来格式化数据
Mingtian Ni 问过这样的一个问题:
我想改变一些类型在fsi输出中的格式,尤其是集合类型。怎样的方法才是比较合理的呢?….有哪位朋友可以提供一点信息吗? 或者一些指南和示例那就更好了。
下面就是一些在F#Interactive中格式化数据的技巧。 这并不足以理解为一个指南,这仅仅能帮你入门。如果你需要更多的示例,请联系我。
对F# interactive来说,一个简单的方法就是使用fsi.AddPrintTransformer来生成一个显示对象的替代品(或者使用更小的、产生一个字符串的fsi.AddPrinter函数),例如:
type C(elems:int list) =
member x.Contents = elems
member x.IsBig = elems.Length > 100
let c = C [1;2;3]
fsi.AddPrintTransformer (fun (c:C) -> box c.Contents)
产生:
val c : C = [1; 2; 3]
AddprintTransformer有一点很好,你可以让它成为有条件的——返回null 来表明formatter(用来格式化的一串字符)应该被忽略:
fsi.AddPrintTransformer (fun (c:C) -> if c.IsBig then null else box c.Contents)
有一点非常好,如果你将它和“obj”类型一起使用,你可以对任意的对象作特定的格式化:
fsi.AddPrintTransformer (fun (obj:obj) -> match obj with :? C as c -> box c.Contents | _ -> null)
这里有一个问题,fsi.AddPrinter和fsi.AddPrintTransformer不会更改位于sprintf、printf等函数中的%A的行为。对于那些设置一个简单标志(即命名一个成员属性产生一个代理对象)而受限制的类型,以及一些周围的文字:
[<StructuredFormatDisplayAttribute("CCC {Contents}")>]
type C(elems:int list) =
member x.Contents = elems
let c = C [1;2;3]
产生:
val c : C = CCC [1; 2; 3]
如果你的类型是一个普通的集合类, 那么使用一个list 或者sequence 作为这个替代对象。
如果你的类型是一个matirx或者table 类型,那么使用一个2D array作为这个替代对象。
如果你的类型在逻辑上说是一个union 类型,但你已经隐藏了其背后的抽象边界的表现形式,那么可以考虑使用一个独立的、能够解开你的对象的结构的helper union 类型(如:如果你的类型是一个递归类型,那么可以解开它的第一层)
如果你的数据是递归树型的,你可以描述它的子树为一个list:
[<StructuredFormatDisplayAttribute("Tree {Contents}")>]
type Tree(node: int, elems: Tree list) =
member x.Contents = (node, elems)
let c = Tree (1, [ Tree (2, []); Tree (3, [ Tree (4, []) ]) ])
let c2 = Tree (1, [ c; c])
let c3 = Tree (1, [ c2; c2])
产生令人舒适的结果:
val c3 : Tree =
Tree (1,
[Tree (1,
[Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])]);
Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])])]);
Tree (1,
[Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])]);
Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])])])])
通常,你也应该考虑实现ToString方法,如果你还经常使用VS debugger,也应该考虑添加一个DebuggerDisplay属性.