Как стать автором
Обновить
36.07

Функциональное программирование *

От Lisp до Haskell

Сначала показывать
Порог рейтинга

Посвящается нашим любимым @ednersky , который стойко обороняет свою Перламутровую Башню из слоновой кости от нападок ржавых..., ржавых... терминаторов и @IUIUIUIUIUIUIUI, без устали занимающегося продвижением наших главных добродетелей: лени, гордыни и самомнения.

Вообще говоря, было бы неплохо обсудить тестирования с нашей точки зрения с учётом изменившегося ландшафта, то есть LLM (больших языковых моделей).

Как известно, многие используют нейросетки для написания unit тестов. Типичная фраза: «я написал код, а потом ChatGPT мне за несколько секунд добавил тесты». Или «код писать ИИ я не доверяю, но тесты он пишет хорошо». Если честно, меня, когда я это читал, не оставляло ощущение какой-то неправильности, однако выводить в рацио было, право слово, лень.

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

Unit тесты бывают разных типов, не только тесты свойств (property-tests, QuickCheck, etc). Там, вы представляете, коллеги, во мраке и ужасах, во владениях жуткого холоднокровного, которое душит и глотает, люди пишут тесты вручную, на, извините за выражение, assert'ах. И эти несчастные, вынужденные работать день и ночь в поте лица своего, разумеется, хотят хоть немного облегчить свою участь. Как известно, в подобной ситуации Резерфорд помрачнел и раздраженно спросил: — Послушайте, а когда же вы думаете?

Но у нас время есть, поэтому за них подумаем мы. Итак, какие вообще есть цели у unit-тестов, написанных на assert'ах? Главная, разумеется, это ИБД, чтобы не выгнали из гестапо за жестокость, а побочных две:

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

  2. Проверка кода при написании.

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

Как несложно вспомнить из курса матана, ну тем, конечно, у кого он был, для проверки линейной функции одного агумента нужно взять две точки; для проверки квадратичной — три, и так далее. Количество точек не то, чтобы быстро, но растёт с увеличением сложности функции и размерности конфигурационного пространства. Уже для квадратичной функции двух переменных нужно 6 точек, а ведь эта функция преспокойно разместится на двух строках. Страшно представить, сколько точечных тестов нужно для типичной простыни на два экрана из кровавого ынтерпрайза и типичным набором из 6-7 аргументов.

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

Про вторую из побочных целей при условии использования LLM, и заикаться-то странно, как мы понимаем. Ведь если unit-тест может написать автоматика, которая из всего контекста имеет лишь код тестируемой функции, то всё, что она может сделать — это переписать исходный текст в немного изменённом виде. Поэтому если мы написали функцию, складывающую 2 и 3, при этом получающую 6, то LLM может написать, что 3 + 2 = 6 или, что 6 - 3 = 2.

И дело тут, разумеется, не в недоразвитости LLM, нет, дело в контексте, который у неё есть. Ну не родятся от осины золотые апельсины. Чтобы что-то проверить, нужна избыточная информация помимо уже написанного кода. Когда кодер пишет тест для своего текста на assert'ах вручную, он лишь имеет небольшой шанс её добавить. Поэтому здравомыслящие люди, работающие в поте лица своего с использованием C в embedded, давно уяснили, что писать тестируемый код должен один программист, а тесты к нему — другой.

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

Теги:
+1
Комментарии0

Principles and Practice of Programming Languages 

Новый зверь среди академических учебников.

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

Теги:
+7
Комментарии0

Я в прошлом разработчик. Поработал во многих ведущих ИТ-компаниях России. После того, как был разработчиком, работал системным аналитиком, продуктовым менедежром. Лет 12-15 назад начал увлекаться вопросами обучения детей математике. Писал даже года 3 назад на Хабре статью о том, как это мое хобби превратилось в профессию. Теперь это еще и область научных интересов: поступил в аспирантуру на мехмат МГУ на кафедру методики преподавания математики.

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

Пример. Можно воспринимать примеры "2 + 3 = 5", "2 = 5 - 3" и "3 = 5 - 2" по отдельности, тогда знак равно будет знаком дейсвтия. А можно как одно отношение между числами 2, 3 и 5, например, 2 + 3 = 5. А вот тут теперь знак равно имеет смысл тождества.

