Pull to refresh
29
0
Кирилл Белов @KirillBelovTest

Инженер по автоматизации тестирования

Send message

Там еще и качать надо и установка не в один клик. Лучший вариант - поставил OC - а там сразу ядро. Как в raspberry pi

Так сам язык WL уже стал бесплатным, но с ограниченной лицензией - можно пользоваться всем что есть в языке, но без UI Mathematica. Зато есть такие же бесплатные приложения, которые реализуют свой UI - можно подключить к Jupyter ядро, а можно в WLJS. Просто исторически так получилось, что Mathematica получила некоторую репутацию, которая декларировала Mathematica а потом WL как нечто за деньги для ученых и бесполезное для программистов. А когда WRI начали пытаться сменить курс - более молодые технологии их уже опережали. Я считаю, что это во многом ошибки WRI в своей стратегии распространения и рекламы продукта.

Отличный исторический экскурс! Я сам неоднократно слышал подобные рассуждения, а в последнее время все чаще с популяризацией LLM. В такие моменты я обычно вспоминал про популярные конструкторы сайтов. Но я хочу так же высказать альтернативную точку зрения.

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

Во-вторых, зная, что в истории заголовки "программисты вымрут" пока что никак не привели к вымиранию - легко рассуждать, что все журналисты и другие люди пропагандирующие эту точку зрения были неправы. Но в настоящем времени никто не станет утверждать со 100% вероятностью, что этого вымирания точно не случится.

В-третьих, люди очень часто склонны смотреть в будущее с необоснованным оптимизмом. Иногда конечно с обоснованным, но все равно оптимизмом. Мы почему-то верим, что всегда найдем выход, со всем справимся и все будет хорошо. Но в будущем может случиться все что угодно. Кризисы уже были, а политика сильно влияет на ситуацию в отдельных регионах. Пусть программисты не вымрут все сразу во всем мире, но в какой-то момент компании поймут, что на улице кризис, а они наняли слишком много сотрудников - так уже было после короновируса. Либо инвесторы, которые раньше массово вкладывали деньги в IT-стартапы следуя за модой уйдут в другую сферу - тогда число компаний. Не обязательно все, но просто снизиться процент от общего капитала, который идет в IT.

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

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

В WL нет массивов, есть только списки. Как я писал выше во всех выражениях нулевой элемент - это заголовок, по этой причине индексация списков начинается с единицы, т.е. в

List[a, b, c] (* полная форма {a, b, c}*)

List - это нулевой элемент. А вообще я например очень редко пользуюсь итераторами, так как если у меня есть список и нужно пробежаться по нему и что-то сделать - то куда проще сделать это через:

Map[func, list] (*или*)
Table[func[item], {item, list}]

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

Да, скоро будет его с конференции на этот год, так что следите за новостями! Если у вас есть интересная тема для доклада - то можете сразу выслать!

Ну, так и получилось на самом деле. Причем разница довольно заметная:

uuids = Table[
	With[{uuid = CreateUUID[]}, 
		func[uuid][args_] := args; 
		uuid
	], 
	{1000}
]; 

symbols = Table[
	With[{symbol = Unique["symbol`$"]}, 
		symbol[args_] := args;
		symbol 
	], 
	{1000}
];

With[{uuid = uuids[[1]]}, RepeatedTiming[func[uuid][1]]]
With[{f = symbols[[1]]}, RepeatedTiming[f[1]]]

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

У меня предположение, что кто-то сознательно организовал "атаку" на дом Торвальдса. Вероятнее всего злоумышленники пытались украсть исходный код linux 🤔

Там не только списки нумеруются с единицы. Все выражения нумеруются с единицы, а заголовок - это нулевой элемент. Это очень удачно выглядит при экспорте в ExpressionJSON

ExportString[f[a, b, c], "ExpressionJSON"]

Out[] = "[
	\"f\",
	\"a\",
	\"b\",
	\"c\"
]"

То есть можно рассматривать любое выражение как плоский список в lisp, где заголовок - это нулевой элемент.

они написаны математиками с бэкграундом на фортране

