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" a
和 f
仍然会被理解。我什至可以只写 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# 编译器能够推断类型 'a
为 string
(因为参数 "Joe"
是一个 string
).因为第二个参数 people
是 Frame<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
。通过查看您的代码,我知道类型 'c
是 Person
记录,但是 F# 编译器无法仅通过对 getRow
的那个调用就知道这一点,因为 getRow
函数被键入。
有两种方法可以解决此值限制错误:
解决此问题的一种方法是将 getRow
的结果通过管道传输到另一个函数,这将允许 F# 编译器推断 特定的 结果的类型。不幸的是,因为我不太了解 Deedle,所以我不能在这里给你一个很好的例子。也许其他人会想出一个并对这个答案发表评论,我会对其进行编辑。它看起来像:
getRow "Joe" people |> (some Deedle function)
但我不知道在我的例子中使用哪个 Deedle 函数:它必须是一个接受 Series
并用它做一些特定计算的函数,以允许 F# 的方式推断这是一个Series<string,Person>
。抱歉,这不是一个很好的例子,但无论如何我都会保留它以防它有帮助。
解决该错误的第二种方法是指定您获取的值的类型。在 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
函数有两个特定类型,第一行的 string
和 string
,第二行的 string
和 int
。因为它的 'b
类型对应于 mkDict
中的 'V
类型,所以 F# 可以解析两行中的 'V
。因此 bar
的类型为 string
,而 five
的类型为 int
.
* Scott Wlaschin says 它应该 "more accurately ... be called "Damas-Milner 的算法 W" "。由于我自己还没有详细研究过它,所以我相信他的话——但如果您有兴趣了解更多信息,我提供的维基百科 link 可能是一个不错的起点。
这是一个基本问题,但我无法通过阅读教程找到简单的答案
假设我有这个简单的框架
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" a
和 f
仍然会被理解。我什至可以只写 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# 编译器能够推断类型 'a
为 string
(因为参数 "Joe"
是一个 string
).因为第二个参数 people
是 Frame<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
。通过查看您的代码,我知道类型 'c
是 Person
记录,但是 F# 编译器无法仅通过对 getRow
的那个调用就知道这一点,因为 getRow
函数被键入。
有两种方法可以解决此值限制错误:
解决此问题的一种方法是将
getRow
的结果通过管道传输到另一个函数,这将允许 F# 编译器推断 特定的 结果的类型。不幸的是,因为我不太了解 Deedle,所以我不能在这里给你一个很好的例子。也许其他人会想出一个并对这个答案发表评论,我会对其进行编辑。它看起来像:getRow "Joe" people |> (some Deedle function)
但我不知道在我的例子中使用哪个 Deedle 函数:它必须是一个接受
Series
并用它做一些特定计算的函数,以允许 F# 的方式推断这是一个Series<string,Person>
。抱歉,这不是一个很好的例子,但无论如何我都会保留它以防它有帮助。解决该错误的第二种方法是指定您获取的值的类型。在 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
函数有两个特定类型,第一行的 string
和 string
,第二行的 string
和 int
。因为它的 'b
类型对应于 mkDict
中的 'V
类型,所以 F# 可以解析两行中的 'V
。因此 bar
的类型为 string
,而 five
的类型为 int
.
* Scott Wlaschin says 它应该 "more accurately ... be called "Damas-Milner 的算法 W" "。由于我自己还没有详细研究过它,所以我相信他的话——但如果您有兴趣了解更多信息,我提供的维基百科 link 可能是一个不错的起点。