Развивая теорию, которая могла бы описать такие переходы от алгоритмов к отношениям и которая могла бы быть основной для построения педагогических методик, я вдруг вспомнил, где я это все видел!!!! Функциональное программирование и ООП!!! У каждой из этих двух парадигм есть свои плюсы и минусы. Функциональная хороша для освоения, для быстрого создания чего-то небольшого, для кодирования чего-то большого, когда есть высокая степень определенности, а планируемые изменения не слишком велики. ООП - хорошо, когда планируются структурные изменения. По крайней мере, так было лет 10 назад. А структурные изменения - это именно то, что происходит при изучении математики в средней и старшей школе и далее.

Теги:
+3
Комментарии4

Знаете как часто это бывает, когда разработчики говорят что мой код, который я написал полгода назад сейчас выглядит отвратительно. Знакомо? Через это проходят все, кто так или иначе начинает заниматься разработкой и нарабатывает опыт в свои первые годы. Но сколько это может продолжаться?

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

Смотря назад, я понимаю что поворотным моментом в моей карьере стал период, когда я увлекся функциональными языками и курсом СИКП, по которому потом во многом строилось обучение на Хекслете. Произошло это так, я видел что вокруг меня, многие профессионалы, говорят и используют функциональные языки и какие-то связанные с этим подходы. В тот момент речь шла про erlang, scheme/racket (для сикпа), clojure и haskell, которые я в разной степени начал изучать. На эрланге даже получилось сделать проект codebattle.hexlet.io, который потом, спустя много лет переписали на elixir (это опенсорс).

Изучение этих языков многое перевернуло в моей голове и дело даже не в том что они функциональные, а в том, что в среде программистов на этих языках поднимались вопросы, с которыми я раньше не сталкивался. Я понял что смотрел на разработку не на том уровне. Весь мой предыдущий опыт был скорее в стиле вот чистый код, вот мартин, вот фаулер, вот ООП, вот паттерны. Как оказалось, несмотря на здравые мысли в этой части, все же это была оболочка, а не фундамент. А фундамент лежал в таких областях как управление состоянием, изоляция побочных эффектов, конечные автоматы, statefull/stateless части системы и тому подобное.

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

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

p.s. Делюсь опытом программирования в своем телеграм-канале организованное программирование

Теги:
Всего голосов 5: ↑4 и ↓1+3
Комментарии1

Functional FizzBuzz

public class FizzBuzz
{
    public static void Main(string[] args)
    {
        Console.WriteLine(FizzBuzzProgram(350));
    }
    public static string FizzBuzzProgram(int n) =>
        String.Join("\r\n", 
            Enumerable.Range(1, n).Select(FizzBuzzPipeline));

    static readonly Func<int, string?>[] handlers = [(n) => n % 3 == 0 ? "Три" : null, (n) => n % 5 == 0 ? "Пять" : null];
    
    public static string FizzBuzzPipeline(int i) => Counter(i, String.Join("", handlers.Select(f => f(i))));

    public static string FizzBuzzPipelinePar(int i)
    {
        var results = handlers.AsParallel().Select(handler => handler(i)).Where(r => r != null);
        return results.Any() ? string.Concat(results) : i.ToString();
    }
}

Насмотрелся тут Скотта Влашина https://youtu.be/ipceTuJlw-M?si=ndSDvv-RWj8L1Ejt

и сделал свой функциональный FizzBuzz с массивом делегатов преобразований числа в Fizz & Buzz.

Этот массив расширяемый - пишите хоть 100 проверок, множество других решений на такое не способно.

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

Хотите попроще ценой потери части универсальности?

    static string FizzBuzzPipelineWOHandlers(int n)
    {
        string result = $"{(n % 3 == 0 ? "Fizz" : "")}{(n % 5 == 0 ? "Buzz" : "")}{(n % 7 == 0 ? "Qux" : "")}";
        return String.IsNullOrEmpty(result) ? n.ToString() : result;
    }

Ката закончил, поклон.

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии5

