Pull to refresh
9
1.2
Эд Нерский @ednersky

Зрящий в суть, указующий путь.

Send message

Непонятно ещё как эти штрафы взимать. Ну то есть, если например превысить скорость на машине - штраф прилетает владельцу. А если с публичного wifi кто то с чего-то зайдёт?

а нужно обратиться к опыту западных коллег

когда в Германии стали бороться с торрентами, то там провайдеры тупо стали присылать счета за факты скачивания, причём анализировали даже и то, ЧТО ты скачал: шиндовс 200 евро? значит в счете будет 200 евро + штраф. солидворкс за 10 тыс евро? значит в счёте будет 10 тыс евро и штраф.

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

а заодно и неаонимный доступ в сеть канул в Лету

как-то так будет и у нас.

в России всегда всё как на западе, просто с большим запозданием

вот лайк, мне эти руки на рельсах тоже сразу в голову пришли

если не планируется, значит рано или поздно, но точно введут

Пусть есть простая задача: прочитать файл и вывести все строки, которые содержат "goto".

Типичное решение:

на Perl это будет так (читаем из stdin или всех переданных программе файлов):

map {print} grep /goto/, <>;

Ну или если планируется добавлять условий помимо goto, можно развернуть в цикл:

Добавим, например, второе условие что длина строки должна быть больше 10:

while(<>) {
   next unless /goto/;
   next unless length > 10;
   print
}

Конечно это опять можно переписать в функциональном стиле:

map {print} grep { length > 10 } grep /goto/, <>;

Но при увеличении числа проверок в какой-то момент функциональный стиль становится менее читабельным, хотя и остаётся более лаконичным.

Правильный код:

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

Обработка отделена от вывода и не содержит магических констант ("goto")..

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

и, кстати, о константах. я всегда был противником давать некоторым константам имя. люди пишут

const seconds_in_minute = 60

И возникает ощущение, что они всерьёз планируют попасть в мир, где эта константа будет иметь иное значение.

Всегда считал такое программирование ошибочным.

да это другой аспект, нежели тот, о котором Вы говорите, но темы очень смежные и отчасти пересекающиеся:

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

то есть усилия, что вы приложили, на отделение констант можно не прилагать (или если возникнет ОБЪЕКТИВНАЯ необходимость - приложить их завтра: ну а чел, определивший в константе количество секунд в минуте, может заняться рефакторингом, когда случится попаданство в иной мир)

как-то так

Можно менять источник данных и/или формат вывода, не трогая бизнес-логику.

А можно это делать только по мере накопления реальных задач.

как там Пётр первый говорил? Получив приказ от начальства - не спеши его исполнять: могут ведь и отменить!

а представьте какой это ад для перфекциониста?

ну да перфекционизм я всегда считал болезнью

а массив - это список хранимых сущностей. выше же есть пример.

нужно от всех людей отобрать мужчин? - дерево

нужно найти человека по email? - дерево

нужно найти человека по ФИО? - дерево

а все эти деревья ссылаются на общий "список людей", то бишь массив.

Это долго для unit тестов.

ага, но это "долго" совершенно некритично.

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

да но я выше говорил, что разбиение на такие функции приводит к увеличению числа строк - снижению лакончичности.

кроме всего прочего базы данных "умные". Умеют в такие вещи, как триггеры, хранимые процедуры и так далее. Потому описанный Вами подход, что функция выполняет подготовку для записи давно слабоактуален.

Как по мне, чистых функций можно выделить не более 1% от общего объёма. И это выделение будет стоить плюс столько же.

а тестировать нужно всё

PS: мои оценки касаются микросервисного стейтфул (микросервис обращается к БД) программирования

а ещё отключать банковские карты

«пусть негодяи с голода мрут!»

бизнес-цель - ин-мемори кеш для микросервиса, например

ну а почему предлагаю именно её — Вы же примеры с деревьями выше делали? делали

а это самое очевидное применение деревьев — быть индексом для поиска

я ничего не понял. Можно пример?
сделайте массив а над ним дерево. и чтоб в этот массив можно было replace элемента сделать, а дерево бы пересчиталось.

или сложно получится?

Такую функцию можно разбить на две. Одна делает только получение значений из БД или текущего времени. Её не надо тестировать. А в другой вся бизнес-логика. Это чистая функция.

неа

на практике у вас получится что вы будете бить фунции по пять строк на две по четыре и по три (4+3 > 5 из-за интерфейсных заморок).

