Как создать значение рекурсивной структуры данных в (функциональном) F#?

Как может значение типа:

type Tree =
    | Node of int * Tree list

есть значение, которое ссылается на себя, сгенерированное функциональным способом?

Результирующее значение должно быть равно x в следующем коде Python для подходящего определения дерева:

x = Tree()
x.tlist = [x]

Правка: Очевидно, необходимы дополнительные пояснения. Я пытаюсь изучить F# и функциональное программирование, поэтому я решил реализовать дерево покрытия, которое я раньше программировали на других языках. Здесь важно то, что точки каждого уровня являются подмножеством точек нижнего уровня. Структура концептуально выходит на уровень-бесконечность.

В императивных языках у узла есть список дочерних элементов, в который входит и он сам. Я знаю, что это можно сделать императивно в F#. И нет, это не создает бесконечный цикл, учитывая алгоритм дерева покрытия.


person Muhammad Alkarouri    schedule 21.06.2010    source источник
comment
Можете ли вы расширить вопрос для тех из нас, кто не очень хорошо говорит на Python? Ваше определение типа выглядит нормально, но обратите внимание, что F # не позволяет напрямую создавать тип Discriminated Union (например, Tree)... только создание значений объединения; например, Node, как в let x = Node( 0, [] ) или пусть y = Node( 1, [x] ), где [] — пустой список.   -  person James Hugard    schedule 21.06.2010
comment
@James: он хочет создать узел, который является его собственным дочерним элементом. Как let rec x = Node (something, [x]) (за исключением того, что это не работает).   -  person sepp2k    schedule 21.06.2010
comment
@sepp2k: обычно это создает бесконечный цикл для любого кода, идущего по списку, хотя ... крайне нежелательно :-) Я видел, как это делается в других языках, чтобы отметить конец списка, но idomatic F # будет использовать другой DU значение, такое как None, для этой цели.   -  person James Hugard    schedule 21.06.2010
comment
Вместо того, чтобы пытаться писать код Python с синтаксисом F#, что точно вы пытаетесь сделать? Как бы то ни было, чисто функциональные, циклические структуры данных — это интеллектуальный швиц (см. ), лучше писать код с нециклическими структурами данных.   -  person Juliet    schedule 22.06.2010
comment
@ sepp2k: Спасибо, я именно это и имею в виду. @Juliet: Спасибо за ссылку. См. отредактированный вопрос выше.   -  person Muhammad Alkarouri    schedule 22.06.2010
comment
@James: Рекурсивные значения, подобные этому, разрешены в OCaml и действительно используются, например. среды рекурсивного закрытия в интерпретаторах.   -  person J D    schedule 06.08.2010


Ответы (2)


Ответ Томаса предлагает два возможных способа создания рекурсивных структур данных в F#. Третья возможность — воспользоваться тем фактом, что поля записи поддерживают прямую рекурсию (при использовании в той же сборке, в которой определена запись). Например, следующий код работает без проблем:

type 'a lst = Nil | NonEmpty of 'a nelst
and 'a nelst = { head : 'a; tail : 'a lst }

let rec infList = NonEmpty { head = 1; tail = infList }

Используя этот тип списка вместо встроенного, мы можем заставить ваш код работать:

type Tree = Node of int * Tree lst
let rec x = Node(1, NonEmpty { head = x; tail = Nil })
person kvb    schedule 21.06.2010
comment
Интересно. Концептуально, будет ли определение «lst» в каком-то смысле эквивалентно ленивому списку? - person Muhammad Alkarouri; 22.06.2010
comment
@Muhammad - Нет, 'a lst не ленив, так что это похоже на встроенный список. Тот факт, что он проходит через запись, позволяет вам создавать самореферентные структуры данных, но вы не можете использовать этот тип, чтобы сделать что-то вроде создания потока всех четных чисел, как вы могли бы с ленивым списком. - person kvb; 22.06.2010
comment
Спасибо. Это то, что я ищу. - person Muhammad Alkarouri; 22.06.2010

Вы не можете сделать это напрямую, если рекурсивная ссылка не задержана (например, обернута в функцию или ленивое значение). Я думаю, мотивация в том, что нет возможности создать значение с непосредственными ссылками "сразу", так что это было бы неудобно с теоретической точки зрения.

Однако F# поддерживает рекурсивные значения — вы можете использовать их, если рекурсивная ссылка задерживается (компилятор F# затем сгенерирует некоторый код, который инициализирует структуру данных и заполняет рекурсивные ссылки). Самый простой способ - обернуть ссылку внутри ленивого значения (функция тоже будет работать):

type Tree = 
    | Node of int * Lazy<Tree list>

// Note you need 'let rec' here!        
let rec t = Node(0, lazy [t; t;])

Другой вариант — написать это с помощью мутации. Затем вам также нужно сделать вашу структуру данных изменчивой. Например, вы можете сохранить ref<Tree> вместо Tree:

type Tree = 
    | Node of int * ref<Tree> list

// empty node that is used only for initializataion
let empty = Node(0, [])
// create two references that will be mutated after creation    
let a, b = ref empty, ref empty

// create a new node
let t = Node(0, [a; b])
// replace empty node with recursive reference
a := t; b := t

Как упомянул Джеймс, если вам не разрешено это делать, у вас могут быть некоторые приятные свойства, такие как то, что любая программа, которая проходит по структуре данных, завершится (поскольку структура данных ограничена и не может быть рекурсивной). Итак, вам нужно быть немного осторожнее с рекурсивными значениями :-)

person Tomas Petricek    schedule 21.06.2010
comment
Хороший ответ. Спасибо. С точки зрения эффективности один лучше другого? (Почему-то я чувствую, что пожалею об этом;) - person Muhammad Alkarouri; 22.06.2010
comment
Lazy строго сложнее, чем ref (он должен отслеживать, был ли он оценен или нет), поэтому ref должен быть более эффективным, но я подозреваю, что реальной разницы нет. - person GS - Apologise to Monica; 06.08.2010