F# - 如何一起定义多个泛型函数

F# - How to defining multiple generic functions together

确定:

let em inp=sprintf"<em>%A</em>"inp
let bold inp=sprintf"<b>%A</b>"inp
printfn"%s"<|em"blabla"///<em>blabla</em>

试图一起定义(编译错误):

let em2,bold2=
    let tag a b=sprintf"<%s>%A</%s>"a b a
    (fun inp->tag"em"inp),tag"b"

错误:

Value restriction. The value 'em2' has been inferred to have generic type val em2 : ('_a -> string -> string)
Either make the arguments to 'em2' explicit or, if you do not intend for it to be generic, add a type annotation.F# Compiler(30)

我认为这行不通,因为 F# 编译器不会将元组视为 "simple immutable value":

The compiler performs automatic generalization only on complete function definitions that have explicit arguments, and on simple immutable values.

This means that the compiler issues an error if you try to compile code that is not sufficiently constrained to be a specific type, but is also not generalizable. The error message for this problem refers to this restriction on automatic generalization for values as the value restriction.

相反,我认为您必须单独定义它们,如下所示:

let tag a b=sprintf"<%s>%A</%s>"a b a
let em2 inp=tag"em"inp
let bold2 b=tag"b"b

如果您希望在这里隐藏 tag 的定义,您可以将其设为私有。

我喜欢将逻辑(此处:HTML 格式化)集中在单个 factory 函数中的想法,以执行 DRY 原则。

与其将 tag 工厂函数完全隐藏在闭包中,不如将其隐藏在其他模块中,使其成为 private 通常足够的封装。重命名后:

let private inside tag content = // 'a -> '-b -> string
    $"<{tag}>{content}</{tag}>"  //  F# 5 interpolated string

那么,在F#中生成具体函数的常用方式就是偏应用。由于当前 inside 函数是泛型的,我们不能在不丢失泛型类型的情况下使用点自由表示法(意味着隐式参数 content):

let em = inside "em" // ⚠️ obj -> string 

我们有 2 个解决方案:

  1. 具有明确的 content 参数:let em content = inside "em" content 但不够优雅。
  2. 更改inside 函数的签名并使所有参数类型为string。事实上,函数 inside 并不关心其参数的类型 - 它只关心 strings 因为它使用 ToString() 方法将它们隐式转换为 string调用此函数时可能会导致意外。
let private inside tag content =    // string -> string -> string
    $"<%s{tag}>%s{content}</{tag}>" //  %s to indicate parameters are strings

let em     = inside "em"            // string -> string
let strong = inside "strong"