Pull to refresh
31
10.5
Кирилл Белов @KirillBelovTest

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

Send message

На мой взгляд использование в коде % - это антипаттерн. Эта штука не опасна, если пользоваться ей только в интерактивной сессии в режиме 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

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

@GorwinH скорее всего имел ввиду создание функции которая применяет другую функцию к списку, т.е. применение фукции высшего порядка

Ну если надо применить сложение к списку то в WL это вот так:

sum = Apply[Plus]

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

Часто причина появления open-cource кода не в том, что автор перечитал манифест Столлмана, а в том, что есть закрытый коммерческий продукт, который нужен разработчику, но ему оказывается проще сделать открытую альтернативу. Все способы монетизации, которые вы предложили просто противоречат этой цели. Если автор попытается open-source монетизировать "в лоб" (сам код, а не поддержку и не услуги), то этот код автоматически перестает быть открытым. Значит он и не нуждается в монетизации, иначе он вообще исчезнет из природы!

Вот как в WL с размерностями можно работать
https://imgur.com/a/Rt8uwWQ

Вы можете установить бесплатный fronted (который разрабатывает @jerryi и я). Там интерфейс удобнее чем командная строка. Когда у меня будет время я постараюсь сделать руководство для тех, кто впервые знакомится с языком. А пока что есть вот такая статья https://tproger.ru/articles/besplatnye-instrumenty-dlja-wolfram-language. Некоторые пункты там конечно уже не актуальны (например редактора Атом уже нет), а новых инструментов тогда еще не было

https://github.com/JerryI/wolfram-js-frontend/releases

Information

Rating
641-st
Location
Саратов, Саратовская обл., Россия
Registered
Activity