Я зашел в Google Trends чтобы посмотреть популярность запроса "Mathematica". Меня, как большого фаната технологии, график очень расстроил.

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

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

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

Я надеялся на то, что с появление бесплатного Wolfram Engine ситуация изменится, но эффект был очень слабым. Слишком поздно это произошло. В итоге все равно оказалось, что лицензия запрещает коммерческое использование бесплатного ядра. А значит компании не заинтересованы внедрять этот язык и нанимать инженеров, которые им владеют. Значит нет вакансий и нет интереса со стороны разработчиков и инженеров и нет смысла его изучать и оставаться в нем.

Mathematica в Google Trends
Mathematica в Google Trends

Теги:
Всего голосов 3: ↑3 и ↓0+3
Комментарии6

Утечки памяти преследовали программы на языке С с тех пор, как существует этот язык. Было предложено множество решений, вплоть до идеи переписать программы на языке C на других языках. Но есть лучший способ.

Здесь представлено простое решение, которое устранит утечки памяти в каждой программе на языке C. Используйте этот модуль с вашей программой, и утечки памяти останутся в прошлом.

#include <dlfcn.h>
#include <stdio.h>

struct leaksaver {
        struct leaksaver *next;
        void *pointer;
} *bigbucket;

void *
malloc(size_t len)
{
        static void *(*nextmalloc)(size_t);
        nextmalloc = dlsym(RTLD_NEXT, "malloc");
        void *ptr = nextmalloc(len);
        if (ptr) {
                struct leaksaver *saver = nextmalloc(sizeof(*saver));
                saver->pointer = ptr;
                saver->next = bigbucket;
                bigbucket = saver;
        }
        return ptr;

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

Every allocated pointer is saved in the big bucket, where it remains accessible. Even if no other references to the pointer exist in the program, the pointer has not leaked.

It is now entirely optional to call free. If you don’t call free, memory usage will increase over time, but technically, it’s not a leak. As an optimization, you may choose to call free to reduce memory, but again, strictly optional.

Problem sovled!

Теги:
Всего голосов 6: ↑3 и ↓30
Комментарии7

​​?️️️️️️ Каррирование и частичное применение

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

И частичное применение, и каррирование, реализуются как функции, принимающие в качестве параметра другую функцию.

Частичное применение — функция partialApply, принимающая первым параметром функцию — fn, а остальные параметры — часть параметров функции fn. Функция partialApply возвращает функцию, которая в качестве параметров принимает недостающие аргументы функции fn.

Каррирование — функция curry, которая принимает единственный параметр — функцию fn, и возвращает каррированную функцию fn.  Можно сказать, что каррированная функция fn — функция аккумулятор, которая будет накапливать переданные аргументы до тех пор, пока не будет передано достаточно параметров для вызова исходной функции. Параметры можно передавать в любом количестве.

Подробнее

https://t.me/cherkashindev/132

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии4

3 июля 2023 г., спустя полтора года вышла новая версия языка функционального программирования Koka. Несмотря на минорность версии в новом компиляторе внедрено изобретённое его авторами Full In-Place Calculus. Если коротко, то суть FIP в следующем: у нас есть чистая функция, производящая деконструирование объекта данных, а затем вновь конструирующая объект данных. Например, это функция трансформации списка или дерева. Так вот при таких манипуляция в памяти происходит создание новых объектов данных, которые затем и используются, а старые остаются там до тех пор, пока их не удалит за ненадобностью сборщик мусора (кстати говоря, язык Koka не использует сборщик мусора). FIP же позволяет производить проверку безопасности переиспользования памяти. Что и было реализовано в новой версии Koka. Теперь вы можете помечать функцию ключевым словом fip или fbit (FBIP техника, предложенная другими авторами), использовать разрушающий match! и получать описанный выше эффект. Так же, по-видимому, в связи со внедрением FIP появился borrowing (владение), которое также участвует в анализе кода на безопасность. Из приводимого в статье результата benchmarks видно, что новый подход увеличивает производительность программ на Koka и приближает её к таковой на C или C++.

Почитать о Koka можно в этом посте.

Рейтинг0
Комментарии0

Вклад авторов