ну и тестирование бизнеслогики без БД смысла имеет мало. имеет но мало.

гораздо дешевле тестирование делается так:

  • стартуем на пустой БД

  • перед тестом генерируем пару нужных записей

  • тестируем интеграционно

то есть по количеству кода это меньше, но результат тестирования заодно проверяет вообще всё, включая коннектор к БД.

Вероятность ошибок в подобных библиотеках близка к нулю.

я говорил или нет, что самая востребованая либа в гошке - парсер ямл, не находится в списке стандартных либ, а является "сторонней"?

нет? ну вот говорю

Вроде, в Python есть и map, и filter.... Только кто их будет использовать, если получается длиннее, и с первого раза сложно написать без ошибок.

Да в питоне лямбды неполноценные и вообще оператор.

заморока с форматированием блоков пробелами - отсюда всё растёт. Кстати у хацкеля местами тоже пробелы важны.

Вот в Haskell удобно использовать map и filter

дык язык заточенный под лямбды

в Perl кстати тоже удобно. Вот простой парсер CSV, читающий из STDIN и складывающий результат в массив (массивов). Пустые строки пропускаются.

my @csv = map{[split /[,;]/]} grep /\S/, <>;

Вряд ли аналог на хацкеле уложится в такое же количество символов :)

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

Чтобы убрать все эти ифы, придумали монаду Either.

В Гошке её нет, в Rust тоже. Да в Rust нарисовали вопросик, но "песок не лучшая замена овсу".

При наличии исключений (способа путешествия ошибок по стеку вне mainflow) монады тоже нафиг не сдались.

Это зависит от того, как делить код на функции.

а как ни дели. если микросервис ходит в БД, то идемпотентными будут разве что функции конверсии объектов в JSON и обратно. И эти функции никто не пишет - все библиотечными пользуются.

погодите, это не ввод-вывод.

зачем нужно дерево? чтобы упорядочивать какие-то данные.

например имеется таблица

id, имя, email, дата рождения, пол

и вот нужно внутри неё быстро находить записи по значению email или даты рождения.

соответственно будет 3-4 дерева: ссылающиеся на id, на имена, на email и так далее.

природа базы данных такова, что вот этот CRUD (ссылка выше) даже сформулировали.

соответственно, работодатель Вам прямо задачу сформулирует: нужно мутабельное хранилище в памяти запрогать.

и как это планируется делать на языке, где мутабельных переменных нет?

Микросервис для доступа к БД использует некую библиотеку. Когда я тестирую микросервис, мне не нужно тестировать эту библиотеку.

  1. при чём тут библиотека?

  2. ошибки могут выявиться и в ней

  3. функцию, возвращаемое значение которой зависит от значений в БД и (или) текущего времени, требуется так же тестировать как и все прочие.

Мне нравится диалог с Вами. Было бы интересно его вживую, за пивом... Эх

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

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

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

Вот не нравится мне в Go и Rust отказ от исключений. Не нравится, что он обоснован э... околорелигиозными объяснениями.

И теперь в каждой первой функции люди вынуждены через строку выписывать

if err != nil {
     return nil, err
}

Вот, кстати, если посмотреть на этот код, убрав его внутрь компилятора, то что мы получим? Правильно, исключения!

Я к чему это говорю? К тому, что как по мне это даже хуже, чем вот это функциональное задротство. Функциональщина, по крайней мере, столько людей не покалечила, сколько Go и Rust.

При этом я сам люблю функциональщину.... Когда она выразительнее императивщины - прям очень.

Согласно Вашей теории, если ООП язык использует что-то не из ООП, то он продолжает оставаться ООП языком.

Погодите, ну я же прямо выше с Java пример обратного приводил!

Я вам писал: Java везде использует ООП и потому является ООП языком. Там даже Hello, world нельзя неООП написать. А в Python такой подход возможен, но коммюнити его не использует, а потому считаем Python не ООП языком. Ну вот прям такой текст выше.

Если бы коммюнити Python надрачивало бы на ООП, всё было бы как в Java.

А если ФП язык использует что-то не из ФП, то он перестаёт быть ФП языком.

Так, ну давайте восстановим контекст. Мы обсуждаем противопоставление парадигм.

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

И говорил что мне всегда нравились принципы TMTOWTDI и KISS. Причём первое я считаю подтипом (или способом реализовать второе).

