F# — рекурсия с анонимными записями

Учитывая следующий фрагмент кода 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.

Каким будет правильный синтаксис для анонимной записи (если она существует)?

Учебная записка [Medium] Leetcode#22 Generate Parentheses
Учебная записка [Medium] Leetcode#22 Generate Parentheses
На этот раз мы собираемся решить еще одну классическую проблему, связанную с парными скобками, так называемую генерацию скобок.
2
0
108
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Не будет правильного синтаксиса, это просто невозможно.

Подумайте о том, каким будет возвращаемый тип f2. Поскольку это запись с полем Children, представляющим собой список одной и той же записи, она будет выглядеть примерно так:

{|
  F1: string
  Children: list<
    {|
      F1: string
      Children: list<
        {|
          F1: string
          Children: list<
            ...
          >
        |}
      >
    |}
  >
|}

Это бесконечный тип. Такого зверя не существует.

Вопрос: а Фёдор, очевидно элемент списка Children это точно такая же запись, не может быть просто так?

Ну, нет, нельзя сказать «точно такая же пластинка», потому что у нее нет названия. Компилятор будет выглядеть как "какая запись? точно так же, как что?"

С другой стороны, когда вы объявляете именованную запись, вы можете использовать ту же самую запись в качестве элемента списка, потому что теперь сама запись является «вещью». У него есть собственная идентичность, которая отделена от его содержания, поэтому на него можно ссылаться отдельно от него.

Я думаю, что сообщение об ошибке могло бы быть более ясным.

ну, я думаю, это немного вводит в заблуждение, если сказать, что нет типа, удовлетворяющего этому? есть рекурсивный тип, который удовлетворяет этому, но поскольку он анонимный, у вас нет синтаксиса для его написания.

MrD at KookerellaLtd 01.04.2022 18:30

Не в системе типов F#

Fyodor Soikin 01.04.2022 19:05

Я не думаю, что есть способ сделать анонимную запись рекурсивной. Проблема в том, что компилятор не может определить возвращаемый тип 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 с большим количеством шагов: номинальный конструктор отделен от остального типа.

Fyodor Soikin 02.04.2022 15:38

C<D> является анонимным и рекурсивным

MrD at KookerellaLtd 02.04.2022 19:51

вы можете видеть его анонимным, потому что значение построено с использованием синтаксиса анонимного типа {|...|} и явно рекурсивно

MrD at KookerellaLtd 02.04.2022 19:52
D не является анонимным.
Fyodor Soikin 02.04.2022 23:08

f2 возвращает не D, а C<D>. Строка также не анонимна, поэтому я не вижу актуальности. C<D> является анонимным и рекурсивным, а возвращаемый тип f2... и почти такое же определение функции, которое написал OP. Так что я надеюсь, что это может заинтересовать его.

MrD at KookerellaLtd 02.04.2022 23:46

Вы не видите смысла? Вы решаете проблему рекурсии точно так же, как и в B — вставляя конструктор данных между рекурсивными итерациями. Так что ничего нового по сравнению с B.

Fyodor Soikin 03.04.2022 03:02

Извините, я не знаю. B не является анонимным, C<D> является. Вопрос касается анонимных рекурсивных типов, поэтому, если я остановлюсь на B, я действительно не ответил на вопрос.

MrD at KookerellaLtd 03.04.2022 09:04

вопрос был "Каким будет правильный синтаксис для анонимной записи (если она существует)?" вы объяснили, почему его нет, учитывая определение f2 в ОП, я просто показал, что есть одно с небольшим изменением.

MrD at KookerellaLtd 03.04.2022 10:58

Я с Федором. C<D> явно не анонимный, потому что у него есть имя: C<D>.

Brian Berns 04.04.2022 01:18

FSI говорит, что это анонимно. Он построен с синтаксисом анонимной записи. Тот факт, что его анонимность не означает, что он не может ссылаться на именованные типы, иначе он не был бы очень полезен.

MrD at KookerellaLtd 04.04.2022 13:00

Я даже объявил его прямо сейчас как анонимный

MrD at KookerellaLtd 04.04.2022 18:26

Другие вопросы по теме