Учитывая следующий фрагмент кода 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.
Каким будет правильный синтаксис для анонимной записи (если она существует)?
Не будет правильного синтаксиса, это просто невозможно.
Подумайте о том, каким будет возвращаемый тип f2
. Поскольку это запись с полем Children
, представляющим собой список одной и той же записи, она будет выглядеть примерно так:
{|
F1: string
Children: list<
{|
F1: string
Children: list<
{|
F1: string
Children: list<
...
>
|}
>
|}
>
|}
Это бесконечный тип. Такого зверя не существует.
Вопрос: а Фёдор, очевидно элемент списка Children
это точно такая же запись, не может быть просто так?
Ну, нет, нельзя сказать «точно такая же пластинка», потому что у нее нет названия. Компилятор будет выглядеть как "какая запись? точно так же, как что?"
С другой стороны, когда вы объявляете именованную запись, вы можете использовать ту же самую запись в качестве элемента списка, потому что теперь сама запись является «вещью». У него есть собственная идентичность, которая отделена от его содержания, поэтому на него можно ссылаться отдельно от него.
Я думаю, что сообщение об ошибке могло бы быть более ясным.
Не в системе типов F#
Я не думаю, что есть способ сделать анонимную запись рекурсивной. Проблема в том, что компилятор не может определить возвращаемый тип f2
и, следовательно, не может определить тип поля Children
анонимной записи. Как люди, мы можем видеть, что создание рекурсивного типа решит проблему, но компилятор этого не знает.
Я согласен с ответом Федора и думаю, что было бы интересно копнуть немного глубже.
поэтому давайте сделаем это немного более явным
вот код, который вы написали, который работает
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 |} //Error is here
let rec f2 (a:A) : C =
{|
F1 = a.P1
Children = a.Children |> List.map f2
|}
поэтому ошибка здесь в псевдониме типа
Severity Code Description Project File Line Suppression State
Error FS0953 This type definition involves an immediate cyclic reference through an abbreviation F# Miscellaneous Files
этот тип не разрешен по причине, которую объяснил Федор, фактически компилятор попытается оценить этот псевдоним типа, заменив внутренний 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.
Утверждение Брайана о том, что не может быть способа сделать анонимный тип рекурсивным, хотя и не совсем верно, мы можем сделать это, если в рекурсивном определении есть некоторый именованный тип, например.
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 теперь определена в терминах рекурсивной анонимной записи, но нам пришлось внедрить именованный тип в рекурсию (и поэтому мы должны использовать конструктор данных D в нашей функции f2), проблема заключается в том, что существует не является анонимным (конечным) типом, который удовлетворяет вашему исходному определению функции, не то, чтобы он был, но компилятор не может его найти.
кажется, неясно, что касается статуса C
если мы напечатаем это
let x : C<D> = {| F1 = ""; Children=[] |}
x.GetType();;
в FSI он ответил
val it: System.Type =
<>f__AnonymousType3901454931`2[Microsoft.FSharp.Collections.FSharpList`1[FSI_0002+D],System.String]....
т. е. C является анонимным типом типа {| F1: строка; Дети: список D |}
мы можем, конечно, полностью отказаться от этого псевдонима, например.
type D = D of {| F1: string; Children: D list |}
let rec f2 (a:A) : {| F1: string; Children: D list |} =
{|
F1 = a.P1
Children = a.Children |> List.map (f2 >> D)
|}
let x = f2 <| A([A([A([])])])
В последнем примере у вас нет анонимного рекурсивного типа. Анонимный тип C<'a>
технически не является рекурсивным, потому что он не упоминает себя, а рекурсивный тип D
не является анонимным. То, что вы сделали, это в основном тип B
с большим количеством шагов: номинальный конструктор отделен от остального типа.
C<D> является анонимным и рекурсивным
вы можете видеть его анонимным, потому что значение построено с использованием синтаксиса анонимного типа {|...|} и явно рекурсивно
D
не является анонимным.
f2 возвращает не D, а C<D>. Строка также не анонимна, поэтому я не вижу актуальности. C<D> является анонимным и рекурсивным, а возвращаемый тип f2... и почти такое же определение функции, которое написал OP. Так что я надеюсь, что это может заинтересовать его.
Вы не видите смысла? Вы решаете проблему рекурсии точно так же, как и в B
— вставляя конструктор данных между рекурсивными итерациями. Так что ничего нового по сравнению с B
.
Извините, я не знаю. B не является анонимным, C<D> является. Вопрос касается анонимных рекурсивных типов, поэтому, если я остановлюсь на B, я действительно не ответил на вопрос.
вопрос был "Каким будет правильный синтаксис для анонимной записи (если она существует)?" вы объяснили, почему его нет, учитывая определение f2 в ОП, я просто показал, что есть одно с небольшим изменением.
Я с Федором. C<D>
явно не анонимный, потому что у него есть имя: C<D>
.
FSI говорит, что это анонимно. Он построен с синтаксисом анонимной записи. Тот факт, что его анонимность не означает, что он не может ссылаться на именованные типы, иначе он не был бы очень полезен.
Я даже объявил его прямо сейчас как анонимный
ну, я думаю, это немного вводит в заблуждение, если сказать, что нет типа, удовлетворяющего этому? есть рекурсивный тип, который удовлетворяет этому, но поскольку он анонимный, у вас нет синтаксиса для его написания.