В то время как или Хвостовая рекурсия в F#, что использовать когда?

Я - крупный сторонник подхода Интегратора, который является действительно ролью, которую я должен был выполнить для приложения наших успешных усилий WPF.

Laurent Bugnion имеет сообщение на этом, которое описывает то, о чем я говорю. Robby Ingebretsen является также крупным сторонником этого подхода.

, Но в основном, кто-то должен покрыть 'разрыв', который существует между миром разработчика и миром разработчика. То, что обычно происходит, - то, что этот человек происходит или из мира разработчика или из мира разработчика. Если они происходят из мира разработчика, то они - вероятно, разработчик с тенденциями разработчика (они ответственны за стиль, зрительный ряд в приложении, расположении экранов, и т.д.). Если они происходят из мира разработчика, то они не боятся кода и обладать дайвинга вниз время от времени кодировать для получения той анимации или безотносительно сверкающий.

Однако независимо от того, из какого мира они происходят, они обычно должны создавать навыки, которые они никогда не имели прежде. В моем случае я - разработчик, который любит слой пользовательского интерфейса, и поэтому я сказал бы, что я - разработчик с тенденциями разработчика. Чтобы покрыть тот разрыв и иметь продуктивные переговоры с нашим графическим разработчиком, я должен был взять целый набор навыков типа разработчика как: учась использовать Дизайн Выражения, 3D XAM, и т.д.

Shannon Braun недавно дал представление на локальной конференции разработчика об отношениях разработчика/разработчика и рабочих процессах, что сообщество обнаруживает работы для них. Я не присутствовал на конференции, но я думал его , слайды были большим обсуждением вопроса.

16
задан Peter 25 November 2009 в 14:54
поделиться

4 ответа

Лучший ответ - «ни то, ни другое». :)

И с циклами while, и с хвостовой рекурсией связано некоторое уродство.

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

Хвостовая рекурсия часто имеет недостаток, заключающийся в том, что требуется дополнительный параметр-аккумулятор или стиль передачи продолжения. Это загромождает программу дополнительным шаблоном для массажа условий запуска функции.

Лучший ответ - не использовать ни циклы while, ни рекурсию. Здесь вам на помощь придут функции высшего порядка и лямбды, особенно карты и свертки. Зачем валять дурака с беспорядочными структурами управления для циклов, если вы можете инкапсулировать их в повторно используемые библиотеки, а затем просто и декларативно изложить суть своих вычислений?

Если вы привыкли часто вызывать map / fold вместо использования циклов / рекурсии, а также предоставления функции сворачивания вместе с любым новым типом данных с древовидной структурой, который вы вводите, вы далеко пойдете. :)

Для тех, кто хочет узнать больше о складках в F #, почему бы не проверить мои первые три блога сообщений из серии по этой теме?

а также предоставляя функцию сворачивания вместе с любым новым типом данных с древовидной структурой, который вы вводите, вы далеко уйдете. :)

Для тех, кто хочет узнать больше о складках в F #, почему бы не проверить мои первые три блога сообщения из серии по этой теме?

а также предоставляя функцию сворачивания вместе с любым новым типом данных с древовидной структурой, который вы вводите, вы далеко уйдете. :)

Для тех, кто хочет узнать больше о складках в F #, почему бы не проверить мои первые три блога сообщения из серии по этой теме?

29
ответ дан 30 November 2019 в 15:17
поделиться

В порядке предпочтения и общего стиля программирования я напишу следующий код:

Отображение / сворачивание, если оно доступно

let x = [1 .. 10] |> List.map ((*) 2)

Это просто удобно и легко использовать.

Неконтролируемая рекурсивная функция

> let rec map f = function
    | x::xs -> f x::map f xs
    | [] -> [];;

val map : ('a -> 'b) -> 'a list -> 'b list

> [1 .. 10] |> map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

Большинство алгоритмов легче всего читать и выражать без хвостовой рекурсии. Это особенно хорошо работает, когда вам не нужно слишком глубоко рекурсировать, что делает его подходящим для многих алгоритмов сортировки и большинства операций со сбалансированными структурами данных.

Помните, log 2 (1 000 000 000 000 000) ~ = 50, так что операция log (n) без хвостовой рекурсии совсем не страшна.

Хвостовая рекурсия с аккумулятором

> let rev l =
    let rec loop acc = function
        | [] -> acc
        | x::xs -> loop (x::acc) xs
    loop [] l

let map f l =
    let rec loop acc = function
        | [] -> rev acc
        | x::xs -> loop (f x::acc) xs
    loop [] l;;

val rev : 'a list -> 'a list
val map : ('a -> 'b) -> 'a list -> 'b list

> [1 .. 10] |> map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

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

Хвостовая рекурсия с передачей продолжения

> let rec map cont f = function
    | [] -> cont []
    | x::xs -> map (fun l -> cont <| f x::l) f xs;;

val map : ('a list -> 'b) -> ('c -> 'a) -> 'c list -> 'b

> [1 .. 10] |> map id ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

Когда я вижу подобный код, я говорю себе «вот это ловкий трюк!». Ценой удобочитаемости он поддерживает форму нерекурсивной функции и нашел его действительно интересным для хвостовых рекурсивных вставок в бинарные деревья .

Вероятно, здесь говорит моя монадофобия, или возможно, мне присуще незнание Lisp call / cc, но я думаю, что случаи, когда CSP действительно упрощает алгоритмы, немногочисленны и редки. Контрпримеры приветствуются в комментариях.

While loops / for loops

Мне пришло в голову, что, Помимо понимания последовательностей, я никогда не использовал циклы while или for в моем коде F #. В любом случае ...

> let map f l =
    let l' = ref l
    let acc = ref []
    while not <| List.isEmpty !l' do
        acc := (!l' |> List.hd |> f)::!acc
        l' := !l' |> List.tl
    !acc |> List.rev;;

val map : ('a -> 'b) -> 'a list -> 'b list

> [1 .. 10] |> map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

Это практически пародия на императивное программирование. Возможно, вам удастся немного сохранить здравомыслие, объявив вместо этого let mutable l '= l , но для любой нетривиальной функции потребуется использование ref .

21
ответ дан 30 November 2019 в 15:17
поделиться

для задач, которые не являются естественно рекурсивными .. в любом случае просто преобразует его в цикл while

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

2
ответ дан 30 November 2019 в 15:17
поделиться

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

В общем, я бы использовал функциональную технику везде, где это возможно, на функциональном языке - циклы никогда не работают, поскольку они полагаются исключительно на побочные эффекты. Таким образом, при работе с императивным кодом или алгоритмами использование циклов является адекватным, но в функциональном контексте они не считаются очень хорошими.

Функциональный метод означает не только рекурсию, но также и использование подходящего высшего порядка. функции.

Таким образом, при суммировании списка ни цикл for, ни рекурсивная функция, а свертка не являются решением для получения понятного кода без изобретения колеса.

5
ответ дан 30 November 2019 в 15:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: