F# Deedle 访问一行

F# Deedle accessing a row

这是一个基本问题,但我无法通过阅读教程找到简单的答案

假设我有这个简单的框架

type Person = 
  { Name:string; Age:int; Countries:string list; }

let peopleRecds = 
  [ { Name = "Joe"; Age = 51; Countries = [ "UK"; "US"; "UK"] }
    { Name = "Tomas"; Age = 28; Countries = [ "CZ"; "UK"; "US"; "CZ" ] }
    { Name = "Eve"; Age = 2; Countries = [ "FR" ] }
    { Name = "Suzanne"; Age = 15; Countries = [ "US" ] } ]

// Turn the list of records into data frame 
let peopleList = Frame.ofRecords peopleRecds
// Use the 'Name' column as a key (of type string)
let people = peopleList |> Frame.indexRowsString "Name"

我如何访问 Joe 的行的值? (作为记录、元组或任何格式)

我试过了

getRow "Joe" people;;

Stopped due to error  System.Exception: Operation could not be completed due to earlier error  Value restriction. The value 'it' has been inferred to have generic type   val it : Series  Either define 'it' as a simple data term, make it a function with explicit arguments or, if you do not intend for it to be generic, add a type annotation. at 3,0

编辑:感谢您的回答,我仍然想知道为什么我的语法不正确,因为我认为我尊重签名

val it :
   ('a -> Frame<'a,'b> -> Series<'b,'c>) when 'a : equality and 'b : equality

我会很简短,将我的评论提升为答案。 您需要使用与您尝试过的语法相反的语法: people.Rows.["Joe"]

我会回答你问题的后半部分,为什么你会得到 "value restriction" 错误。如果您在 Stack Overflow 上搜索 [f#] value restriction,您会找到很多答案,这些答案可能会让您感到困惑,也可能不会。但真正简短的版本是:F# 构建在 .Net 框架之上,而 .Net 施加了某些限制。具体来说,functions 允许泛型,但 values 不能泛型。所以你可以这样做:

let f<'TData> (a:'TData) = printfn "%A" a

但是你不能这样做:

let (a:'TData) = Unchecked.defaultof<'TData>

函数定义很好,因为底层 .Net 框架知道如何处理泛型函数。但是您不允许在 .Net 中使用通用的 values;任何值都必须是 特定 类型。

(注意:我在 f 定义中明确地写了 <'TData>,但我不必这样做:我可以只写 let f (a:'TData) = printfn "%A" af 仍然会被理解。我什至可以只写 let f a = printfn "%A" a,它也会做同样的事情)。

现在让我们看看您遇到的错误:"the value "它“已被推断为具有通用类型 val it : Series<string,obj>”。如果您查看您发布的 getRow 的函数签名,它看起来像这样:

('a -> Frame<'a,'b> -> Series<'b,'c>)

当您将其称为 getRow "Joe" people 时,F# 编译器能够推断类型 'astring(因为参数 "Joe" 是一个 string).因为第二个参数 peopleFrame<string,string>,F# 编译器能够推断类型 'b 也是 string。但是该函数调用的结果是 Series<'b,'c>,到目前为止,F# 编译器对 'c 将是什么一无所知。并且由于您在 F# 交互式 REPL 中 运行 getRow "Joe" people,它尝试将您键入的结果存储为名称 it 的值(F# 交互式 REPL 始终提供值之前的表达式为 it) -- 但由于到目前为止它知道的唯一类型是 Series<string,'c>,F# 无法弄清楚要分配给什么 specific 类型值 it。通过查看您的代码,我知道类型 'cPerson 记录,但是 F# 编译器无法仅通过对 getRow 的那个调用就知道这一点,因为 getRow函数被键入。

有两种方法可以解决此值限制错误:

  1. 解决此问题的一种方法是将 getRow 的结果通过管道传输到另一个函数,这将允许 F# 编译器推断 特定的 结果的类型。不幸的是,因为我不太了解 Deedle,所以我不能在这里给你一个很好的例子。也许其他人会想出一个并对这个答案发表评论,我会对其进行编辑。它看起来像:

    getRow "Joe" people |> (some Deedle function)
    

    但我不知道在我的例子中使用哪个 Deedle 函数:它必须是一个接受 Series 并用它做一些特定计算的函数,以允许 F# 的方式推断这是一个Series<string,Person>。抱歉,这不是一个很好的例子,但无论如何我都会保留它以防它有帮助。

  2. 解决该错误的第二种方法是指定您获取的值的类型。在 F# 中,您可以使用 : (type) 语法来执行此操作,例如:

    getRow "Joe" people : Series<string,Person>
    

    或者,由于 F# 编译器有足够的信息来推断该类型的 string 部分,您还可以编写:

    getRow "Joe" people : Series<_,Person>
    

    当您在类型签名中写入 _ 时,您是在告诉 F# 编译器 "You figure out what type this is"。这仅在 F# 编译器有足够的信息来正确推断该类型时才有效,但当类型签名大而笨拙时,它通常很方便 shorthand。

这两种方法都可以解决您眼前的问题,消除 "value restriction" 错误,并让您继续工作。

希望这个回答对您有所帮助。如果它让您感到困惑,请告诉我,我会看看是否可以解释您感到困惑的地方。

编辑: 在评论中,Soldalma 询问 F# 编译器(这是一个从上到下、从左到右工作的单通道编译器)是否可以从中推断出类型前向管道。答案是肯定的,因为表达式还没有完成。只要表达式未完成,F# 的类型推断(基于 the Hindley-Milner type system*)就可以携带一组尚未解析的类型。如果在表达式完成之前解析类型,则表达式可以解析为特定值(或特定函数)。如果表达式完成后类型 not 尚未解析,则它必须解析为 generic 值或函数。 .Net 中允许通用函数,但不允许通用值,因此出现 "value restriction" 错误。

为了在实践中看到这一点,让我们看一些示例代码。将以下代码复制并粘贴到 F# 编辑器中,您可以将鼠标悬停在变量(或函数)名称上以查看其类型。我推荐带有 Ionide-fsharp 扩展的 VS Code,因为它是跨平台的,但 Visual Studio 也能正常工作。

open System.Collections.Generic

let mkDict (key:'K) = new Dictionary<'K,'V>() // Legal

let getValueOrDefault (key:'a) (defaultVal:'b) (dict:Dictionary<'a,'b>) =
    match dict.TryGetValue key with
    | true,v -> v
    | false,_ -> defaultVal

let d = mkDict "foo" // Error: value restriction
let bar = mkDict "foo" |> getValueOrDefault "foo" "bar" // Legal: type string
let five = mkDict "foo" |> getValueOrDefault "foo" 5 // Legal: type int

继续将光标悬停在每个函数和变量名称上以查看其类型,或者按 Alt+Enter 将每个函数或变量声明发送到 F# Interactive。 (一旦您看到 let d 行出现 "value restriction" 错误,请将其注释掉,以便其余代码能够编译)。

这里发生的事情很好地展示了这一切是如何运作的。 mkDict 函数有两个未解析的类型,'K'V,因此它必须是通用的。但这很好,因为 .Net 对泛型函数没有问题。 (mkDict 实际上并不是非常 有用 ,因为它实际上 "throws away" 其参数的数据并且对它没有任何作用。但这应该是一个微不足道的例子,所以忽略它有点无用的事实。)同样,getValueOrDefault 有两个未解析的类型,'a'b,所以它也是一个通用函数。

但是,let d = mkDict "foo"合法。在这里,通用类型 'K 已被解析为特定类型 string,但是 'V 在表达式完成时尚未解析,因此 d 会是通用的(在显式通用语法中它看起来像 d<'V>)。但是 d 不是函数(因为它没有参数),它是 的名称,并且 .Net 不允许通用值。

但是在接下来的两行中,当编译器解析 mkDict "foo" 时,表达式还没有完成,所以它还不需要 "lock in" 未知类型。它可以非常愉快地将未解析的类型 'V 带入表达式的下一部分。在那里,getValueOrDefault 函数有两个特定类型,第一行的 stringstring,第二行的 stringint。因为它的 'b 类型对应于 mkDict 中的 'V 类型,所以 F# 可以解析两行中的 'V。因此 bar 的类型为 string,而 five 的类型为 int.

* Scott Wlaschin says 它应该 "more accurately ... be called "Damas-Milner 的算法 W" "。由于我自己还没有详细研究过它,所以我相信他的话——但如果您有兴趣了解更多信息,我提供的维基百科 link 可能是一个不错的起点。