Pull to refresh

Comments 86

Питонов, конечно, много разных, но в первую очередь производительностью.

А еще в статье, кажется, ни слова о мощнейшей встроенной математической библиотеки из коробки, а это одна из основных фич.
Производительность Питона — это очень непростая тема. Чаще всего люди, которые говорят о производительности языка, меряют её на разного рода бенчмарках, что-то вроде вычисления числа Пи или рекурсивного обхода дерева. Однако, реальные приложения работают иначе. Возьмём три области, в которых Python имеет значительный авторитет: Linux-скрипты, веб и научные вычисления.

Для скриптов производительность практически не имеет значения. Скрипт либо отрабатывает за долю секунды (и тут гораздо важнее время разогрева), либо работает долго, но упирается по производительности в автоматизируемые операции (например, компиляция проекта на C++ или копирование файла). Соответсвенно, производительность языка скриптования тут особой роли не играет.

Другое дело — веб. Тысячи пользователей, миллионы просмотров страниц, а тут Питон со своим тормознутым интерпретатором. Однако, на деле производительность веб-приложения практически всегда упирается либо в сервер приложений (например, Apache2), либо в базу данных, либо в какие-нибудь ресурсы (сеть, диски и т.д.). В интерпретаторе языка веб-запрос проводит не так уж и много времени, так что фреймворки на «медленном Питоне» вполне себе неплохо масштабируются.

Ну и, наконец, научные вычисления, где код может выполняться и час, и день, и неделю. Оптимизация даже в пару раз способна значительно облегчить исследование (а иногда даже сама является достойным исследованием). Интерпретируемый язык в такой среде обречён на провал. Тем не менее, scientific stack в Python — один из самых мощных: SciPy stats, sckit-{learn, image, sparse}, Pandas, Theano — вот только некоторые из широкого набора инструментов. При этом все тяжёлые операции выполняются на стороне хорошо продуманных библиотек (а вернее, расширений), написанных на C. Python же только управляет логикой и даёт приятный интерфейс.

Не так давно я как раз писал модуль для машинного обучения и конкретно распознавания визуальных образов. ML — наука неточная и требующая экспериментов. А один прогон эксперимента даже на небольшом объёме данных длился, ни много ни мало, 7.5 часов. Естественно, мне захотелось оптимизировать. После профилирования оказалось, что 95% времени тратится на операциях свёртки (convolution). Не то, чтобы свёртка у меня работала медленно, но уж больно много раз она выполнялась. Я перепробовал несколько библиотек из научного набора Питона (все написаны на C), но результат оставался примерно тем же. Решение нашлось в функции filter2D из библиотеки OpenCV (с обёрткой в виде питоновского модуля cv2). Как оказалось, OpenCV использует специальные инструкции процессора для параллельной обработки данных, и благодаря этому, после пары часов колдовства, эксперимент стал отрабатывать ровно за 1.5 часа, т.е. в 5 раз быстрее. И всё это не покидая интерпретатора Питона. Если Julia сможет сделать операцию свёртки в 5 раз быстрее, чем C код, то да, безусловно я сразу переключусь на неё. Однако этого, по очевидным причинам, не произойдёт.

По большому счёту, производительность Python упирается в его циклы. Циклы в интерпретаторе — это всегда долго. Но в большинстве случаев циклы прекрасно векторизуются и перекладываются на массивы NumPy (не всем это нравится, для многих циклы всё же привычней, но это уже дело вкуса). Да и PyPy понемногу допиливает свою реализацию NumPy с блекджеком и JIT-оптимизированными циклами.

В общем, не стоит сбрасывать Python со счетов просто потому что он «медленный». За этой толстенькой, на первый взгляд, змеёй стоит вся сила нативных библиотек ;)
UFO just landed and posted this here
Термин «векторизация» относится именно к математическим вычислениям, поскольку оптимизации применяются именно к операциям над векторами и матрицами. В более же общем случае подобные оптимизации называются пакетной и/или параллельной обработкой. Суть здесь одна и та же.

Например, представим, что нам нужно сложить поэлементно два вектора: a и b. Наивная реализация будет выглядеть примерно так:

import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([4, 3, 2, 1])

