1

假设以下代码:

type Large = { v1: int; v2: int }
type Small = { v1: int }

let fn (value: Small) = printfn "%d" value.v1
// or
let fn (value: {v1: int}) = printfn "%d" value.v1

let large = { v1 = 5; v2 = 6 }
fn large

Small基本上是一个子记录Largefn large引发错误。有什么“好”的方法可以让它发挥作用吗?

背景:

通常情况下,您有大量数据。您希望将该数据作为参数传递给用户提供的未知函数。您唯一知道的是该函数将需要一部分数据。但是您不知道数据的哪一部分,也不知道Small类型。你只知道Large类型。一种选择是以large记录的形式将所有数据发送到未知函数。但我认为,如果该函数只使用其中的一小部分,它fn就不应该接受数据。数据只会使Large函数更难阅读、理解和单元测试。在我看来,该功能应该只接受它需要的东西而不接受其他任何东西,但在前面我不知道哪个部分LargefnfnLarge键入所需的fn功能。

4

4 回答 4

2

接口可能更适合您在更新的问题中描述的内容:

type Small =
    abstract v1 : int
type Large =
    inherit Small
    abstract v2 : int

let fn (value: Small) = printfn "%d" value.v1

let large = 
    { new Large with
        member small.v1 = 5
        member large.v2 = 6 }

fn large

有了接口,Large类型现在可以由一种或多种Small类型组成。函数只能期望Small它们需要的类型,调用者可以简单地将Large类型传递给每个函数。

根据场景,您还可以考虑使用Large实现一个或多个Small接口的记录类型。

type Small = abstract v1 : int
type Large = { v1: int; v2: int } with
    interface Small with member this.v1 = this.v1

let fn (value: Small) = printfn "%d" value.v1

let large = { v1 = 5; v2 = 6 }

fn large

另一种选择是为所有可能的Small项目定义一个联合,并定义Large为一个列表:

type Item =
    | V1 of int
    | V2 of int

type Large = Item list

let (|Small|) (large:Large) = 
    large |> List.pick (function V1(v1) -> Some(v1)  | _ -> None)

let fn (Small value) = printfn "%d" value

let large = [V1(5);V2(6)]    

fn large

活动模式可用于Small从数据中提取类型。这种方案的一个例子是FIX 协议,它由小的标记数据块组成大的金融数据块。

于 2013-04-15T14:17:17.040 回答
1

如果你真的事先不知道 Large fn 的哪一部分会用到,那你就必须全力以赴。这是唯一语义正确的选择。

现在您可能会感到困惑,因为您觉得 fn 并不需要所有这些,因为fn 是一种函数,而您只是缺少为该类函数命名的名称。

一个你给这个类命名,你可以将 Large 减少到它需要访问的部分,或者在一个通用的部分中打破 fn ,另一个与你的类型相关联的函数类,比如

type Large = { v1: int; v2: int }
   with x.AdaptToPrintable() =  x.v1.ToString()

fn x = printfn "%A" x

PS:如果您的问题在于语义或指定值的操作方面,我想我会感到困惑。如果是后者,那么菲尔的答案似乎是最好的..

于 2013-04-15T20:23:24.653 回答
1

基于以下几点:

F# 记录的奇怪行为

重要的是要意识到 F# 不使用结构类型(这意味着您不能将具有更多字段的记录用作获取具有较少字段的记录的函数的参数)。这可能是一个有用的特性,但它不适合 .NET 类型系统。这基本上意味着你不能期望太花哨的东西 - 参数必须是众所周知的命名记录类型的记录。

尼古拉斯:

如果你真的事先不知道 Large fn 的哪一部分会用到,那你就必须全力以赴。这是唯一语义正确的选择。

我提出了两种可能的解决方案:

解决方案 1

我基本上将用户功能一分为二。好处是reducedPostprocessFn它只接受它需要的东西。因此,此功能易于推理和单元测试。postprocessFn太短了,看它的作用也很简单。我发现这个解决方案类似于Phillip Trelford. 我想知道主动模式的优势是什么?

(* simulation *)

type Large = {v1: int; v2: int}

let simulation postprocessFn =
  let large = {v1 = 1; v2 = 2}
  postprocessFn large

(* user *)

let reducedPostprocessFn (v1: int) =
  printfn "%d" v1

let postprocessFn (large: Large) =
  reducedPostprocessFn large.v1

simulation postprocessFn

解决方案 2

此解决方案使用鸭子类型,但是:

http://msdn.microsoft.com/en-us/library/dd233203.aspx

显式成员约束:……不适合通用。

F# 和鸭式打字

我只记得这不适用于记录类型。从技术上讲,它们的成员是字段,尽管您可以使用 with member ... 与成员一起修改它们。

所以我使用了一个普通的类而不是记录。现在我只使用一个函数而不是两个函数,但老实说,在 F# 中输入鸭子实在是太难看了。

(* simulation *)

type Large(v1, v2) =
  member o.v1 = v1
  member o.v2 = v2

let simulation postprocessFn =
  let large = Large(1, 2)
  postprocessFn large

(* user 2 *)

let inline postprocessFn small =
  let v = (^a: (member v1: int) small)
  printfn "%d" v

simulation postprocessFn
于 2013-04-16T07:27:49.540 回答
1

大数据只会使 fn 函数更难阅读、理解和单元测试。

我不确定我是否正确解决了问题。但如果主要问题是关于可读性和可测试性,那么也许只使用转换函数

let forgetful (large : Large) = ({v1=large.v1} : Small)

然后写你的功能

let fn (value: Small) = printfn "%d" value.v1

它是可读/可测试的,并通过规定得到期望函数 f

let f x = fn (forgetful x)

不再需要测试了..

于 2013-04-16T08:03:44.723 回答