vitus_wagner: My photo 2005 (Default)
vitus_wagner ([personal profile] vitus_wagner) wrote2017-03-15 09:37 am

Semantic locality

http://esr.ibiblio.org/?p=7421

Раймонд умный пост написал по поводу концепций, которые лежат под Unix way. Я эту мысль про семантическую локальность три дня думать буду.
allter: (Default)

[personal profile] allter 2017-03-16 08:07 am (UTC)(link)
Чем-то эта концепция ещё перекликается с принципом единой ответственности в ОО-дизайне, а также с остальными принципами в SOLID.

Правда на практике делать экспорт-импорт данных из монолитной системы в виде потоков данных с семантической локальностью как правило лень и нет времени. Я даже сейчас подумал, что это основная проблема с юникс-вэйностью systemd, который там сразу начали обсуждать в комменариях.
Edited 2017-03-16 08:14 (UTC)

[identity profile] amarao-san.livejournal.com 2017-03-16 09:29 am (UTC)(link)
Я думаю, semantic locality и stream processing - практически одно и то же. Если мы можем выполнять работу без random access вне рабочего окна, то:
а) наша работа семантически локальна
б) может выполняться над потоком данных

Вообще, лично у меня к классическому unix-way-pipes есть масса претензий, основная, это отсутствие типизации. Все эти бесконечные awk -F '-' '{print $1" "$2"}' - это восстановление типов по косвенным признакам.

В современном серверном софте есть некотоая благая подвижка, в которой cli ко всякому сложному софту выводят данные в json. Всё то же самое, но уже с минимальной структурой, так, что не нужно тратить специальные усилия на обработку специальных случаев (пробелы в именах, "неправильные" символы и т.д.). Вместе с jq и другими видами query languages это очень удобно.

Революция произойдёт в тот момент, когда GNU научится хоть какой-то структуре в выводе программ.

Условно говоря, получение ip-адреса для eth3 будет выглядеть так: ip -j a l|jq .eth3.ipv4.address[0].

Второй больной вопрос: это error stream. У нас есть софт для работы с stdout/stdin, а stderr - полностью на откуп человеку. Всего машиночитаемого в обработке ошибок - единственный int, который относится не к куску данных, а ко всей программе. Для stderr нет никаких методов обработки - оно "human readable".

allter: (Default)

[personal profile] allter 2017-03-16 10:23 am (UTC)(link)
> В современном серверном софте есть некотоая благая подвижка, в которой cli ко всякому сложному софту выводят данные в json. Всё то же самое, но уже с минимальной структурой, так, что не нужно тратить специальные усилия на обработку специальных случаев (пробелы в именах, "неправильные" символы и т.д.). Вместе с jq и другими видами query languages это очень удобно.

Это здорово, но проблема в том, что это не универсально. К примеру, понадобились в структуре данных циклы - выход только в том, что бы сделать несколько команд-запросов разных видов данных, либо в том, что бы использовать более сложную структуру, типа YAML. Захотелось структуры более ориентированной на вывод (например, с названиями элементов в определённом порядке) - придётся использовать XML. А ведь сейчас в 2017 году, несмотря на то что все упомянутые форматы существуют много лет, до сих пор у людей большие проблемы, что бы распарсить даже одиночные сообщения в данных форматах. Не говоря уже о том, что потоковая передача заранее неизвестного количества сообщений в этих форматах определена только для YAML.
Edited 2017-03-16 10:25 (UTC)
allter: (Default)

[personal profile] allter 2017-03-16 10:23 am (UTC)(link)
> Условно говоря, получение ip-адреса для eth3 будет выглядеть так: ip -j a l|jq .eth3.ipv4.address[0].

Скорее, если я правильно вас понял, будет что-то вроде:


SomeInterpreter.exe /eval "import System; WriteLn( System.getNetworkStackInterface('eth3').getAddress( System.Network.NET_IPV4 ) )"


Утилиты для этого не нужны. :)

[identity profile] amarao-san.livejournal.com 2017-03-16 10:32 am (UTC)(link)
Вообще, потоковый парсинг json'а уже есть. Тот же jq предполагает, что указанная "семантическая локальность" вообще реализуется посредством стекирования json'ов (закончился один, начинается следующий).

Отсутствие универсального решения, которое бы покорило всех - да, печалит. Но, с другой стороны, отсутствие решения печалит ещё больше.

Ситуация с пайпами и данными в них напоминает компьютеры первых поколений, когда ещё не были придуманы типы данных. Вот тебе память, вот тебе адрес, вот тебе инструкция "читать из памяти", делай что хочешь. До момента более-менее приличных структур данных в языках несколько поколений языков сменилось.
livelight: (Default)

[personal profile] livelight 2017-03-16 10:38 am (UTC)(link)
Мы говорим Unix Way - подразумеваем Unix Shell, говорим Unix Shell - подразумеваем Unix Way :)

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