c = np.zeros((len(a),))
for i in range(len(a)):
    c[i] = a[i] + b[i]


Вот как это выглядит с точки зрения системы:

1. Если индекс цикла меньше максимального*, то:
2. Найти адрес i-ого элемента массива a.
3. Найти адрес i-ого элемента массива b.
4. Получить и сложить элементы массивов.
5. Найти адрес нового элемента в массиве c и записать туда результат.
6. Вернуться на шаг 1.

* — на самом деле здесь, конечно же, итератор, а не просто цикл со сравнениями, но суть та же.

При этом все операции, по сути, выполняются в интерпретаторе Python, то есть проходят весь путь от байткода Питона до машинного кода, туда и обратно, туда и обратно. Особенно прискорбно, что приходится каждый раз вычислять адрес i-ого элемента. Ведь по сути, мы знаем, что следующий элемент находится ровно в N байтах от текущего (где N — длина типа данных) и можем просто инкрементить адрес. Но вместо этого мы ныряем в Питоновский интерпретатор и проходим весь цикл поиска элемента по индексу: поиск имени переменной (hash!), поиск метода __getitem__() (hash again!) и медленный питоновский вызов найденного метода. И это с учётом того, что мы использовали наипсанную на C структуру ndarray вместо стандартного списка, в котором элементы — это вообще ссылки на отдельные объекты.

К счастью, NumPy предоставляет специальные функции для обработки векторов «целиком», а не поэлементно. Например, предыдущий цикл можно переписать так:

... 
c = a + b


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

Более того, низкоуровневая имплементация на C может использовать также и оптимизации на уровне процессора. Например, SIMD инструкции. Такие инструкции позволяют загружать сразу несколько элементов и применять к ним одну и ту же операцию в параллели. В случае сложения массивов это означает, что элементы складываются, например, не по одному, а по 4 сразу. А это и чистое ускорение в 4 раза, и уменьшение количества дорогих для процессора if-инструкций во столько же раз. Правда, я не буду утверждать, что все эти оптимизации используются во всех научных расширенях Питона, но по крайней мере реализовать это можно.

Так вот, это всё касалось векторизации, и почему она улучшает производительность. На самом деле всё то же самое можно перенести и на другие области. Например, большие массивы данных в распределённых системах часто обрабатываются пачками — данные буферизируются до определённого количества, а затем передаются одним пакетом, чтобы избежать накладных расходов по передаче каждого кусочка по отдельности. Аналогично тому, как векторизация позволяет избежать накладных расходов по вызову функций для каждого элемента. И аналогично SIMD инструкциям любую обработку можно распараллелить на несколько ядер, обрабатывая за один период времени сразу по несколько элементов данных.
Здорово, что вы подняли эту тему. Чем мне особенно нравится Julia, так это тем, что в ней, как и в python или matlab можно реализовывать вычисления сразу в векторной форме. Это просто (с точки зрения написания кода), но не всегда эффективно, если компилятор/библиотека линейной алгебры не умеет оптимизировать такие вычисления.

Пример:
r = a * k + b + c
(здесь a, b, c — массивы, а k — скаляр).

При наивной реализации компилятора/библиотеки этот код приведет к созданию 2-х временных массивов, в первый будет записан результат умножения, во второй результат сложения, ну и если особенно сильно не повезет, то добавится и третий временный массив. В настоящий момент, насколько мне известно, в python/numpy все обстоит именно таким образом. Накладные расходы на выделение памяти могут быть не столь заметны, если язык в целом довольно медленный, и мы счастливы просто от того, что может вызывать быстрые функции из BLAS/LAPACK, но после того, как все вычисления векторизованы до упора, а производительность надо поднять еще и еще, то нужно продолжать сбрасывать балласт.

И тут в julia есть замечательный механизм макросов и библиотека Devectorize, которая позволяет мне написать:
@devectorize r = a .* k + b + c (здесь .* — это поэлементное умножение, сейчас станет понятно, для чего требуется это уточнение), и для данного конкретного примера автоматически вместо данной строчки будет сгенерирован цикл:

for i in 1:length(a) r[i] = a[i] * k + b[i] + c[i] end