Несмотря на то, что Python явно декларирует отказ от принципа TMTOWTDI, но у них ничего не вышло (и хорошо что так). Вот опять же пример с ООП как бы намекает на то, что не вышло.

И ни в одном процедурном языке не получилось.

Теперь, если вернуться к противопоставлению ФП vs ПП, то у нас получается:

ПП - вбирает в себя все парадигмы, потому я выше предпочитаю называть такие языки "универсальными"

а ФП предпочитает оставаться в рамках строго одной парадигмы.

Причём (выше обсудили) не важно по какой причине: ограничения компилятора или коммюнити.

Мне кстати Rust не нравится вот этим коммюнити. Если Go отказались от исключений и теперь их в каждой функции вручную прогать, то Rust ещё отказались и от менеджмента памятью и теперь ещё возиться и с памятью. Капец.

А обоснования всего этого околорелигиозные.

При определении стиля Вы собираетесь использовать ту же логическую ошибку, описанную выше?

ну логическая ошибка (как описано выше) у Вас. Я прямо противоположное писал.

Если Вы хотите сместить акцент с ФП на декларативное, так я только За буду.

Вот SQL - больше декларативное программирование, нежели функциональное (хотя по сути и то и другое).

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

начинаешь выворачивать подобные вещи на декларативном языке (а там в реальности ещё условий набрасыывается: признаки заблокированности, отключённости, обработка ошибк итп) и потом в какой-то момент плюёшь на мегабайтные простыни декларативки и пишешь просто

user1 := (SELECT * FROM users WHERE id = $user1);
user2 := (SELECT * FROM users WHERE id = $user2);
if куча условий THEN
   RETRURN какой надо формат возврата
END
user1 = (UPDATE users SET ... WHERE id = user1.id RETURNING *);

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

Хотите ещё пример НЕлаконичности декларативности? Их есть у меня.

Вот был у нас SysVInit. Среднему пользователю приходилось знать bash. Что такое bash? Это язык о где-то десяти (прописью: десяти) нюансах.

Вот у нас теперь декларативный systemd. Сейчас в его справочнике уже порядка 6500 ключевых слов. То есть ключевых слов, описывающих юнит может быть около 6500! Да в этом справочнике тупо не найдёшь нужного, как оно понадобится!

This index contains 6467 entries in 24 sections, referring to 413 individual manual pages.

Как думаете, стоило это адище декларативности разводить? В момент, когда на него переходили была заявка про зависимости, кажется, и простоту. Зависимости отлично в SysVInit введением списка зависимостей делали (и сейчас делают), а вот насколько проще был init, сегодняшние пользователи и представить себе не могут.

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

пусть так, но полезных программ на Хацкеле нет. а на С++ до чёртиков.

впрочем, я много раз об этом говорил.

потому что даже отрицая TMTOWTDI на самом деле не получается от него отвернуться. Новичок будет использовать простой императивный стиль с h-c файлами (hpp-cpp). когда ему понадобится класс, он введёт парочку. Когда шаблоны - снова нарастит сложность. и так далее.

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

Для тестирования чистой функции нужны только значения аргументов

  1. чистые функции встречаются и в ФП и в ПП

  2. доля чистых функций (моё имхо) при реальном программировании: микросервисы, десктопные программы, сервера, БД, итп - не более 1% в коде. Потому, кстати, некоторые проекты отказываются от юнит-тестов в пользу интеграционных. то есть даже когда нужен юнит-тест, всё рано он оформлен как интеграционный.

Мне вот (чисто субъективнo) в данном случае визуальный язык нравится больше.

Я не против, очень даже за

я вопрос о другом задаю: что мешает визуалке "под капотом" генерить не странный текст, завязанный строго на одного производителя, а нечто, что условно "все знают": Си, Python, ASM на худой конец.

или это как раз способ привязки к продавцу: если это начал использовать, то на другой контроллер код просто так не перенести?

ну нет же.

речь не о луддизме, а о лаконичности.

если на АСМ можно писать понятнее, лаконичнее, то АСМ лучше С. Но это не так. Потому Ваш пример тут - пример луддизма: "я не хочу/не могу переучиваться к новым реалиям".

Си позволил радикально короче записывать те же конструкции, что были на ASM

а ASM позволил унифицировать и визуализировать коды и перфоркарты

а что делает вот этот язык? в чём его профит, кроме того, что он генерируется визуалкой?

  • визуалка может генерировать любой код, в том числе ASM

  • почему не генерить C?

