2017-03-07 53 views
6

這是一個基本的問題,但我無法找到答案很簡單閱讀教程F#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" 

如何訪問值的行喬? (作爲一個記錄,元組或任何格式)

我想這

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 
+2

你有沒有嘗試過反向語法? 'people.Rows。[「喬」]'? –

+0

是的,它的工作原理非常感謝 –

回答

6

我來回答你的問題的後半部分,爲什麼會得到一個「值約束」的錯誤。如果您在Stack Overflow上搜索[f#] value restriction,您會發現很多答案,這些答案可能會或可能不會讓您感到困惑。但是真正的簡短版本是:F#建立在.Net框架的基礎之上,.Net強加了某些限制。具體而言,函數被允許爲通用的,但不能是通用的。所以,你可以這樣做:

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

,但你不能做到這一點:

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

函數定義是好的,因爲.NET Framework的基礎知道如何處理的通用功能。但是,您不允許在.Net中使用通用;任何值必須是特定的類型。

(注:我在f定義中明確寫了<'TData>,但我沒得:我剛纔寫let f (a:'TData) = printfn "%A" af的一般性詞語會被仍然理解我連剛纔寫let f a = printfn "%A" a,它會做同樣的事情)。

現在讓我們看看你得到的錯誤:「值」it「已被推斷爲具有通用類型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#無法弄清楚什麼具體的類型分配給值it。通過查看代碼,我知道類型'cPerson記錄,但由於輸入getRow函數的方式,F#編譯器無法從那一次調用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#編譯器「您可以確定它是什麼類型」。這僅適用於F#編譯器有足夠信息正確推斷該類型的信息,但當類型簽名很大且笨拙時,它通常是方便的簡寫。

這兩種方法會解決立即解決問題,擺脫了「價值限制」的錯誤,並允許你繼續工作。

我希望這個答案能幫助你。如果它絕望地讓你困惑,讓我知道,我會看看我能不能解釋你困惑的事情。

編輯:在註釋中,Soldalma詢問F#編譯器(這是一種從上到下和從左到右工作的單通編譯器)是否可以從前向管道推斷出該類型。答案是肯定的,因爲表達還沒有完成。只要表達式沒有完成,F#的類型推斷(基於the Hindley-Milner type system *)就可以處理一組尚未解析的類型。如果在表達式完成之前解析類型,則表達式可以解析爲特定值(或特定函數)。如果類型是而不是在表達式完成時尚未解析,則它必須解析爲通用值或函數。在.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 

繼續前進,將光標懸停在每個函數和變量名稱可以看到它的類型,否則按下Alt + Enter鍵,每個函數或變量聲明發送到F#互動。 (一旦你看到let d行給出了「值限制」錯誤,請註釋掉,以便編譯其餘的代碼)。

這裏發生的一切都很好地展示了這一切如何運作。 mkDict函數有兩個未解決的類型,'K'V,所以它必須是通用的。但這很好,因爲.Net對通用函數沒有問題。 (mkDict實際上並不是非常有用的有用的,因爲它實際上「拋棄」了它的論點的數據,並且對它沒有任何作用,但它應該是一個簡單的例子,所以只是忽略它是無用的。同樣,getValueOrDefault有兩個未解決的類型,'a'b,所以它也是一個通用函數。

但是,let d = mkDict "foo"而不是合法。這裏,通用類型'K已被解析爲特定類型string,但'V在表達式完成時尚未解析,因此d必須是通用的(它看起來像顯式通用語法中的d<'V>)。但d不是函數(因爲它沒有參數),它的名稱爲,而.Net不允許使用通用值。

但是在接下來的兩行中,表達式在編譯器已經解析mkDict "foo"時沒有完成,所以它還沒有「鎖定」未知類型。它可以非常愉快地將未解決的類型'V帶入表達式的下一部分。在那裏,getValueOrDefault函數有兩個特定類型,第一行爲stringstring,第二行爲stringint。因爲它的'b類型對應'V類型從mkDict,因此F#可以在兩行中解析'V。所以barstringfiveint

* Scott Wlaschin says它應該「更準確地說......被稱爲」Damas-Milner算法W「」。由於我沒有詳細研究過自己,我會聽取他的意見 - 但如果您有興趣瞭解更多信息,我提供的維基百科鏈接可能是一個不錯的起點。

+0

沒有它不迷惑,現在它絕對清楚,非常感謝你花時間寫這個長答案 –

+0

傑出的答案。我從中學到了很多東西。但是我有一個關於你第一個建議的解決問題的方法的問題。據我瞭解,F#編譯器從高到低和從左到右工作,就像讀書的人一樣。如果你做'getRow'Joe'people |>(一些Deedle函數)'編譯器能夠「向後」工作來推斷'people'的類型嗎? – Soldalma

+0

是的,它可以推斷出類型。我現在正在使用我的手機,所以我不能寫出一個好例子,但是當我回到電腦時,我會在回答中添加一個。 – rmunn

7

我會很短,推薦我的評論給出答案。 您需要使用的語法反向你已經嘗試了一句: people.Rows.["Joe"]

+1

非常感謝你給我快速的實際答案,但是我認爲你會同意rmunn爲他的長期努力配上「積分」。 –