В данном коде не происходит создания временных объектов, а благодаря тому, что в julia циклы быстрые, почти такие же как в Си, то мы получаем еще немного (на самом деле очень много) пространства для оптимизации без нужды писать некрасивые циклы.
Согласен, штука прикольная, но всё-таки не killer feature. Если уж дошло до оптимизации выделения памяти, а доступные функции не принимают в качестве параметра выходной массив (как это сделано для многих методов OpenCV, где массивы действительно большие), то не грех и спуститься на уровень C.

Т.е., Julia, конечно, обладает отличным набором качеств, но ни одно из них не имеет достаточного веса, чтобы «сорвать куш» и вытеснить какой-то другой язык из его ниши. Например, Python всё больше вымещает Matlab, потому что 1) бесплатный и 2) хорошо интегрируется с промышленными системами. У Julia таких важных преимуществ перед другими языками я не вижу, поэтому пока что лично для меня это скорее интересный эксперимент, но не замена существующим инструментам.

Хотя посмотреть, чем этот эксперимент закончится, будет интересно. Сейчас Julia позиционируется как один язык, подходящий для всего. Последний раз больше чем на одну нишу (производительность + веб) претендовал Go, но в итоге ни одну, ни другую область он так и не завоевал.
У питона огромная проблема с параллельными вычислениями. GIL — вот это всё. Обычно физические задачи довольно легко разделяются, то есть надо вычислить f(x) для набора x, и результат f(x1) не зависит от f(x0). На сях или фортране на помощь приходит openmp и можно ускорить программу в количество ядер на проце практически бесплатно. Конечно, на питоне есть модуль multiprocessing, но это страшный костыль. Мало того что его возможности весьма ограничены, например, если где-то внутри есть внешняя lambda, то работать ничего будет, да он ещё и каждый раз интерпретирует файл и начинает тормозить уже интерпретатор. Использовать shared memory только ручками можно. В openMP для этого надо просто пометить переменную как shared.

В общем я тут сделал проект на питоне и что-то у меня такое чувство, что я хочу вернуться к плюсам. Питон даёт неплохой старт, но когда дело доходит до реального счёта получается медленно.
Я бы сказал, тут проблема глубже. А вернее, тут две отдельные проблемы: GIL в Python и поддержка OpenMP в языках кроме C++.

Насколько я знаю OpenMP (а знаю я его плохо, так что не стесняйтесь поправлять), он ближе к расширению языка, чем к библиотеке. Например, тот же NumPy можно просто импортировать как любую другую библиотеку и работать с ним, не меняя общий workflow. OpenMP же предполагает изменение семантики (как без #pragma пометить переменную как shared?) и процесса сборки (-fopenmp). Лучшее, что может предложить в таких условиях динамический языка как Python — это вынести параллельную обработку в статическое расширение. А для этого уже есть Cython. Теоретически, конечно, можно ещё круче — встроить поддержку OpenMP в JIT-компилятор, но это уже совсем нетривиальная задача.

Что же касается GIL, то да, это насущная проблема. Тут либо менять парадигму параллелизма (multiprocessing, message passing, etc.), либо опускаться ниже (опять же, Cython). К счастью, ребята из PyPy активно работают над этим вопросом и уже имеют несколько вариантов имплементации без GIL-а. Осталось дождаться, пока на одном ядре такие имплементации будут работать не сильно медленнее, чем стандартная версия :)
Да, openMP это расширение языка, но оно поддерживается как минимум gcc и llvm. С пришествием C++11 с std::future и std::thread возможно и без openmp достаточно просто писать параллельные задачи. К Cython я присматриваюсь, но как-то пока не осилил. Да и если работать с numpy, то все его массивы это по факту массивы C, их можно передать во внешнюю библиотеку без проблем.

C PyPy печаль, что за ними никто не стоит. Тот же openMP пилит Intel, за то время как я его использую (около 5 лет) они очень продвинулись. А с python3 и numpy/scipy у PyPy воз и ныне там.

