Это хорошее упражнение для того, чтобы стать более свободно говорящим на том языке программирования, который вы хотели выучить, но с которым легко повозились. Это включает в себя работу с объектами, использование или моделирование замыканий и растяжение системы типов.
Ваша задача - написать код для управления отложенными списками, а затем использовать его для реализации этого алгоритма генерации чисел Фибоначчи:
Примеры кода в Хаскеле
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
in take 40 fibs
Результат:
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]
Ваша реализация отложенного списка должна соответствовать этим рекомендациям:
- Узел List - это одна из трех вещей:
- Ноль - пустой список.
[]
- Минусы - один элемент в сочетании со списком оставшихся элементов:
1 : [2,3,4,5]
(:
является оператором cons в Haskell) - Thunk - отложенное вычисление, которое при необходимости создает узел List.
- Ноль - пустой список.
- Поддерживаются следующие операции:
- nil - создать пустой список
- минусы - построить против клетки.
- thunk - Создайте Thunk, учитывая функцию, которая не принимает аргументов и возвращает Nil или Cons.
- force - задан узел List:
- Если это ноль или минусы, просто верните его.
- Если это Thunk, вызовите его функцию, чтобы получить ноль или минусы. Замените громоотвод с этим ноль или минусы и верните его.
Примечание. Замена thunk его принудительным значением является важной частью определения «ленивый» . Если этот шаг пропущен, приведенный выше алгоритм Фибоначчи будет слишком медленным.
- empty - посмотреть, является ли узел List нулем (после его форсирования).
- head (он же «машина») - Получить первый элемент списка (или бросить приступ, если это ноль).
- tail (он же "cdr") - получает элементы после заголовка списка (или подбрасывает, если это Nil).
- zipWith - учитывая двоичную функцию (например
(+)
) и два (возможно, бесконечные) списка, примените функцию к соответствующим элементам списков. Пример:
zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
- take - Учитывая число N и (возможно, бесконечный) список, возьмите первые N элементов списка.
- печать - печать всех элементов в списке. Это должно работать постепенно, когда дан длинный или бесконечный список.
fibs
использует себя в своем собственном определении. Настроить ленивую рекурсию немного сложно; вам нужно сделать что-то вроде этого:- Выделите громоотвод для
fibs
. Оставьте это в фиктивном состоянии пока. - Определите функцию thunk, которая зависит от ссылки на
fibs
. - Обновите Thunk с его функцией.
Возможно, вы захотите скрыть это, определив функцию,
fix
которая вызывает функцию, возвращающую список, со своим собственным возвращаемым значением. Подумайте о том, чтобы немного вздремнуть, чтобы эта идея начала развиваться- Выделите громоотвод для
Полиморфизм (способность работать со списками элементов любого типа) не требуется, но посмотрите, можете ли вы найти способ сделать это, что идиоматично для вашего языка.
- Не беспокойтесь об управлении памятью. Даже языки со сборкой мусора имеют тенденцию переносить объекты, которые вы никогда больше не будете использовать (например, в стеке вызовов), поэтому не удивляйтесь, если ваша программа утечет память при обходе бесконечного списка.
Не стесняйтесь слегка отклониться от этих рекомендаций, чтобы учесть особенности вашего языка или изучить альтернативные подходы к ленивым спискам.
Правила:
- Выберите язык, который вы плохо знаете. Я не могу «требовать» этого, отсюда и тэг «honor-system». Тем не менее, избиратели могут проверить вашу историю, чтобы увидеть, на каких языках вы публиковали.
Не используйте встроенную поддержку ленивых списков вашего языка, чтобы делать все. Опубликовать что-то существенное или хотя бы интересное.
Хаскелл в значительной степени отсутствует. То есть, если вы не сделаете что-то вроде этого:
data List a = IORef (ListNode a) data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
Примечание: нестрогая оценка Haskell не является запретной, но ваша реализация отложенного списка не должна получать свои возможности непосредственно оттуда. На самом деле, было бы интересно увидеть эффективное, чисто функциональное решение, которое не требует лени.
Python:
- Не используйте itertools.
- Генераторы в порядке, но вы используете их, вам придется найти способ запоминать принудительные значения.
источник
zipWith
двух списков разной длины?zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]
. Однако это не имеет значения для алгоритма Фибоначчи, приведенного выше, поскольку оба аргумента zipWith представляют собой бесконечные списки.fibs
правильной реализации , так как это зависит от самого себя. Я обновил вопрос, чтобы развить ленивую рекурсию. FUZxxl понял это сам.Ответы:
PostScript
Я играл с PostScript раньше , но я бы не сказал, что знаю его особенно хорошо (на самом деле, я предполагаю, что вы можете посчитать количество людей в мире, которые действительно знают PostScript, используя одну руку).
Я отклонился от вашей спецификации в том, что функция, используемая для создания thunk, может возвращать другой thunk;
force
продолжит оценку, пока результат не будет anil
или acons
.Списки реализованы в виде словарей:
Код следует. Обратите внимание, что мы перезаписываем некоторые встроенные операторы (в частности
print
, я не проверял, есть ли еще); в реальном мире за этим нужно следить. Конечно, реального использования не будет, так что все в порядке.Комментарии до процедуры следует читать как
т.е. показ ожидаемого содержимого стека до вызова и результирующего содержимого стека после вызова. Комментарии в процедурах показывают содержимое стека после выполнения конкретной строки.
Загрузите это в Ghostscript, игнорируя отображаемую страницу - мы работаем только с переводчиком. Вот алгоритм Фибоначчи:
Две дополнительные интересные функции:
Начните считать с 5, умножьте каждый элемент полученного списка на 3 и отобразите первые десять значений:
Что касается полиморфизма: даже несмотря на то, что PostScript строго типизирован, он допускает произвольные типы в качестве значений словаря, поэтому вы можете добавить что угодно:
Обратите внимание, что ошибки типа, например, при попытке добавить строки к числам, будут возникать только во время оценки:
источник
force
запоминает возвращенные значения?copy
оператор копирует содержимое оцененной версии в оригинал, перезаписывая/type
и, возможно, устанавливая другие значения. После того, как рекурсивный оценки , пока мы не будем иметьnil
илиcons
, это также (черезundef
) удаляет/func
и, где это применимо,/data
. Последний шаг не является строго необходимым (/func
и/data
будет просто проигнорирован), но выход из этого шага приведет к утечке памяти еще больше :)С
Я абсолютный новичок в C, этот код на самом деле является первой реальной вещью, которую я написал в C. Он компилируется без каких-либо предупреждений и прекрасно работает в моей системе.
Как построить
Во-первых, получите архив с моего сервера . Он включает в себя make-файл, поэтому просто запустите его
make
для сборки, а затемmake run
запустите. Затем программа печатает список первых 93 чисел Фибоначчи. (После номера 94 64-разрядное целое число без знака переполняется)объяснение
Ядром программы является файл
lazy-list.c
. В соответствующем заголовочном файле я определяю структуруlist
, то есть наш ленивый список. Это выглядит так:Член
kind
является своего рода тегом. Он помечает, перезаписали ли мы списки end (NIL
), ячейку, которая уже оценена (CONS
) или thunk (THUNK
). Затем следует объединение. этоСодержимое объединения утверждается тегом. Если тег есть
NIL
, содержимое объединения не определено.Определяя вспомогательные функции, упомянутые в спецификации выше, обычно можно абстрагировать определение списков от их использования, например. Вы можете просто позвонить,
nil()
чтобы получить пустой список, а не создавать его самостоятельно.Три наиболее интересные функции
zipWith
,take
иfibonaccis
. Но я не хочу объяснятьtake
, так как это очень похоже наzipWith
. Все функции, которые работают лениво, имеют три компонента:В случае
zipWith
, этоzipWith
,__zipWith
и__zipArgs
. Я просто показываю их здесь без каких-либо дополнительных объяснений, там должна быть понятна функция:Другая интересная функция
fibonaccis()
. Проблема заключается в том, что нам нужно передать указатель первой и второй ячейки на блок третьей, но для создания этих ячеек нам также нужен указатель на блок. Чтобы решить эту проблему, я заполнил указатель на thunkNULL
первым и изменил его на thunk после его создания. Вот прослушивание:Возможные улучшения
content_t
, который можно изменить на любой подходящий.источник
void*
как типcontent_t
.void*
тоже думал об использовании , но думал, что это обойдет систему типов слишком далеко. Разве это не возможно с помощью шаблонов?void*
и друзей.kind
является своего рода тегом». Вы могли бы просто назвать егоtag
, поскольку это довольно приемлемый термин для концепции (например , теговое объединение , G-машина без тегов Spineless . С другой стороны, «вид» имеет другое значение в . Haskell контекст: тип типаInt
имеет вид*
,[]
имеет вид* -> *
, и(,)
имеет вид* -> * -> *
.C ++
Это самая большая вещь, которую я когда-либо писал на C ++. Я обычно использую Objective-C.
Это полиморфно, но никогда ничего не освобождает.
Моя
main
функция (иadd
функцияZipWith
) в итоге выглядела так:Это дает
Классы работают так:
Полный источник: здесь . Это беспорядок, в основном потому, что он в одном большом файле.
Изменить: изменил ссылку (старая была мертва).
источник
()
оператор) и использовать наследование, чтобы избежать необходимости использованияvoid*
. Смотрите здесь для тривиального примера этого.питон
Не использует генераторы для реализации списка, просто для реализации
__iter__
метода для использования сfor
.Список Фибоначчи создается так:
источник
self.__class__ = node.__class__
. Обратите внимание, что это вызывает исключение NotImplemented, когда оно достигает 2971215073 (long), что, очевидно, является недопустимым аргументом для int .__ add__. Чтобы поддержать большие целые числа, сделайтеfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Рубин
Моя первая программа на Ruby. Мы представляем все узлы как массивы, где длина массива определяет тип:
Код тогда довольно прост, с хаком для сброса функции thunk для настройки рекурсивного фиба.
источник
[...]
вместоArray[...]
.Google Go
Относительно новый язык, и я выучил его,
CTRL+F
используя Spec .Проблема была решена, имея дело с thunk-in-thunks. Тем не менее, кажется, что онлайн-компилятор не может взять 40 элементов, возможно, из-за памяти. Я проверю это на моем Linux позже.
Я протестировал код с онлайн-компилятором , потому что не могу легко установить Go на Windows.
источник
iota
генератором констант. См. Пример в спецификации языка программирования Go и ответ на StackOverflow .Fibs
функция не работает, потому что Go использует строгую оценку иFibs
использует саму себя без завершающего условия.Fibs0
/Fibs1
использует простой генераторный подход, а не алгоритм, описанный в моем посте, поэтому он не соответствует «требованиям». Я обновил свой пост, чтобы подробно описать ленивую рекурсию, которую необходимо реализоватьfibs = 0 : 1 : zipWith (+) fibs (tail fibs)
.Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))))
, уходит из памятиCons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))
и я получаю неверный адрес памятикристалл
Несмотря на то, что я следовал GitHub-репозиторию, я до сих пор никогда не использовал Crystal. Crystal - это статически типизированный вариант Ruby с полным выводом типа. Несмотря на то, что ответ Ruby уже есть, статическая типизация Crystal привела меня к использованию полиморфизма, а не массива, для представления узлов. Поскольку Crystal не разрешает модификацию
self
, я создал класс-оболочку с именемNode
, который будет оборачивать все остальное и управлять thunks.Наряду с классами, я создал функцию конструкторы
lnil
,cons
иthunk
. Раньше я никогда не использовал Ruby более чем для 20-строчного скрипта, так что блочные вещи меня немного отбросили.Я основал эту
fib
функцию на ответе Go .источник
Я немного изменил правила, потому что здесь еще нет решения .NET - или, в более общем смысле, решения ООП, за исключением решения в Python, которое использует наследование, но оно достаточно отличается от моего решения, чтобы сделать оба интересными (в частности, начиная с Python позволяет модифицировать
self
экземпляр, делая реализацию thunk простой).Так что это C # . Полное раскрытие: я далеко не новичок в C #, но я давно не касался этого языка, так как в настоящее время я не пользуюсь им на работе.
Существенные моменты:
Все классы (
Nil
,Cons
,Thunk
) вытекают из общего абстрактного базового класса,List
.Thunk
Класс использует Envelope-Letter шаблон. По сути, это эмулируетself.__class__ = node.__class__
присваивание в исходном коде Python, посколькуthis
ссылка не может быть изменена в C #.IsEmpty
,Head
ИTail
являются свойствами.Все соответствующие функции реализуются рекурсивно и лениво (за исключением того
Print
, что не может быть ленивым), возвращая thunks. Например, этоNil<T>.ZipWith
:... и это
Cons<T>.ZipWith
:К сожалению, C # не имеет многократной отправки, иначе я мог бы также избавиться от
if
утверждения. Увы, без кубиков.Теперь я не очень доволен своей реализацией. Я до сих пор счастлив, потому что все вышеперечисленное совершенно просто. Но . Я чувствую, что определение
Fib
излишне сложно, так как мне нужно обернуть аргументы в thunks, чтобы заставить его работать:(Здесь
List.Cons
,List.Thunk
иList.ZipWith
это удобство обертки.)Я хотел бы понять, почему следующее намного более простое определение не работает:
учитывая соответствующее определение
Concat
, конечно. По сути, это то, что делает код Python, но он не работает (= подбрасывает)./ РЕДАКТИРОВАТЬ: Джои указал на очевидный недостаток в этом решении. Тем не менее, замена второй строки с thunk также приводит к ошибке (Mono segfaults; я подозреваю, переполнение стека, которое Mono плохо обрабатывает):
Полный исходный код можно найти в GistHub .
источник
fib.ZipWith
иfib.Tail
использовать староеfib
, которое остается[0,1]
и не меняется. Таким образом, вы получаете[0,1,1]
(я думаю), и вашаTake
функция не позволяет вам брать из нуля ( хотя Haskell принимает , но). Попробуйте обернуть значение второй строки в thunk, чтобы оно ссылалось на новое,fib
а не на старое.Pico
для справки , это решение использует перевод силы задержки схемы, как определено в srfi-45 . и строит ленивые списки поверх этого.
Результат выглядит следующим образом : (но , в зависимости от того, как
tpico
. пропатчена может иметь более двойные кавычки в немdisplay
. обычно печатает строки в кавычках , т.е. все выступления[
,,
,]
будет иметь кавычки вокруг них , как"["
.)из-за ограничений целочисленного типа данных в tpico это не удается при вычислении 45-го (или 46-го смещения) числа Фибоначчи.
обратите внимание, что tpico 2.0pl11 не работает в этом
begin(a,b)
(что обычно пишется как{a;b}
), иif
функция не является хвостовой рекурсивной. не говоря уже о том, что мне понадобилось 5 лет, чтобы понять, почемуbegin
хвост не был рекурсивным. также в то время я написал перевод srfi-45 в Пико. Оказалось, чтоbegin
ожидание значенияb
перед возвратом, когда не нужно было ждать. и как только я понял, что я тоже смог исправить, уif
него была та же проблема. и была другая ошибка, из-за которой не работал конструктор метауровняmake
.Pico позволяет функции контролировать, оцениваются ли ее аргументы перед вызовом функции или просто упакованы как thunks. для этого кода я могу сделать многоточие на странностях вызова по функции .
Пико не имеет вывода типа. Я думал об этом некоторое время, но столкнулся с проблемой из-за странностей вызова по функции . я пришел с утверждением, что типы должны кодировать существование имен связанных переменных . но я в основном думал о том, как адаптировать вывод типа Хиндли-Милнера к подмножеству Пико без мутации. Основная идея заключалась в том, что средство проверки типов возвращает несколько возможных схем, если существует более одной возможной привязки, и проверка типа завершается успешно, если существует хотя бы одна возможная схема типов . Возможная схема - это то, что нет конфликта типов.
источник