F#Deedle访问一行

Fag*_*ain 6 f# f#-data deedle

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

假设我有这个简单的框架

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"
Run Code Online (Sandbox Code Playgroud)

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

我试过这个

getRow "Joe" people;;
Run Code Online (Sandbox Code Playgroud)

 

由于错误System.Exception而停止:由于早期错误值限制,无法完成操作.值'it'被推断为具有泛型类型val:Series将'it'定义为一个简单的数据项,使其成为具有显式参数的函数,或者,如果您不打算将其作为泛型,则添加一个类型注解.在3,0

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

val it :
   ('a -> Frame<'a,'b> -> Series<'b,'c>) when 'a : equality and 'b : equality
Run Code Online (Sandbox Code Playgroud)

Ole*_*nov 7

我会非常简短,将我的评论推荐给答案.您需要使用与您尝试过的语法相反的语法: people.Rows.["Joe"]


rmu*_*unn 7

我会回答你问题的后半部分,为什么你有一个"价值限制"错误.如果您[f#] value restriction在Stack Overflow上搜索,您会找到许多答案,这些答案可能会或可能不会让您感到困惑.但真正的简短版本是:F#建立在.Net框架之上,.Net强加了某些限制.具体而言,允许函数是通用的,但值不能是通用的.所以你可以这样做:

let f<'TData> (a:'TData) = printfn "%A" a
Run Code Online (Sandbox Code Playgroud)

但你不能这样做:

let (a:'TData) = Unchecked.defaultof<'TData>
Run Code Online (Sandbox Code Playgroud)

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

(注意:我明确地<'TData>在f定义中写了这个,但我没有必要:我本来可以写的,let f (a:'TData) = printfn "%A" a并且f仍然可以理解通用性.我甚至可以写完let f a = printfn "%A" a,它会做同样的事情) .

现在让我们看看你得到的错误:"值"它被推断为具有泛型类型val it : Series<string,obj>".如果你查看getRow你发布的函数签名,它看起来像这样:

('a -> Frame<'a,'b> -> Series<'b,'c>)
Run Code Online (Sandbox Code Playgroud)

当你把它称为时getRow "Joe" people,F#编译器能够推断出类型'a是string(因为参数"Joe"是a string).因为第二个参数people是a Frame<string,string>,F#编译器能够推断出类型'b也是如此string.但是该函数调用的结果是a Series<'b,'c>,到目前为止,F#编译器并不知道'c将会是什么.因为你getRow "Joe" people在F#interactive REPL上运行,它试图存储你输入的结果作为名称的值it(F#interactive REPL总是提供前一个表达式的值it) - 但是因为它知道的唯一类型到目前为止Series<string,'c>,F#无法确定要赋值的具体类型it.我知道从你的代码中看到类型'c是Person记录,但是F#编译器无法从那一次调用中知道getRow,因为getRow函数是如何输入的.

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

  1. 解决这个问题的一种方法是将结果传递给getRow另一个函数,这将允许F#编译器推断其结果的特定类型.不幸的是,由于我不太了解Deedle,我不能在这里给你一个很好的例子.也许其他人会拿出一个并对这个答案发表评论,然后我会编辑它.它看起来像:

    getRow "Joe" people |> (some Deedle function)
    
    Run Code Online (Sandbox Code Playgroud)

    但我不知道在我的例子中使用哪个Deedle函数:它必须是一个函数,它采用a Series并对其进行一些特定的计算,其方式允许F#推断这是一个Series<string,Person>.对不起,这不是一个很好的例子,但无论如何我会留下它以防万一.

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

    getRow "Joe" people : Series<string,Person>
    
    Run Code Online (Sandbox Code Playgroud)

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

    getRow "Joe" people : Series<_,Person>
    
    Run Code Online (Sandbox Code Playgroud)

    当你写_一个类型签名时,你告诉F#编译器"你弄清楚这是什么类型".这仅在F#编译器具有足够的信息来正确推断该类型时才有效,但是当类型签名很大且难以处理时,它通常是一种方便的简写.

这两种方法都可以解决您的问题,摆脱"价值限制"错误,并让您继续工作.

我希望这个答案可以帮到你.如果它无可救药地让你感到困惑,请告诉我,我会看看我是否可以解释你困惑的事情.

编辑:在评论中,Soldalma询问F#编译器(这是一个从上到下和从左到右工作的单程编译器)是否可以从正向管道推断出类型.答案是肯定的,因为表达还没有完成.只要表达式没有完成,F#的类型推断(基于Hindley-Milner类型系统*)可以很好地携带一组尚未解析的类型.如果在表达式完成之前解析了类型,则表达式可以解析为特定值(或特定函数).如果类型不是当表达式是完全没有解决,那么它必须解析到一个通用的值或功能.并且泛型函数在.Net中是允许的,但不是通用值,因此是"值限制"错误.

为了在实践中看到这一点,让我们看一些示例代码.将以下代码复制并粘贴到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
Run Code Online (Sandbox Code Playgroud)

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

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

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

但在接下来的两行中,表达式在编译器解析时尚未完成mkDict "foo",因此它不必"锁定"未知类型.它可以很愉快地将未解析的类型'V带入表达式的下一部分.还有,该getValueOrDefault功能有两种特定类型的,string并string在第一行,string并int在第二行.因为它的'b类型对应于'Vfrom类型mkDict,因此F#可以'V在两行中解析.所以bar有型string,并five有型int.

*Scott Wlaschin说它应该"更准确地......被称为Damas-Milner的算法W".由于我自己没有详细研究过,我会接受他的话 - 但如果你有兴趣了解更多,我提供的维基百科链接可能是一个不错的起点.