这是一个基本问题,但我找不到阅读教程的简单答案
假设我有这个简单的框架
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)
我会回答你问题的后半部分,为什么你有一个"价值限制"错误.如果您[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函数是如何输入的.
有两种方法可以解决此值限制错误:
解决这个问题的一种方法是将结果传递给getRow另一个函数,这将允许F#编译器推断其结果的特定类型.不幸的是,由于我不太了解Deedle,我不能在这里给你一个很好的例子.也许其他人会拿出一个并对这个答案发表评论,然后我会编辑它.它看起来像:
getRow "Joe" people |> (some Deedle function)
Run Code Online (Sandbox Code Playgroud)
但我不知道在我的例子中使用哪个Deedle函数:它必须是一个函数,它采用a Series并对其进行一些特定的计算,其方式允许F#推断这是一个Series<string,Person>.对不起,这不是一个很好的例子,但无论如何我会留下它以防万一.
你可以解决错误的第二种方法是指定你得到的值的类型.在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".由于我自己没有详细研究过,我会接受他的话 - 但如果你有兴趣了解更多,我提供的维基百科链接可能是一个不错的起点.