Edit: openMP поддерживает как минимум C/C++ и Fortran.
Py3k на PyPy как раз уже вполне юзабелен. По крайней мере мне не удалось найти несовместимостей. С NumPy проблема идеологическая — очень долго вообще было непонятно, как его писать (подключать ли расширения из CPython, или использовать мосты к C++, или допиливать ctypes), в конечном итоге решили переписать всё по своему и сейчас борятся за прохождение юнит-тестов. В целом, NumPy — это для них одна из приоритетных задач. Но, как вы правильно заметили, за ними никто не стоит, а на одни донейты не сильно разгонишься.
За ссылку на morepypy спасибо — попробую его, а то у меня уже всё под py3. К переписыванию from scratch с отношусь скептически, всё же scipy использует старые добрые фортрановские библиотеки, где уже вылизано всё как только можно, им можно доверять. А вот что там понапишут новое ещё не известно. Хотя если будет время постараюсь помочь проекту.
Про переписывание с нуля я имел ввиду немного другое: насколько я знаю из последних событий, в PyPy в конце концов заколебались думать, как состыковать свою архитектуру с NumPy, написанной на C, и решили эту самую часть просто переписать. О переписывании библиотек, на которые опирается сам NumPy, конечно же, речи не идёт.
Когда последний раз когда я ее (julia) щупал, то эта «мощнейшая библиотека» была настолько базовой что аж грустно.
Увы, гадкий R, с огромной тучей CRAN пакетов рулит :(
Учитывая то, сколько R лет, в этом нет совершенно ничего удивительного.
Кстати, из julia легко вызывать код на Си или питоне
Проблема каждой новой технологии. В старую уже много заинвестировали и т.д. и т.п.
Как уже сказал Elsedar, производительность решает.

Вообще, разработчики в первую очередь ориентируются на потребности научного мира, а там, помимо производительности, нужна и простота — тяжело одновременно быть нейробиологом и знать премудрости C++ (конечно, ничего невозможного, но, чем ниже барьер входа, тем больше исследований смогут проводить учёные). Писать быстрый код для научных вычислений на Julia (по словам тех, кто этим занимается) выходит быстрее, чем на Python.

Если действительно интересуетесь, рекомендую почитать обсуждение к оригинальной статье на Hacker News.
Мультиметоды полезнее, чем классический ООП. Производительность. По мне так и синтаксис гораздо более приятный.
могли бы со скалой сравнить?
Со Scala все-таки сильно разные области применения. Скорее надо с R сравнивать. Или с Clojure (с библиотекой incanter).
Тем, что julia создавался с нуля исключительно для научных вычислений и активно оптимизируется исключительно под эту нишу, не пытаясь при этом быть универсальным-самым-лучшим-в-мире-языком.
Верю, что
была создана не гиками, а студентами точных наук из MIT

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

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

Именно, так.
Вы весь комментарий почитайте-то.
К примеру, если индекс массива — word, тогда мы можем с 0 до 255 пронумеровать элементы (00-FF). А если с 1 до 255, тогда один элемент теряем.
Нумерация блоков памяти с нуля идёт исторически. Видимо, потому что если нумерация с нуля, тогда текущий номер элемента — это смещение от начала. У нулевого смещение ноль.
Ну и как вы сами сказали — привыкание к такой нумерации настраивает на программирование, потому что во всех языках так и не надо путаться.
Именно, это техническая особенность адресации современных процессоров, которую, конечно, можно скрыть (на этапе компиляции) — но зачем?
Это создаст дополнительный слой преобразования, головную боль с переполнениями и отладкой (когда в исходниках одно — а в ассемблерном режиме — совсем даже другое).
А оно надо?
Для удобства человека а не машины. В этом похоже главная мысль.
Продолжая об удобстве программиста, я всегда считал, что нумерация с единицы это здорово, потому что тогда можно использовать 0 в качестве индикатора отсутствия результата, который имеет тривиальную проверку в блоке if. Сейчас же нужно сравнивать с какой-то иной константой (например -1), что визуально загрязняет проверку.

С другой стороны в строготипизированных языках это «слишком нестрого», и если есть типы-объединения, то можно пойти ещё дальше и возвращать какой-нибудь Either<Index, Nothing> там, где индекс может быть, а может не быть.
Ну и как вы сами сказали — привыкание к такой нумерации настраивает на программирование, потому что во всех языках так и не надо путаться.

Т.е. вы Pascal или Delphi языком программирования не считаете? :)
Уверен, что авторы ориентируются во многом на Fortran (это к слову об «исторически сложилось») и Matlab, где индексы тоже начинаются с 1.
Не во всех, есть исключения. Однако, насколько я помню, в паскале можно задать массив с любым начальным значением.
вообще по хорошему счету, нумерация всегда идет с 1, но когда идет речь о программировании на реальном железе то нумерацию заменяют на адреса элементов в массиве, а вот адреса-то и начинаются с нуля! Именно для удобного совмещения абстракции и реального железа ввели нумерацию элементов с нуля. Впрочем, иногда и эта абстракция даёт сбой — например строки в классическом паскале — 0-м элементом идет длина строки, поэтому реальное содержимое строки начинается с 1-го элемента.
Просто не надо путать понятия «номер» и «индекс». У первого элемента индекс ноль.
Наверное вот: поэтому
Вот «оригинал»