[identity profile] amarao-san.livejournal.com 2017-03-16 10:41 am (UTC)(link)
Я дал это для примера. Подобный вывод (структурированный) должен появиться у всех. У grep, find, ls, ps, etc. Чтобы awk '{print $2}' стало прошлым.
livelight: (Default)

[personal profile] livelight 2017-03-16 10:46 am (UTC)(link)
"потоковый парсинг json'а" - это почти оксюморон :)

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

Во-вторых, JSON имеет отягощённую наследственность: у него в нутре гены JS, не предполагающего упорядоченность где-либо, кроме массивов, соответственно, любой обработчик имеет право переставлять ключи местами как попало, и читателю опять же придётся считать их все, прежде чем что-то обработать.

Чтобы обрабатывать это всё потоком - придётся много дополнительных ограничений на JSON наложить. В старом добром же Юниксе, можно было в большинстве случаев говорить о локальности, пока нам кто-нибудь гарантирует ограниченность длинны строки (в смысле line, а не string)
allter: (Default)

[personal profile] allter 2017-03-16 10:50 am (UTC)(link)
По поводу пайпов согласен.

К примеру, если бы g++ выплёвывал stderr в каком-то структурированном формате, то возможно, было бы проще копаться в template vomit.

Но с другой стороны, благодаря принципу семантической локальности также оказалось возможным реализовать функционал, связанный с командой make в vim - благодаря давнему соглашению о текстовом формате имя_файла:строка:столбец: (make не знает в каком точно формате идёт вывод ошибок). Если каждый раз придумывать свой формат, то взаимодействующие программы будут вынуждены менять код для разбора вывода.

[identity profile] amarao-san.livejournal.com 2017-03-16 10:53 am (UTC)(link)

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

Хотя в том же json'е, например, задача "извлечь 100 Гб значение по ключу из 10000 Гб json'а" вполне решается. Мы просто вешаем обработчик на чтение значения, которое выдаёт значение на stdout по мере чтения, а всё остальное пропускаем. Если у нас при этом нет превышения по размеру ключа (очевидно, что мы не можем найти что-то по ключу, который больше нашей оперативной памяти, т.к. не сможем его передать как параметр программы), то это вполне рабочее.

Я баловался с потоковыми парсерами json'ов, на удивление, это вещь и она может существовать в константной памяти при неконстантных входных данных.

[identity profile] amarao-san.livejournal.com 2017-03-16 10:58 am (UTC)(link)
Ну вот смотри. Есть у тебя соглашение "имя_файла:строка:столбец". Строка и столбец - инты. А имя файла - строка, произвольная строка, соответствующая требованиям unix для имени файла.

Этот протокол неконсистентный, ибо он не сможет показать ошибку в файле "пример ошибки в файле:33:44" на строке 2 и столбце 4.

И вот эта неконсистентность между соглашениями (нам обещают stateless-separator, выдавая либо что-то без эскейпинга, либо с ложными срабатываниями) и превращает шелл-пайпы из няшки в ад и некомфортность.

Собственно, что там далеко говорить. Из свежего: systemd положил файлик "-" - и всё, миллион вопросов о том, почему ls ломается.
livelight: (Default)

[personal profile] livelight 2017-03-16 11:05 am (UTC)(link)
Я поэтому и оговорил ограниченную длину строки. Притом команде sort, например, это не поможет, поскольку она по сути нелокальна, но большинству фильтров этого достаточно. А вот с настоящим JSON этот фокус не пройдёт, даже если мы ограничим длины всех констант, всех массивов и всех хэш-таблиц. Впрочем, если ограничить ещё и глубину деревьев, то таки получится.

[identity profile] amarao-san.livejournal.com 2017-03-16 11:08 am (UTC)(link)
Я давно про это думал. Удивительно или нет, но мне кажется, что единственное решение тут - это монады. Мы передаём на вход утилиты, ожидающей Just data, вывод утилиты, выдающей Maybe Data, и получаем ошибку прям от шелла со словами, который говорит "нельзя". Мы ставим между ними в пайп, конвертор, который знает, что делать когда дата не "Just", и у нас всё работает точно. Наверное, под нужды шелла можно написать и более выразительные монады.

[identity profile] amarao-san.livejournal.com 2017-03-16 11:09 am (UTC)(link)
Скажи, какую операцию в json'е ты хочешь делать? Многие из них могут выполняться в локальном контексте.
livelight: (Default)

[personal profile] livelight 2017-03-16 11:15 am (UTC)(link)
Банальный фильтр, который должен бы быть поточным: читаем входной поток неких токенов с полями, а для тех, у которых выполняется некое условие на поле1 и поле2, выводим в выходной поток поле3 целиком, не вникая в содержимое.

Соответственно, в случае JSON мы всегда можем получить на вход что-нибудь такое:
{
поле1: значение,
поле3: { развесистое и глубокое дерево },
поле2: значение
}


