首页 > 解决方案 > 针对 PrintfFormat 编写单元测试

问题描述

我有一种类型,我试图通过编写单元测试来理解它,但是我无法推理如何处理 PrintfFormat

  type ValueFormat<'p,'st,'rd,'rl,'t,'a> = {
      format: PrintfFormat<'p,'st,'rd,'rl,'t>
      paramNames: (string list) option
      handler: 't -> 'a
    }
    with 
      static member inline construct (this: ValueFormat<_,_,_,_,_,_>) =
        let parser s = 
          s |> tryKsscanf this.format this.handler
            |> function Ok x -> Some x | _ -> None
        let defaultNames =
            this.format.GetFormatterNames() 
              |> List.map (String.replace ' ' '_' >> String.toUpperInvariant)
              |> List.map (sprintf "%s_VALUE")
        let names = (this.paramNames ?| defaultNames) |> List.map (sprintf "<%s>")
        let formatTokens = this.format.PrettyTokenize names
        (parser, formatTokens)

我有信心我可以解决所有问题,但 PrintfFormat 正在向我抛出所有这些泛型。

我正在查看要单元测试的代码的文件是FSharp.Commandline 框架

我的问题是,什么是 PrintfFormat 以及应该如何使用它?

printf.fs 文件的链接在这里。它包含 PrintfFormat 的定义

标签: f#

解决方案


F# 源代码中定义的类型PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>有四个类型参数:

  • 'Result是您的格式化/解析函数产生的类型。这是string为了sprintf

  • 'Printer是一种基于格式字符串生成的函数类型,例如"%d and %s"会给你一个函数类型int -> string -> 'Result

  • 'Tuple是基于格式字符串生成的元组类型,例如"%d and %s"会给你一个元组类型int * string

  • 'State并且'Residue是当您使用自定义格式化程序时使用的类型参数%a,但为了简单起见,我现在将忽略它(除非您有%a格式字符串,否则永远不需要它)

有两种使用类型的方法。无论是格式化,在这种情况下,您都需要编写一个返回'Printer结果的函数。困难在于您需要使用反射构造返回函数。这是一个仅适用于一种格式字符串的示例:

open Microsoft.FSharp.Reflection

let myformat (fmt:PrintfFormat<'Printer,obj,obj,string,'Tuple>) : 'Printer = 
  unbox <| FSharpValue.MakeFunction(typeof<'Printer>, fun o ->
    box (o.ToString()) )
  
myformat "%d" 1
myformat "%s" "Yo"

这只是返回作为%dor的值传递的参数%s。要使其适用于多个参数,您需要递归地构造函数(这样它不仅是例如int -> string,而且是int -> (int -> string)

在另一种用途中,您定义一个返回的函数,'Tuple它需要根据指定的格式化字符串创建一个包含值的元组。这是一个仅处理%s%d格式化字符串的小示例:

open FSharp.Reflection

let myscan (fmt:PrintfFormat<'Printer,obj,obj,string,'Tuple>) : 'Tuple = 
  let args = 
    fmt.Value 
    |> Seq.pairwise
    |> Seq.choose (function
      | '%', 'd' -> Some(box 123)
      | '%', 's' -> Some(box "yo")
      | _ -> None)
  unbox <| FSharpValue.MakeTuple(Seq.toArray args, typeof<'Tuple>)
  
myscan "%d %s %d"

推荐阅读