Да еще потому, что в низкоуровневых языках (ассемблер) для адресации элементов используется указатель на начало массива, а номер элемента является смещением. Соответственно, адрес первого элемента это [указатель]+0, а не [указатель]+1
Я отвечу вам в последенм комментарии:

Ну да, смещение, ассемблер, вот это все. В памяти все хранится а память нумеруется.
Но я в массивах также храню список комментариев, или, например, сообщения пользователей, и в меньшем количестве случаев, я храню там байты и биты. И нумерую я их: «первый комментарий», «второй». итд. В голове. А не впамяти компьютера.
Ну, если вы вообще имеете дело с индексированными массивами — то счет с нуля более «правилен» математически (не улетает в переполнение — в минус — на границах диапазона). Почитайте по ссылкам, там в общем-то довольно понятно. Хотя и на английском. Да, да тоже терпеть не могу математику — а куда деваться...

Ну а если вы работаете на высоком уровне — то это скорее «старость» языка. Современные языки для перебора коллекций используют итераторы и индексами там и не пахнет. Никакими…
foreach($cmments as $comment){ }

Так что, возвращаясь к исходному вопросу — для «старых» языков удобство в том, что при счете с нуля вы наделаете меньше ошибок :-)
А для новых — индексы и вовсе не очень-то нужны, да и сделать можно любые, какие хочется (привет итераторам)
Прошел по ссылке, и мой интерес удовлетворен. Пожалуй теперь я не согласен с разработчиками Julia :D
В остатке от деления. Он от 0 до n-1
В том, что эту единичку чаще не нужно прибавлять/убавлять при реализации некоторых сложных структур, тот же heap в виде массива.
Отсчёты бывают не только от какого-то указателя, но и от времени.

PS Ряд Тейлора принципиально важно стартует с 0. Комбинаторика доопределила 0!=1 тоже не просто так
Могу ошибаться сонным мозгом, но вроде тогда так не сработает (пример на js)
var a = []; // представим, что он заполнен
i = a.length;
while (i--) console.log(a[i]);

На элементе с индексом 0 будет undefined. Не спрашивайте меня зачем такой код бы мог понадобиться :)
В любом случае, перестроиться будет уже очень непросто.
Индексная арифметика. Прибавляем к указателю на массив байтов индекс нужного байта элемента и получаем адрес байта.
Вы как будто не читали вопроса и весь тред

Я знаю как ето работает внутри. Вопрос был не в этом.
Так это и есть практическое применение особенности — адрес первого элемента и адрес массива совпадают, значения array, array + 0 и array[0] указывают на одну область памяти.

Просто не нужно путать индекс элемента и его номер.
Про это я тоже писал в треде )
Может из-за двоичной системы счисления? Тип мы не можем добиться 256 вариантов считая с 1? Максимум 255, а как бы терять один вариант думаю не очень логично.
Предоставлю слово Джеффу Безансону, одному из разработчиков Julia:
There is a huge discussion about this on the mailing list; please see that. If 0 is mathematically «better», then why does the field of mathematics itself start indexes at 1? We have chosen 1 to be more similar to existing math software.