в результате мы должны запомнить всё, что в поле1 и в поле3, прежде чем считаем поле2 и сможем принять решение, выводить ли нам поле3. А это уже нелокальность, приходится помнить много лишнего.
allter: (Default)

[personal profile] allter 2017-03-16 11:18 am (UTC)(link)
В целом согласен.

Кстати, я сейчас подумал, что для соблюдения семантической локальности в JSON-протоколе нужно принять соглашение, что бы единичные сущности внутри потока были описываемыми в виде object с ненулевым количеством ключей. Т.к, например, если мы каким-то инструментом (специализированным, интересно - есть ли уже такой?) находим строку


{ "filename": "пример ошибки в файле:33:44", "line": 2, "column": 4 }


то эта строка описывает именно некий объект (семантически-похожий на "упоминание места в файле"), т.к. если бы эта строка была в другой строке, то кавычки были бы заэскейплены.
Edited 2017-03-16 11:20 (UTC)

[personal profile] anonim_legion 2017-03-16 07:05 pm (UTC)(link)
Как часто требуется обрабатывать преогромные деревья, да еще и сортировать их?

Для XML существуют SAX-парсеры, вполне себе работают на многогигабайтных данных.

[personal profile] anonim_legion 2017-03-16 07:23 pm (UTC)(link)
От слова "монада" перекосит тех, кто писал (и работает с) bash. Если у них уж скобки в if должны непременно отделяться пробелами, то какие уж тут монады...

На этом месте мне вспомнился чудовищных размеров флейм с КЫВТа, где люди (в том числе те, кто компиляторы пишут, а не энтерпрайз какой) спорили о сравнительном удобстве и производительности между передачей кодов ошибок, как в Golang и исключениями, как много где. В теории, исключения лучше, если они реализованы правильно, и особенно они хороши в случае вызовов с в 40 уровнями вложенности в стеке, когда программист попросту устанет передавать ошибку наверх руками.

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

Чего нет в типичных пайпах - так это общепринятых границ сообщений. Даже для потоковых данных может появиться смысл в передаче данных некими кусками, chunk'ами. В виндовых named pipes есть механизм передачи отдельных сообщений, в юниксах же, насколько мне известно, подобное есть только в d-bus. Самостоятельно сделать разбивку на сообщения несложно, но - каждый раз это все равно нужно делать, и всякий будет делать это по-своему.

[personal profile] anonim_legion 2017-03-16 07:28 pm (UTC)(link)
Когда коран писали - минных полей не было. В наше время все компании стремятся к консолидации, чтобы на одного админа приходилась тыща серверов и все работало само. Админ всё это глазами не прочитает никогда.

И если сравнивать количество данных, переданных по нетипизированному unixway и по стандартизированному HTTP - НTTP выиграет в тысячи раз.
livelight: (Default)

[personal profile] livelight 2017-03-16 08:16 pm (UTC)(link)
Там нет проблемы 2, так что при хорошей структуре XML и соответствующих запросах, связка SAX + заточенный под запрос фильтр вполне справляется на ограниченной памяти.
livelight: (Default)

[personal profile] livelight 2017-03-16 08:31 pm (UTC)(link)
HTTP, конечно, стандартизованный, только вот человеку читать передаваемый по нему какой-нибудь SOAP глазами - теоретически возможно, но на практике проще застрелиться. В этих условиях всякий там Binary JSON и прочий Diameter были бы гораздо лучше, но на всякий случай все держат фигу в кармане: а вдруг человек изволит снять дамп (или просто поднять логи кровавого тырпрайз-сервера, который с другими такими же общается по SOAP) и почитать его глазами?
ext_646638: (Default)

[identity profile] rdia.livejournal.com 2017-03-17 04:41 am (UTC)(link)
> основная, это отсутствие типизации.

К сожалению, как вам там уже сказали, это не универсально. Unix cli - это полуавтомат, который может работать с ошибками. Строгая типизация - это штука, которая по определению работает без ошибков. А так, конечно, Haskell смог бы заменить bash.

> Всего машиночитаемого в обработке ошибок - единственный int, который относится не к куску данных, а ко всей программе.

Это было изящно решено товарищами из Digital в их операционных системах. В последней - OpenVMS оно выглядит так:

$ HELP/MESSAGE/LIBRARY=TOOLS:[MYPROJ]MYMESSAGES.MSGHLP$DATA ACCVIO
%MSGHLP-F-MDFERR, error accessing Help Message database file "TOOLS:[MYPROJ]
MYMESSAGES.MSGHLP$DATA"
-RMS-E-FNF, file not found
-SYSTEM-W-NOSUCHFILE, no such file

Ошибки - это строки с процентами и "-" в выводе. Т.е. сначала даётся машино-человеко-читаемый код %MSGHLP-F-MDFERR (подсистема-серьёзность-ошибка), после которого идёт нормальное описание.

Page 1 of 3