Warm tip: This article is reproduced from serverfault.com, please click
f#

其他-有没有一种方法可以使F#保持参数类型的通用性?

(其他 - Is there a way to make F# keep the type of a parameter generic?)

发布于 2020-12-02 20:28:51

我是F#的新手(到目前为止很喜欢),并且我一直在尝试代码。我发现了一些我理解其原因的东西,但不知道如何克服它。

我有一些类似于以下内容的代码:

let pairTest list = if List.length list = 2 then Some list else None

// This seems to compile and work just fine
[ [1; 2] ]
|> List.choose pairTest
|> List.map (fun l -> l |> List.map (fun i -> "a"))
|> List.choose pairTest
|> printfn "%A"

let testFunc groupTest =
  [ [1; 2] ]
  |> List.choose groupTest // Okay
  |> List.map (fun l -> l |> List.map (fun i -> "a"))
  |> List.choose groupTest // Error: expected int list, not string list
  |> printfn "%A"

testFunc pairTest

我了解F#的类型推断的工作原理,并且可以看到在testFunc中,它是int list在第一次调用时到达的groupTest,然后分配该类型(然后groupTest在IDE中检查该参数将其显示为int list -> int list option),然后使其对于第二次调用无效。但是,如果我运行一个函数(第一功能块)的代码外,它工作得很好并无缝地切换pairTest<'a>之间int liststring list

我的问题是:有没有办法阻止F#锁定类型groupTest或者,如果不是,是否有一种(F#-惯用的)方法来解决这个问题?

在这种特定情况下,我只可以传入一个length参数并将List.length检查移到函数内部,但是我仍然对更一般情况的答案感到好奇(例如,如果我的检查比较复杂)。

Questioner
Alphacat
Viewed
0
Tomas Petricek 2020-12-03 05:20:53

仅使用普通函数无法在F#中完成此操作。问题是在F#中,你不能将通用函数作为参数传递给另一个函数。当F#打印的类型时testFunc,你将得到:

('a list -> 'a list option) -> unit

这里的关键是,'a当你调用函数时,它是一个固定的类型变量。形式上,对整个类型有一个通用的量化,即:

forall 'a . (('a list -> 'a list option) -> unit)

这意味着,当你调用时testFunc,首先要设置'a为一种特定的类型。这里需要的(以及F#不支持的功能)是仅对参数类型进行量化:

(forall 'a . ('a list -> 'a list option)) -> unit

如果可以执行此操作,则函数主体本身可以'a在访问该函数时设置为两种不同的类型。这在F#中无法通过普通函数完成,但是你可以使用接口对此进行编码:

type GroupTest =
  abstract Invoke<'T> : 'T list -> 'T list option

该接口具有通用方法,即本质上是type的函数(forall 'a . ('a list -> 'a list option))然后,你可以testFunc根据以下内容编写GroupTest -> unit

let testFunc (groupTest:GroupTest) =
  [ [1; 2] ]
  |> List.choose groupTest.Invoke
  |> List.map (fun l -> l |> List.map (fun i -> "a"))
  |> List.choose groupTest.Invoke
  |> printfn "%A"

testFunc { new GroupTest with member x.Invoke a = pairTest a }

这样做的语法有点笨拙,因此,只有对我的域而言必不可少的情况下,我才使用此技巧。如果仅在一个地方需要此功能,则最好使用简单的技巧,例如复制参数(或testFunc为每个不同的函数复制整个函数groupTest)。但是界面技巧起作用了,这是完成你所要询问的一种通用方法。

let testFunc groupTest1 groupTest2 =
  [ [1; 2] ]
  |> List.choose groupTest1
  |> List.map (fun l -> l |> List.map (fun i -> "a"))
  |> List.choose groupTest2
  |> printfn "%A"

testFunc pairTest pairTest