It really isn't that interesting a topic though. We all work with both 0-based and 1-based languages and I don't think it matters that much.
В математике степенные ряды сплошь и рядом стартуют с 0-ой степени и нулевого индекса. Ну или с первого индекса и потом уродливую единичку везде прибавляют
Ряд Тейлора, определение факториала (0!=1), ряд Фурье вполне себе с нулевого индекса определяются.
Отсюда вывод — начало индексации должно быть произвольным. С единицы — для массивов и строк, с нуля — для рядов Фурье и Тейлора.
Это не «нормальные программеры», а формировавшиеся после набора популярности Си, фактически — в 1990-х и позднее. А раньше, до массовой «сификации», это был даже не холивар, а просто рабочий момент, который каждый программист сразу узнавал при изучении нового языка. Мне в то время даже в голову не приходило спорить, с 0 или 1 должен начинаться индекс. Как в данном языке принято — так и надо использовать :)
Julia она для математиков и статистиков. По образу и подобию R.
В математике нет массивов, а есть вектора. И нет «нулевого» компонента вектора. Есть только первый.

Все.
Это скорее не математические, а общекультурные заморочки. Если бы математики и простые люди были бы сразу приучены, что натуральные числа начинаются с нуля, многое было бы проще.
Нумерация с 0 и в математике не менее популярна. И она гораздо более элегантна. ru.wikipedia.org/wiki/%D0%9D%D0%B0%D1%82%D1%83%D1%80%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%87%D0%B8%D1%81%D0%BB%D0%BE

Кроме того, при использовании низкоуровневых языков llvm или asm внутри julia нумерация ведь будет с 0. Это может привести к шизофрении)

> Индексирование не всегда правильно работает для UTF8-строк,
21 век на дворе… а до сих пор изобретают языки без поддержки юникода, перегрузки операторов и многозадачности
> Я обычно работаю таким образом: сначала пишу прототип на одном языке, после переписываю критические секции на другом языке, а если мне нужно, чтобы программа летала, я использую третий язык.

Учитывая что в практически всех проектах над которыми я работал все упиралось в производительность не языка а БД, и переписывать никто ни на что не собирается, этот язык наверно не для меня.
Вообще говоря, ассемблерные вставки в не самом новом языке Паскаль (во всех средах, Turbo Pascal, Delphi, Lazarus) никто вроде не отменял. Равно как и возможность дебажить / просматривать в нативных средах разработки ассемблерный код.
Так что с новизной тут как-то не очень…
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Не всегда же. Если я пишу прототип, чтобы проверить какую-то идею, можно и на выброс код написать. Как я понимаю, язык расчитан на ученых, а им обычно бывает просто необходимо какую-то штуку посчитать и уже использовать полученные данные. С другой стороны, иногда этого качества очень не хватает, когда этот кусок кода в production систему надо встраивать.
Язык очень похож на LuaJIT, с его FFI возможен довольно простой биндинг с Си. Не пробовали сравнить?

А сила Питона всё-таки в готовых библиотеках. Как в Julia обстоят дела с Qt? А GTK+? Тут у Питона неоспоримая ниша.
Такой язык хочется задействовать для роботов и компьютерного зрения.
Есть ли там биндинги к OpenCV?
И какой-нибудь аналог CRAN (CPAN, Cabal...)?
По-моему, отличный язык для изучения ассемблера ;)
Меня оттолкнуло что массивы начинаются с 1 а не с 0, нету ООП (или я не нашел). Но для некоторых задач думаю может быть удобным
Есть мультиметоды, которые перекрывают возможности ООП.
Пока читал статью, думал, ну вот, ещё один язык программирования… Зачем? Но, когда стал смотреть описание языка, заинтересовался. Что-то в нём есть, действительно, изящное. Мне, как выпускнику «примата», очень нра. Возможность задать рациональную константу 2/3 впечатляет и притягивает. Скажите, какие есть ещё языки программирования, позволяющие делать такие вещи?
А интересно, можно ли в каких-то языках работать еще и с иррациональными числами? Ну т.е. sqrt(2) + 1 будет представляться именно иррациональным комплексным типом (в виде дерева). И если возвести это значение в квадрат, то получить 2 * sqrt(2) + 3?
В системах символьной математики, которые все в себе несут язык программирования более-менее общего назначения.
Но можно и для обычных языков библиотечку сделать.
Что вы имеете в виду под «иррациональным комплексным типом (в виде дерева)»? В haskell, например, можно достаточно просто реализовать тип действительных чисел по определению — в виде бесконечного потока цифр. Правда, для таким образом представленных чисел неразрешимо сравнение, например (т.е. нельзя сделать всегда завершающийся оператор «равно»). Это не проблема реализации, а фундаментальное ограничение.
Что вы имеете в виду под «иррациональным комплексным типом (в виде дерева)»?

