2015-11-24 3 views
3

У меня есть две аналогичные функции nested и nestedCurried, которые пересекают дерево XML с XLinq. Они не делают ничего полезного - это просто «сжатый» отрывок из более сложного кода.F # XLinq traversal - curried version of functions throws StackOverflowException

То, что я бы ожидать от этих двух функций вести себя таким же образом, как и для меня это выглядит как они идентичны, с той лишь разницей, в nestedCurried не явно объявлять e: XElement аргумент - это curied за счет использования elements из функции и функции композиции >>

Между тем, функция nestedCurried бросает StackOverflowException при вызове на любом XElement

оценена в FSI:

#r "System.Xml.Linq" 
open System.Xml.Linq 

let inline elements (e: XElement) = e.Elements() |> Seq.toList 

let rec nested() e = elements e |> List.collect (nested()) 
let rec nestedCurried() = elements >> List.collect (nestedCurried()) 

let x = XDocument.Parse """<a></a>""" 

let ok : XElement list = nested() (x.Root) 
// Stack Overflow below 
let boom : XElement list = nestedCurried() (x.Root) 

Почему возникает StackOverflowException, какова техническая разница между этими двумя функциями, и как я могу объявить функцию nested без указания аргумента XElement явно?

ответ

2

Посмотрите: каждый раз, когда вы звоните nestedCurried, вы вызываете nestedCurried снова, сразу же, безоговорочно.

Чтобы сделать вещи более ясными, считайте, что выражение List.collect f эквивалентно let x = f; List.collect x. Это означает, что ваше определение nestedCurried эквивалентно следующему:

let nestedCurried() = 
    let x = nestedCurried() 
    elements >> List.collect x 

Является ли это яснее, почему это приведет к бесконечной рекурсии?

+0

ok Я думаю, что знаю, моя первоначальная мысль заключалась в том, что эти две функции эквивалентны поведению - но они не являются , Правильно ли я понимаю, что если бы F # оценивалась лениво, то исключение не произошло бы? – theimowski

+1

Да, это правильно. –

1

Ваш() параметр не нужен и путает вещи. Вы частично применили элементы с X.Root, так что вы вызываете nestedCurried с X.Root снова и снова - отсюда переполнение стека. Для того, чтобы объявить вложенной без указания аргумента явно вы можете сделать:

let nested = 
    let rec inner e = elements e |> List.collect (inner) 
    inner 

Если вы объявили nestedCurried в

let rec nestedCurried = elements >> List.collect (nestedCurried) 

Вы бы получили ошибку компилятора, что «nestedCurried оценивается как часть своего собственного определения» ,

+0

Параметр() ('unit') означает, что какой-либо аргумент может быть передан функции - чтобы упростить задачу, я разделил тип этого аргумента на единицу. Не уверен, что я понимаю, почему 'nestedCurried' вызывается с X.Root снова и снова - не следует ли его вызывать с дочерними элементами? Почему «вложенная» функция не работает в этом случае? – theimowski

+0

Вы уже передаете аргумент функции - e. В вашем определении вы передаете() и e.() Ничего не делает в этом случае, кроме помощи, которую вы объявляете функцией, которая вызывает переполнение стека :) – Kevin