Функциональное программирование julia

Совсем недавно я подцепил какую-то Джулию. Насколько я вижу, Джулия поддерживает функциональное программирование несколько лучше, чем Python. И все же мне интересно, есть ли веские причины для изучения “полноценных” функциональных языков. Таких как LISP. Ocaml и Haskell. Я постоянно слышу, как люди пропагандируют эти языки, но сам никогда их не изучал. Из поверхностного чтения в Интернете. Одной из многих уникальных характеристик этих “полноценных” функциональных языков является использование связанных списков в качестве структуры данных по умолчанию. В отличие от массивов в Джулии.

В Ocaml и Haskell массивы доступны только из стандартной библиотеки, а не из языка ядра. В то время как в Julia все наоборот: вы не можете использовать связанные списки. Если не используете, например. DataStructures.jl или не пишете свою собственную реализацию. В чем преимущество использования связанных списков везде, а не для особых проблем. Требующих быстрой вставки и удаления?

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

Реальные функциональные программисты могут иметь вес. Но мое ощущение консенсуса по связанным спискам заключается в том. Что никто на самом деле не думает. Что они все такие.

Я почти уверен, что они не будут возглавлять чей-либо список причин, по которым они предпочитают Haskell. OCaml или что-то еще.

Ваша точка зрения об оптимизации хвостовых вызовов гораздо ближе к цели; шаблоны проектирования в функциональном мире. Как правило. Сильно отличаются от Джулии или Python. Они обычно используют рекурсию и сопоставление шаблонов вместо циклов и условных выражений. Проблемы, связанные с древовидными структурами данных, часто особенно поддаются этому стилю; например. Pandoc написан на языке Haskell.

Мое любимое объяснение этого момента-это ответ Quora Тихона Джелвиса. Он подробно описывает конкретный пример (бесконечные квадранты). Который довольно убедительно иллюстрирует преимущества алгебраических типов данных и лени, ИМО.

У Джулии действительно есть MLStyle.jl и Lazy.jl. Поэтому при желании можно позаимствовать некоторые элементы функционального программирования. Но на самом деле это не идиоматическая Джулия. И отсутствие TCO (среди многих других вещей) не позволяет Джулии быть убедительной альтернативой программисту Haskell. Который хочет продолжать делать вещи так. Как они сделаны в Haskell.

Но это нормально: Джулия для тех, кто считает, что альтернативы не совсем подходят для них.

ИМО, TCO-это действительно классная идея, но на самом деле это ошибка. Любой код, который нуждается в TCO, может быть довольно легко написан в цикле. Так что это не такое уж большое преимущество. Кроме того, TCO полагается на уничтожение стека, когда происходят хвостовые вызовы. Это звучит хорошо, но особенно в сложных случаях значительно усложняет устранение прослушивания.

Кроме того, TCO не является дополнительной функцией. Если вы используете язык, в котором он есть. Вы никогда не можете ожидать запуска кода с отключенным кодом. Иначе вы получите переполнение стека из пакетов и стандартных библиотечных функций. Которые предполагают работу TCO.

Ну, у Хаскелла на самом деле нет петель, так что им лучше иметь TCO. :slight_smile:

По крайней мере, в Haskell списки часто используются как итераторы . А не как структура данных для хранения (см., Например. Этот поток Reddit, в частности. Этот ответ).

У меня нет опыта работы с Haskell & Co, но мне интересно. Влияет ли их чисто функциональный подход на производительность. При таком подходе невозможно изменить структуры данных (например, массивы) на месте. Что менее эффективно из-за выделения ресурсов, верно?

Я думаю, что размещение Lisp в той же когорте, что и Ocaml и Haskell, является небольшим заблуждением.

(Общий) Lisp по своей сути является динамически типизированным императивным интерпретируемым языком с функциями первого класса. Джулия во многих отношениях просто аромат Лиспа с более знакомым синтаксисом.

ML и Haskell являются статически проверяемыми на тип “реальными” функциональными языками. Что делает опыт очень разным. Связанные списки-незначительная разница, сильный тип гарантирует более заметное изменение.

Главное преимущество FP-это ощущение, что “если он компилируется, он работает” (или, по крайней мере. Он не будет внезапно падать во время выполнения). Одна из вещей, которую применяет FP, — это исчерпывающие проверки условий в определенных случаях. Другое-более формальное определение признаков через классы типов. Скажем, если вы определяете числовой тип, в идеале вы хотели бы убедиться, что у вас есть вся арифметика. Сравнения и тому подобное. Определенные для него. Джулия не проверяет это, Хаскелл проверяет.

