【问题标题】:Wrapping sprintf to not throw errors at compile time but at runtime?包装 sprintf 以在编译时但在运行时不抛出错误?
【发布时间】:2020-02-23 02:48:56
【问题描述】:

假设我想拥有sprintf 的别名,我会这样做:

namespace FSharp

module Core =
    let specialsprintf x y =
        sprintf x y

这将给我带来与 sprintf 相同的编译时优势(与其 C# 表亲 API System.String.Format 相比),例如类型检查、检查传递的参数数量是否正确等。

但是,假设我想禁用这个编译时的细节,并通过调用下面的 String.Format 来编写一个简单版本的 sprintf。这可能吗?我知道这个目标听起来很愚蠢,但我想做这个心理练习来确保我理解 F# 打字在这里是如何工作的。如果我这样做(假设我们只能传递一个参数):

namespace FSharp

module Core =
    let specialsprintf x y =
#if NORMAL_FSHARP
        sprintf x y
#else
        let x = x.Replace("%s", "{0}")
        System.String.Format(x,y)
#endif

它甚至没有编译,错误是:

~/FSharpPlayground.fs(17,17): Error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved. (FS0072) (FSharpPlayground)

嗯,为什么?

好的,如果我这样指定类型:

namespace FSharp

module Core =
    let specialsprintf
#if NORMAL_FSHARP
        x
#else
        (x: string)
#endif
        y =
#if NORMAL_FSHARP
            sprintf x y
#else
            let x = x.Replace("%s", "{0}")
            System.String.Format(x,y)
#endif

然后我最终在调用者中出现编译错误:

~/FSharpPlaygroundUse.fs(48,48): Error FS0001: This expression was expected to have type 'obj []' but here has type 'string' (FS0001)

我想我现在需要限定 y 的类型,但不知道该怎么做,以防我想扩展它以使用 2 个参数而不是 1 个(我无法做到使其与ParamArray 属性一起使用)。有些东西告诉我,我可能还需要一个 uncurry 函数,但我有点迷茫:-/

【问题讨论】:

  • 它失败了,因为 String.Format 中的第二个参数是一个对象数组。请改用[| y |]
  • 如果你这样做#if NORMAL_FSHARP x #else (x: string) #endif 并传入字符串以外的其他内容,那么x.Replace("%s", "{0}") 将失败,所以我不确定我是否理解目标是什么。您是否试图诱使编译器认为某些东西不是类型安全的?

标签: f#


【解决方案1】:

我假设您想要的是 sprintf 之类的东西,但是是可变的,并且没有类型检查格式。

不幸的是,F# 没有“uncurrying”或可变参数函数。 话虽如此,可以选择使用参数数组。 ParamArray 仅对类成员有效,而不是绑定,因此我们可以选择 static member,它的范围类似于 let fn () =

type SpecialPrint =
    static member sprintf (format, [<ParamArray>] args) =
        let index = ref -1        
        let stringFormat = Regex.Replace(format, "%[a-z]", (fun _ -> sprintf "{%d}" (Interlocked.Increment index)))
        String.Format(stringFormat, args)

有;

let result = SpecialPrint.sprintf ("Hello %s%s", "World", "!") //Hello World!

【讨论】:

  • P.S.联锁是矫枉过正,但是,嘿。
  • 但是有了这个,我需要改变我当前对 sprintf 的所有使用以使用非咖喱参数,而不是简单的 sprintf->specialsprintf search&replace :(
  • 我无法准确理解您想要什么。
  • 您是否只希望 specialsprintf 仅使用单个参数?
  • printf 是特殊的 - 编译器将您的格式字符串重写为 PrintFormat&lt;&gt;,F# 不支持可变数量的参数。
猜你喜欢
  • 1970-01-01
  • 2022-08-24
  • 2015-09-02
  • 1970-01-01
  • 2014-07-09
  • 2012-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多