Я имею в виду, sqrt(2) + 1 будет представляться как:

Данный вопрос у меня возник в связи с тем, что я почти дописал статью и проект по обработке математических выражений в .NET, в котором есть такой тип данных (правда там еще и с неизвестными). И поэтому интересуюсь данной тематикой.
(quote (+ (sqrt 2) 1))

Свёртку математических операций (например, чтобы "(+ 1 2)" сворачивалось просто в «3») можно добавить буквально одной рекурсивной функцией. В последних главах SICP-а подробно описывается, как работать (вычислять-применять) с таким абстрактным синтаксическим деревом. Даже если вы не пользуетесь Lisp-ом, то можно довольно быстро написать простенький интерпретатор в любом удобном для вас языке, в т.ч. для .Net.
Посмотрите в сторону Mathnet Palladium и других библиотек того же автора. Я начинал тоже что-то такое делать, но оно не production-ready и вообще лишь учебный концепт.
На самом деле все еще хуже. Нельзя отличить последовательность 1(0) и 0(9). То есть возможны алгоритмы, не только сравнения, которые в некоторых случаях не смогут выдать очередную цифру.
Интересно представление чисел в виде цепных дробей. Но и там будут проблемы вблизи рациональных чисел.
Можете развить мысль? Я не совсем понял, как это нельзя отличить последовательности 1.(0) от 0.(9). Если использовать представление чисел в виде бесконечных списков цифр, как я писал выше, то как раз эти последовательности отлично различатся уже по первой цифре. Другое дело, что получив на вход 0.(9) мы никогда не сможем сказать, действительно ли это 0.(9), или где-то дальше будет не девятка (следовательно, не сможем сказать, равно ли это число единице).
Например, мы вычитаем из известной удиницы «1.0» неизвестный ноль «0.(0)». Мы не можем выдать первую цифру «1» не убедившись, что вычитаемое строго равно нулю. То есть вычисление очередной цифры требует бесконечного сравнения.
Кстати, у p-адических чисел такой проблемы нет. Хотя сравнение может никогда не завершиться, вычисление очередной цифры для «нормальных» функций происходит за конечное время.
Вообще теперь я вас понял, но в данном конкретном примере всё-таки получится нормально вычислить :) Мы же можем выдать сразу 0, и далее все девятки. Если вычитаемое — ноль, то получится число 0.(9), которое равно единице. Если же какая-то цифра вычитаемого не ноль, то и в ответе не 9 будет.
А вот в случае 0.(9) + 0.(0) как раз возникает описаннная вами проблема.
А кто-нибудь знает, почему у in словарь вторым аргументом, а у haskey и get — первым?
Предположу, потому что ближе к английской фразе key in dict.
И название какое: Julia. Тоже в честь какой-то программистки назван, по аналогии с языком «Ada»? :)

А вообще да: Юля красивая.
Вопрос по существу. Как запустить файл с расширением jl
На винде jl запускается без проблем в cmd я делаю путь на julia и второй строкой путь к моей программе на jl Программа выполняется в командой строке без проблем. Но ничего не выходит на маке. В результате в терминале запускается только julia, программа не выполняется. Подозреваю, что причина в сыроватости.
Ок, сам спросил, нашел ответ, сам ответил. В терминале путь к /Applications/Julia-0.3.0-prerelease-d2d9bd93ed.app/Contents/Resources/julia/bin/julia-readline далее путь к программе /Users/Documents/firsl_julia.jl
На винде я тоже прописывал путь к julia-readline
Sign up to leave a comment.

Articles