Это очень спорный вопрос. Mathematica почти в текущем виде появились в 1988 и естественно в те времена по моим представлениям было очень трудно найти хорошего математика, который знал бы все паттерны проектирования и умел проектировать архитектуру приложений как на глобальном уровне всего приложения, так и в те моменты, когда вопрос касается небольших функций. Ведь "Чистый код" впервые опубликовали в 2008, а принципы SOLID решили так назвать и начали всем рекомендовать в начале 2000-х. И вообще много чего тогда не было. Но что на мой взгляд заслуживает внимания - первые разработчики сначала SMP, а затем Mathematica сразу начали писать код на C и затем на С++. А ведь SMP была выпущена в 1981! И это приложение уже придерживалось всех принципов, которые затем перешли в Mathematica. Только представьте как сложно было найти математиков с опытом программирования на С++ с учетом того, что С++ появился в 1984.

WL изначально не задумывался как мультипарадигменный язык программирования

Все верно, изначально он вообще не задумывался как язык программирования, но в итоге стал им. И я не считаю, что он для этого не предназначен. Если WL нельзя использовать как язык программирования, а пригоден он только для решения задачек по математическому анализу и физике, то та же участь должна постичь такие языки как JS или С. Они ведь тоже провоцируют писать код как попало. На них точно так же совсем небольшой процент действительно хорошего кода, но эти языки все еще остаются самыми популярными в мире.

Cам язык к этому провоцирует

Язык очень гибкий и на мой взгляд оказался в заложниках своей истории. А история говорит нам, что Mathematica эта штука для развлечения ученых и не более. Его гибкость и принципы исполнения кода не провоцируют, а позволяют делать что угодно и как попало в любом виде. Я перед тем как опубликовать статью и предвидя комментарии касающиеся "write-only" кода спросил в группе в вк о примерах "чудовищных выражений на WL". Не так много ответили как мне хотелось бы, но вот несколько таких:

