我來回答你的問題的後半部分,爲什麼會得到一個「值約束」的錯誤。如果您在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" a
和f
的一般性詞語會被仍然理解我連剛纔寫let f a = printfn "%A" a
,它會做同樣的事情)。
現在讓我們看看你得到的錯誤:「值」it「已被推斷爲具有通用類型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#無法弄清楚什麼具體的類型分配給值it
。通過查看代碼,我知道類型'c
是Person
記錄,但由於輸入getRow
函數的方式,F#編譯器無法從那一次調用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#編譯器「您可以確定它是什麼類型」。這僅適用於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
函數有兩個特定類型,第一行爲string
和string
,第二行爲string
和int
。因爲它的'b
類型對應'V
類型從mkDict
,因此F#可以在兩行中解析'V
。所以bar
有string
和five
有int
。
* Scott Wlaschin says它應該「更準確地說......被稱爲」Damas-Milner算法W「」。由於我沒有詳細研究過自己,我會聽取他的意見 - 但如果您有興趣瞭解更多信息,我提供的維基百科鏈接可能是一個不錯的起點。
你有沒有嘗試過反向語法? 'people.Rows。[「喬」]'? –
是的,它的工作原理非常感謝 –