Таким образом, основное преимущество FP заключается в том. Что система типов разработана таким образом. Чтобы позволить компилятору проверить. Проверил ли программист все граничные случаи.

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

@sswatson Спасибо за ссылку на ответ Quora Тихона Джелвиса. Некоторые примеры, такие как ленивое четырехугольное дерево. Кажется довольно трудным воспроизвести в Джулии. Я думаю, что пакет Lazy.jl реализует только ленивые списки, но не ленивые деревья. С другой стороны, я нахожу. Что довольно легко использовать Джулию для воспроизведения примера упрощения арифметического выражения. И код так же короток. Урезанная версия кода Haskell-это использование алгебраического типа данных и сопоставления с образцом,

data Expr = Var String | Lit Integer | Expr :+: Expr | Expr :*: Expr deriving (Show, Eq) simplify :: Expr -> Expr simplify expr | nextStep == expr = expr | otherwise = simplify nextStep where nextStep = case expr of Var x -> Var x Lit n -> Lit n (Lit n1 :+: Lit n2) -> Lit (n1 + n2) (Lit n1 :*: Lit n2) -> Lit (n1 * n2) (e1 :+: e2) -> simplify e1 :+: simplify e2 (e1 :*: e2) -> simplify e1 :*: simplify e2 

Мой код Джулии

abstract type Operator end Math = Union{Operator, Integer} struct Add <: operator a :: math b end struct multiply evaluate x>

Теперь если ты побежишь

formula = Multiply(2, Add(4,3)) evaluate(formula) 

Вы получите 14, как и ожидалось. Конечно, версия Haskell еще более гибкая, так как вы можете расширить код, чтобы обрабатывать 0 и 1 иначе. Чем другие числа. Как в оригинальном сообщении Quora. В то время как Julia отправляет только по типам. А не по значениям. Пожалуйста, дайте мне знать, если вы видите какие-либо другие подводные камни вышеупомянутого кода Julia!

Главное преимущество Haskell, для новичка, конечно, в том. Что он заставляет строгий функциональный стиль. Как только вы освоитесь с Haskell, как только вы закодируете приложение или библиотеку, станет очевидно. Как вы также закодируете их в F# и Clojure. У меня изначально были реальные проблемы с F# и Clojure, потому что я пытался кодировать в том же стиле. Что и C и Fortran. И это возможно. Но идет очень против зерна. После работы с Haskell они стали намного проще в использовании.

По сравнению с Джулией, Хаскелл опять-таки заставляет по-другому взглянуть на проблему. Вы начинаете думать об использовании функций map, filter, reduce и более высокого порядка. В целом вы пишете меньшие функции и проводите различие между чистыми и нечистыми функциями даже в языках, которые. В отличие от Haskell. Не навязывают это различие.

Функциональные языки склонны использовать списки из-за их неизменности. Список можно изменить, добавив элемент в копию неизменяемого списка. Проигнорировав старый список и продолжив работу с новым. Массив-это изменяемая конструкция в глубине души. И хотя функциональные языки могут обрабатывать их (особенно F# и Scala. Которые допускают изменяемые данные). Это не кажется естественным. Julia, Fortran, MatLab — все это более естественно для обработки массивов.

Циклические конструкции, такие как Некоторые функциональные языки могут справиться с этим, некоторые-нет. Рекурсия, переходящая на новое неизменяемое значение каждый раз вокруг цикла. Лучше подходит для функционального языка. Из стилей рекурсии оптимизация хвостового вызова (TCO) гарантирует, что стек не будет переполнен. После небольшой практики я нахожу. Что TCO легче рассуждать, чем более прямолинейная рекурсия. Возможно, это только я…

сравнивая Haskell (и подобные языки) с Julia, я задаюсь вопросом. Можно ли “отправить по пустому списку”
, например. Функцию map можно определить в Haskell как пару определений (ссылка):

mymap f [] = [] mymap f (x:xs) = f x : mymap f xs 

Можно ли это сделать в Джулии? то есть отделить завершение рекурсии от другого определения функции на основе того факта. Что входной список пуст?

Да, это возможно для функций на кортежах:

mymap(::Tuple{}) = () mymap(f, (x, xs...)) = (f(x), mymap(f. Xs)...) 

Невозможно сделать то же самое с изменяемыми коллекциями. Потому что тогда “пусто” — это состояние, в котором может находиться коллекция. А не значение. Которое может быть связано с определенным типом “пусто”.

Спасибо!
Вполне логично использовать кортежи. Так как функциональное программирование не изменяет свои аргументы (никаких побочных эффектов).
Я не знаю, будет ли скомпилированный код эффективен, но хорошо знать. Что синтаксис Julia поддерживает этот стиль (чистого) функционального программирования