или, скажем, почему не Python?

ну именно это уточнение я же и внёс чуть ниже.

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

Вот отвлечённая аналогия про ООП:

Например возьмём Java. Там так выглядит, что не объектно-ориентировано и писать-то нельзя. Даже для Hello, world определяем классы:

class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

Однако возьмём скажем Python. На нём можно писать и ООП и не писать ООП. Для Hello, world можно и функцию не определять!

Заглянем в его библиотеки (или, как они любят говорить, - батарейки). Что видим? Где ООП необходим, там он есть. Где он не обязателен, там его и нет.

Можем говорить, что Java - ООП язык, а Python - нет. Но если бы подавляющее большинство кода на Python писалось бы в ООП-стиле, то мы б его называли ООП.

Ну пришёл ты такой в Python коммюнити и выложил библиотеку не в ООП-стиле, а тебя джентельмены на смех подняли. Как-то так же было бы?

(вот, кстати, как-то автора Actix в Rust травили всем коммюнити, он даже порывался закрыть реп и бросить разработку - настолько токсично коммюнити требовало избавиться от unsafe где-то в кишках. Даже статьи на хабре были, вот: https://habr.com/ru/articles/484436/).

Rust мог бы быть функциональным, но его коммюнити вполне толерантно использует mut.

Однако коммюнити жёстко наяривает на вот эти вот "ссылки", "владение" и прочее управление памятью. Потому Rust - универсальный язык с задротством в управление памятью. Как-то так.

Э ну да, в первом случае у Вас лаконичнее получилось.

Вопрос, а где здесь находится собственно этот самый АДТ?

PS: Я бы с наследованием тут не заморачивался, а сделал бы нечто такое:

type Node struct {
     Value    *int
     Children []Node
}

Конечно, возможен был бы случай, когда пользователь заполнил бы одновременно и Value и Children, но я б на него тупо забил бы категорическим заявлением "не возможно" :)

Ну вы поняли.

Тут два метода работы: если код мы контролируем полностью (мы его и пишем и используем), то категорических заявок достаточно.

Если имеются клиенты, им выдаём конструктор "не позволяющий" ненужное и тоже успокаиваемся.

Ну да, Go'шка многословная (особенно в определении типов), а на Rust (универсальный язык), уже есть оператор enum позволяющий в то же самое:

pub enum Node<T> {
  Nil,
  Leaf(T),
  Children(Box<Node<T>>, Box<Node<T>>)
}

Да не три строки, а целых пять, с учётом скобок, ну и наружу выставлена возня с памятью (Box), ну да цель существования Rust - чтобы пользователь (программист) уделял возне с памятью максимум внимания.

Ну а функция определения размера будет вточь как у Вас: match на три варианта Nil - вернёт 0 и так далее. Вместе с объявлением функции будет опять пять строк, а не три.

Ну да ладно. Golang в этой задаче с треском проигрывает. Rust проигрывает меньше. Но тоже в пролёте.

Ответьте на вопрос:

Вот этот enum в Rust это АДТ или не АДТ?

Ну и если мы продолжим дискуссию ФЯ vs ПЯ, то наиинтереснейший вопрос в этом примере - МОДИФИКАЦИЯ дерева.

Предположим, что это самое дерево используется как индекс в БД.

Предположим в Вашей базе данных на старте миллиард записей.

Пользователь делает поиски и всё у него хорошо, Вы описали поисковую функцию, которая возвращает ответ за O(log N).

Однако база данных - не только чтение, но и запись. А здесь, напомню, часто выделяют CRUD-операции.

Что Вы бы делали в ФЯ, когда пользователь вызывает update, create или delete?

А когда Вы из одной группы языков берёте единственный без изменяемых переменных, а из другой - единственный с АТД

насколько я понимаю в обоих случаях имеются в виду ФЯ.

но я сказал: на роль сравниваемых подойдут С++ и golang

а на роль противоположности - хацкель. ну ещё можно F# этот рассмотреть, но он, ЕМНИП работает на байткоде, а не компиляции.

и на основании этого сравнения делаете выводы обо всех языках

давайте от понятия пойдём. сформулируем его потщательнее.

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

однако Вы привели пример ФЯ у которого есть и мутабельный синтаксис. поэтому предлагаю скорректировать определение Википедии.

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

