給定以下 F# 代碼段:
type A(children: A list) =
member val P1 = ""
member val P2 = ""
member val Children = children
type B = {
F1:string
Children: B list
}
let rec f1 (a:A) =
{
F1 = a.P1
Children = a.Children |> List.map f1
}
let rec f2 (a:A) =
{|
F1 = a.P1
Children = a.Children |> List.map f2 //Error
|}
let a = A([A([A([])])])
f1 a
f2 a
////////////////////
Error FS0001 Type mismatch. Expecting a
'A -> 'a'
but given a
'A -> {| Children: 'a list; F1: string |}'
The types ''a' and '{| Children: 'a list; F1: string |}' cannot be unified.Active
編譯器在 f2 中抱怨,但在 f1 中沒有。
匿名記錄的正確語法是什么(如果存在)?
uj5u.com熱心網友回復:
不會有正確的語法,這是不可能的。
想想 . 的回傳型別是什么f2。由于它的欄位Children是同一記錄的串列,因此它看起來像這樣:
{|
F1: string
Children: list<
{|
F1: string
Children: list<
{|
F1: string
Children: list<
...
>
|}
>
|}
>
|}
是無限型別。這樣的野獸是不存在的。
Q:但是Fyodor,很明顯串列的元素Children是完全相同的記錄,不能就這樣嗎?
嗯,不,沒有辦法說“完全相同的記錄”,因為它沒有名字。編譯器會像“什么記錄?和什么完全一樣? ”
另一方面,當您宣告一個命名記錄時,您可以使用與串列元素完全相同的記錄,因為現在記錄本身就是一個“事物”。它有自己的標識,與它的內容是分開的,因此可以與它分開參考。
我認為錯誤訊息可能會更清楚。
uj5u.com熱心網友回復:
我認為沒有任何方法可以制作遞回的匿名記錄。問題是編譯器無法推斷 的回傳型別f2,因此無法推斷匿名記錄Children欄位的型別。作為人類,我們可以看到使型別遞回可以解決問題,但編譯器不知道這一點。
uj5u.com熱心網友回復:
因此,雖然我同意 Fyodor 的回答,但我認為值得深入挖掘。
所以讓我們讓它更明確一點
這是您撰寫的有效代碼
type B = {
F1:string
Children: B list
}
let rec f1 (a:A) : B =
{
F1 = a.P1
Children = a.Children |> List.map f1
}
然后,您嘗試使用匿名記錄來構造等效問題,但讓我們明確地這樣做,并使用型別別名告訴編譯器我們期望的型別并查看它在哪里失敗
type C = {| F1: string; Children: C list |}
let rec f2 (a:A) : C =
{|
F1 = a.P1
Children = a.Children |> List.map f2 //Error
|}
所以這里的錯誤是關于型別別名
Severity Code Description Project File Line Suppression State
Error FS0953 This type definition involves an immediate cyclic reference through an abbreviation F# Miscellaneous Files
由于 Fyodor 解釋的原因,這種型別是不允許的,實際上編譯器會嘗試通過用型別別名定義替換內部 C 來評估這種型別別名,顯然這永遠不會終止。(這種情況在其他但不是所有語言中很常見)。這個問題不僅限于匿名記錄,還包括任何遞回型別別名,例如
type Foo = Foo -> Foo
正常的方法是給型別一個明確的名稱,因此通常將內部運算式包裝在資料建構式中(注意這現在不再是型別別名)。
type C = C of {| F1: string; Children: C list |}
let rec f2 (a:A) : C =
{|
F1 = a.P1
Children = a.Children |> List.map f2
|}
|> C
而且這個型別基本上等價于顯式命名的型別B。
Brian 的斷言可能沒有辦法使匿名型別遞回雖然不是很正確,但只要遞回定義中有一些命名型別,我們就可以做到這一點,例如
type C<'a> = {| F1: string; Children: 'a list |}
type D = D of C<D>
let rec f2 (a:A) : C<D> =
{|
F1 = a.P1
Children = a.Children |> List.map (f2 >> D)
|}
我們的 f2 函式現在是根據遞回匿名記錄定義的,但是我們必須將命名型別注入到遞回中(因此我們必須在 f2 函式中使用資料建構式 D),但問題仍然存在是沒有滿足你原始函式定義的匿名(有限)型別,不是說有,而是編譯器找不到。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/455052.html
下一篇:如何按2個條件過濾串列?