Position[Nest [Append [#1, If [#3 > 1, #1[­[-1]]/#3, 3 #1[­[-1]] + #2 + 1]] & @@ {#1, #2, GCD[#1[­[-1]], #2]} & @@ {#, Length@# + 1} &, {1}, 10^4], 1][­[All, 1]]
Image@Total[NestList[Transpose[Mod[.91 + # . BoxMatrix[1]/3, 1] & /@Transpose[i = 1; RotateLeft[#, i--] & /@ #]] &,SparseArray[{_, 51} -> 1, {3, 101}], 101], {2}]

Там на стене можно и другие поискать, а еще есть tweet a program. В общем к чему я - всего одна строчка, а прочитать ее составляет огромных усилий. И это тот самый антипаттерн из статьи. Такой код хорош если его нужно выполнить один раз или когда его нужно написать в качестве упражнения, где короткое и запутанное выражение является самоцелью. Но если нужно сделать действительно полезную библиотеку или приложение, то придется себя дисциплинировать и следовать все принципам. Это в точности то, зачем я написал эту статью! Какие-то языки жестоко наказывают программиста если они что-то делают не по конвенции - я считаю, что это по своему прекрасно. Но в WL такого нет, единственный вариант чтобы код не сработал - это написать его синтаксически неверно. Тогда он просто не выполниться, а все остальное работает по принципу "ты это хотел, ты это получил" (из того великолепного перевода, что вы мне скинули выше, который мы читали всей командой).

Мой вывод касательно этого пункта такой: до тех пор пока пользователи не будут относиться к языку серьезно, а считать сам язык плохим и провоцирующим на ужасный код - код на нем таким и останется. Легкость освоения на первоначальном этапе и сложность на последующих сыграли с WL злую шутку. Очень многие остановились на возможности построить график и взять интеграл, а большего им никогда и не потребуется (в чем нет ничего плохого). Но из-за этого большого количества хороших примеров так никогда и не появится. А еще есть преграды в виде цены, закрытых исходников и отсутствия вакансий в коммерческой разработке, которые могли бы стимулировать программистов изучать язык, писать хороший код и тем самым улучшать сам язык и привлекать других людей.

Но всё остальное - это костыли, которые логически из постановки решаемой задачи вытекают слабо.

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

Например - это функции Reap и Sow, необходимые для пополнения одного списка в процессе итерирования другого без оверхеда.

Если под оверхедом вы имеете ввиду то, что список нельзя изменить, а можно только создать новый, то я б не назвал это костылем. Таков принцип языка, что все выражения неизменяемы и только значения символов можно изменить. Но это естественным образом приводит к снижению эффективности, и в итоге появились некоторые функции, которые не до конца соблюдают принцип неизменяемости. Sow/Reap используют внутри себя функцию Internal`Bag, которая позволяет добавлять значения в структуру данных за O(1) и затем возвращать список. А вот накопление в пустой список происходит за O(n) в наивном случае. Но вы легко можете создать свой накопитель без оверхеда вот так:

SetAttributes[MyList, HoldAll]

MyList[] := With[{array = Unique["MyList`array$"], length = Unique["MyList`length$"]}, 
	array = {0}; 
	length = 0; 
	MyList[array, length]
]

MyList /: Append[MyList[array_Symbol, length_Symbol], element_] := (
	length = length + 1; 
	If[IntegerQ[Log2[length]], array = Join[array, ConstantArray[0, length]]];
	array[[length]] = element;
)

А вообще сейчас уже есть изменяемые структуры данных (да они появились поздно), которые создаются при помощи CreateDataStructure.

Ну а когда выбирать SetDelayed, а когда RuleDelayed - это отдельное интересное приключение.

Да, этот тот самый случай, когда для понимания разницы нужно чуть-чуть больше усилий, чем на то, чтобы научиться строить графики и решать уравнения. Я сам хоть и постоянно пользовался Математикой в университете, но только спустя годы понял, что это и зачем вообще нужно. Нужно чтобы тех, кто знает как и когда применять -> а когда :> было больше и чтобы они писали красивый код!

Значит, что программировать надо учиться. Не просто выучить синтаксис и стандартную библиотеку - а как строить архитектуру, разбивать задачи на подзадачи и всё такое.

Это не относится к конкретному языку. Здесь вы кажется превращаетесь в Д'Артаньяна. В WRI есть разные разработчики с разным опытом и уровнем как и в любой компании. Как я и говорил есть хорошие и плохие внутренние библиотеки и у меня сложилось впечатление, что внутри компании нет никакого соглашения по написанию кода. Я на самом деле даже задал этот вопрос одному из разработчиков, который там давно работает - и ответ был утвердительный. И это еще одна причина почему я написал эту статью. Возможно в будущем я ее изменю и переопубликую, чтобы она была более строгой.

Я ж совсем забыл. На хабре тоже есть две статьи про WLJS Notebook

Чистая функция Function хороша по своему, но имеет недостатки на фоне функций (их еще иногда называть шаблонными функциями). Главный недостаток - сложность и громоздкость работы собственно с шаблонами и громоздкость создания переопределений и перегрузок. А некоторые вещи для чистой функции вообще недоступны, так как по сути это неизменяемое выражение, а обычная (шаблонная) функция - это символ с определением.

В чем преимущество чистых функций:

  1. Авто-компиляция при использовании внутри функций высшего порядка

  2. Более простой и стандартный вид - в них невозможно использовать громоздкие шаблоны

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

Авто-компиляция. Магия, которая срабатывает если в одном месте и в одно время встретились:

  • Чистая функция

  • Функция высшего порядка

  • Упакованный массив

func = #^2 + 1&; (*чистая функция*)
mapFunc = Map[func]; (*функция высшего порядка + чистая функция*)
packedArray = Range[10000]; (*упакованный массив*)
Developer`PackedArrayQ[packedArray] (*так можно убедиться что он упакован*)

mapFunc[packedArray] (*тут сработает автокомпиляция, т.е. код выполнится быстрее*)

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

Более стандартный вид. Ну тут очевидно, что упрощение и более строгие рамки ведут к более понятному коду.

Использование как лямбда-функции. Собственно это в первом примере и показано. Но я должен объяснить один момент про символы. Дело в том, что каждой символ - это отдельный объект в ядре. При его создании происходит выделение памяти в процессе и куча еще какого-то оверхеда связанного с выделением места в списке определений. Поэтому без особой нужды символы лучше не создавать. А если нужна функция и она достаточно простая и короткая - то можно обойтись чистой функцией.

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

Это не функция, а правило замены

addOne[x_] := x + 1

Не думаю, что вам нужно что-то доказывать, так как я помню, что вы хорошо знаете WL и читали предыдущие статье где я писал про правила и шаблоны. Но я должен поправить терминологию. Выше - это функция. Функция в WL - это символ, который имеет DownValues или SubValues (но вообще можно и с OwnValues создать "функцию", т.е. символ, который применяется к набору аргументов с квадратными скобками). Она только использует правило замены в определении, но сама по себе не является правилом замены. Правила замены это однозначно только:

rules = {
    x -> 10, 
    time :> Now
}; 

Функция определяется как

addOne = Function[{x}, x + 1]

А это принято называть "чистая функция". Если использовать вашу терминологию, то в итоге выражение Function[..] тоже сведется к правилу замены. Ведь абсолютно все определения символов в WL хранятся в списках определений в виде правил замены, примерно как в анекдоте про урок физики в церковной школе... 🤔

И по второму пункту. Как я и писал в статье - любой текстовый файл с кодом на WL можно назвать пакетом, но в статье я описал правила и рекомендации по разработке стандартизированных пакетов. Естественно, если вам не требуется пакет соответствующий "стандарту" вы можете просто создать один файл с расширением .wl, перечислить в нем определения, затем добавить папку с ним в $Path и использовать без всех тех ритуалов что я описал. Т.е. например файл MyPack.wl в директории ~/MyPack:

func1[x_, y_] := x + y; 

Код добавления пакета в пути поиска (один раз в $UserBaseDirectory/Kernel/init.m):

$Path = DeleteDublicates[Append[$Path, "~/MyPack"]]; 

И в блокноте:

<<MyPack`

func1[1, 2] (* => 3 *)

В итоге папка MyPack всего с одним файлов уже будет готовым пакетом, просто не по стандартам WRI.

И второе, что я хотел сказать по этому пункту. Дело в том, что между WL и python есть различие в структуре кода и файлов. Что касается импорта пакетов, то python обрабатывает файловую структуру, а сам код уже на втором месте. То есть создав простой модуль его имя и способ загрузки зависят от файла и его расположения, но не от кода внутри. А в WL в первую очередь важен сам загружаемый код и неважно где он находится. Создать и загрузить в сессию полноценный пакет можно прямо в самой сессии, для этого даже не нужно создавать никакие файлы. А вот PacletManager как раз стандартизирует подход к организации паклетов именно через структуру файлов и папок, то есть смещает фокус в сторону, которая ближе к Python.

Сначала отвечу на первый пункт.
Во-первых, как говорят Джейсон Стэтхэм и тимлид нашей команды: "Лучше меньше кода, чем больше кода. 400 строк - это хороший мердж-реквест, 20 строк - отличный, а если удалил 100 строк, то просто великолепный". Привычный глаз легко разбирает все псевдонимы и сокращения на WL, но их такое огромное разнообразие, что я, например, постепенно от них отказываюсь и оставляю только самые лаконичные и очевидные. Например, вот это на мой взгляд хорошо:

Map[func] @ {1, 2, 3, 4}

А вот это уже значительно хуже, так как тут явное злоупотребление для одной строки, хоть и совсем небольшое:

Exp[#^2 - Sin[#] + Log[x^1/2]& /@ {1, 2, ,3 , 4}

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

Во-вторых, это какой-то стереотип на счет итераторов и того что они являются плохим тоном в WL. Да, For очень неудобен, но While - это самый удобный способ создать бесконечный цикл, а Table самая лучшая, читаемая и очевидная функция для обработки списков. Даже не смотря на то, что я часто пользуюсь функциями высшего порядка - Table для меня не является чем-то плохим. К стандартным циклам вроде For такое отношение из-за их громоздкости и того, что они не локализуют итератор. И кстати говоря в Table вполне работает автокомпиляция как и в Map.

Спасибо! Кроме самой Mathematica я использую VS Code вместе с официальный плагином для Wolfram Language от Wolfram Research. Он бесплатный. Еще если его правильно настроить (указать путь до ядра), то он начинает работать значительно лучше - показывает usage и лучше работает автодополнение.

Кроме этого я упоминал про бесплатный интерфейс WLJS Notebook для Wolfram Engine который разрабатывает @JerryI и я.

А еще у меня есть старая статья с обзором бесплатных инструментов для WL. Сейчас часть информации оттуда устарела, может быть в будущем я напишу продолжение. Если у вас есть еще вопросы - с радостью отвечу =)

Да, это сработает, но противоречит правилам, которые предлагаются в WRI. Без usage примерно тоже самое, но ближе к конвенции это вот так (подразумеваются двойные переносы):

BeginPackage["ContextName`"];

SomeVariable;

SomeFunction;

Begin["`DontLookAtMe`"];

SomeVariable = 777;

SomeFunction[_] := RandomInteger[{0,10}];

End[]; 

EndPackage[];

В итоге в сессии будет доступно сразу:

func
var
someVariable
someFunction

В таком случае все таки лучше оформлять пакет как скрипт, где определения идут подряд. Но беда такого способа в том, что любая локальная переменная будет засорять текущий контекст пользователя.

Information

Rating
Does not participate
Location
Саратов, Саратовская обл., Россия
Registered
Activity