Тогда

  • F# будет универсальным, НО функциональным (по расширенному определению)

  • Хацкель будет функциональным (по базовому определению)

  • LISP, кстати, будет функциональным (по базовому определению)

  • Rust/C/C++/Perl,Python/Php/Lua будут универсальным

Rust мог бы стать функциональным (Вы, по крайней мере, хотели бы его таковым видеть), но не стал: тыкаем в большинство прикладных библиотек и видим мутабельность.

Причём Rust зачастую процедурен даже там где мог бы быть функционален. Помимо условно сложных (хотя они базовые) http, мутабельность используют многие стандартные модули, базовые, встречающиеся повсюду, вот, например std::fmt.

Далее, что мы видим?

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

Когда Вы сравниваете два языка по производительности, а в сравнении другой пары языков отказываетесь учитывать производительность, то это называется подтасовкой.

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

Вот возьмём нейтральный Lua и сравним его между собой. Есть Lua, есть реализация Lua на golang, есть LuaJIT.

По производительности, Lua - 1, LuaJIT - 5, GoLua - 0.1.

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

Потому если хотите сравнивать, то нужно сравнивать одинаковые технологии: JIT будем сравнивать с JIT, интепретацию с интепретацией и так далее.

Хацкель можно сравнивать с С++ и golang. Как-то так.

Либо Вы считаете производительность важным критерием, и тогда все интерпретируемые языки хуже, чем Haskell.

ну конечно я считаю её критерием, но не считаю её критерием когда идёт сравнение несравнимого. см. выше.

Потому, кажется, компромис выглядит следующим образом:

берём язык умеющий в две технологии сразу и сравниваем по производительности обе технологии внутри него.

Вот умеет Perl и в goto и в map'ы. Можно сравнить две реализации парсеров - функциональный и процедурный.

Вот умеет F# или Rust в мутабельность и иммутабельность, можно сравнить две реализации парсеров на нём.

Вот Хацкел не умеет в мутабельность, его мы можем сравнивать только с близкими аналогами: Rust, C++, Golang.

Ну и если сравнивать, то вводить второй критерий - читабельность/лаконичность.

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

Может быть, Вы бы взялись на примере F# нечто подобное изобразить?

Нет. Изучите матчасть.

ну я не вижу в нём сравнения, хоть убейте.

Де факто мой код делает (2) strchr. Просто ищет ноль, предполагая, что строка может содержать в себе нули, разумеется. Можете преобразовать свою программу на поиск первого вхождения символа A вместо 0?

for (i = 0; str[i] != 'A'; i++); return i;

"Колбечный адище" не имеет никакого отношения к монадам. Вы пробовали отлаживать код с монадами?

смотря, что Вы вкладываете в понятие "отлаживать код".

если работу с дебаггером/отладчиком или периодический запуск и распечатка результатов, то я такой фигнёй занимался в последний раз лет 30 назад.

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

В этом месте, кстати, мутабельность снова рулит и бибикает, поскольку внутри тестов позволяет сделать, например, инжекшены, чтобы упростить моделирование ситуации.

Да. Упрощённо программу можно представить в виде "ввод - вычисления - вывод". Логика ввода и вывода обычно простая.

гладко оно бывает на бумаге, да вмешиваются овраги.

предположим, Вы пишете микросервис в своей работе, опирающийся на базу данных. Вот хоть в хабрахабр Вы программистом работать устроились. Увы, задач, которые можно сводить к чистой идемпотентности будет куда меньше всего остального. Да и тестов, которые можно сводить к Unit тоже (к вопросу о тестах в Хацкель: Unit мы там нашли, а что по интеграционному тестированию? Но да ладно, не будем заострять).

Знаете как такие штуки тестируются?

Берётся собирается тестовый кластер (такой же как боевой, но пустой), наполняется тестовыми данными, затем некий робот изображает пользовательскую активность. Как-то так.

есть ещё крайне некрасивый метод, называется "мокать мир", когда база данных заменяется её симулякром, а главное соседи по кластеру - тоже. А ещё время при этом идёт вперёд подконтрольно (то есть тоже мокается).

Но мне идея с моками категорически не нравится, потому что очень легко "мокая мир" прийти к ситуации: "тесты проходят, а в продакшене падает".

Как-то так.

1
23 ...

Information

Rating
1,570-th
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Backend Developer, Fullstack Developer
Lead
From 1,000,000 ₽
Python
Perl
PostgreSQL
Tarantool
Linux
Lua
C
JavaScript
HTML
Rust