Субъективное видение идеального языка программирования

    Дальнейший текст — моя точка зрения. Возможно, она позволит кому-то по-новому взглянуть на дизайн языков программирования или увидеть какие-то преимущества и недостатки конкретных фич. Я не буду лезть в частные подробности типа "в языке должна быть конструкция while", а просто опишу общие подходы. P.S. У меня когда-то была идея создать свой язык программирования, но это оказалось довольно сложным процессом, который я пока не осилил.


    Влияние предыдущего опыта


    На написание статьи меня вдохновила вот эта статья. Автор придумал свой язык программирования, и этот язык своим синтаксисом и особенностями оказался подозрительно похожим на Free Pascal, на котором и была написана реализация ВМ для языка. И это не совпадение. Языки программирования, на которых мы раньше писали, загоняют мышление в рамки языка. Мы сами можем не замечать этого, но сторонний наблюдатель с иным опытом может посоветовать что-то неожиданное или сам научиться чему-то новому.


    Рамки мышления немного раздвигаются после освоения нескольких языков. Тогда в языке А вам может захотеться иметь фичу из Б и наоборот, а ещё появится осознание сильных и слабых стороны каждого языка.


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


    Мой опыт: когда-то я начинал с паскаля, впоследствии познакомился с Java, Kotlin, C++, Python, Scheme, а основными языком считаю Scala. Как и в вышеописанном случае, мой "идеальный" язык имеет много общего со Scala. По крайней мере, я отдаю себе отчёт в этом сходстве)


    Влияние синтаксиса на стиль кода


    "Писать на фортране можно на любом языке"


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


    Python:


    filtered_lst = [elem for elem in lst if elem.y > 2]
    filtered_lst = list(filter(lambda elem: elem.y > 2, lst))

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


    Scala:


    val filteredLst = lst.filter(_.y > 2)

    Имхо, это близко к идеалу. Ничего лишнего. Если бы в питоне можно было объявлять лямбды более коротким способом, хотя бы it => it.y > 2, то генераторы списков оказались бы не очень нужными.


    Самое интересное, что подход как в скале хорошо масштабируется в цепочку вызовов типа lst.map(_.x).filter(_>0).distinct() Мы читаем и пишем код слева направо, элементы идут по цепочке преобразований тоже слева направо, это удобно и органично. Вдобавок, среда разработки по коду слева может давать адекватные подсказки.


    В питоне в строчке [elem for elem in среда разработки до последнего не подозревает, какой же тип у элемента. Большие конструкции приходится читать справа налево, из-за чего эти самые большие конструкции в питоне обычно не пишут.


    ... = set(filter(lambda it: it > 2, map(lambda it: it.x, lst))))

    Это же ужасно!


    Подход с lst.filter(...).map(...) в питоне мог бы существовать, но он убит на корню динамической типизацией и неидеальной поддержкой сред разработки, которые далеко не всегда догадываются о типе переменной. А подсказать, что в numpy есть функция max — всегда пожалуйста. Поэтому и дизайн большинства библиотек подразумевает не объект с кучей методов, а примитивный объект и кучу функций, которые его принимают и что-то делают.


    Ещё один пример, уже на java:


    int x = func();
    final int x = func();

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


    let x = 1;
    let mut x = 1;

    Получается, что синтаксис языка реально важен, и должен быть по возможности простым и лаконичным. Язык должен изначально создаваться под часто используемые фичи. Антипримером можно назвать С++, где по историческим причинам определение класса раскидывается по паре файлов, а объявление простой функции может не влазить в строчку благодаря словам типа template, typename, inline, virtual, override, const, constexpr и не менее "коротким" описаниям типов аргументов.


    Статическая типизация


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


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


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


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


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


    Unit, void и отличия функции от процедуры


    В паскале/delphi есть разделение на процедуры (не возвращающие значений) и функции (что-то возвращающие). Но никто не запрещает нам вызвать функцию, а возвращаемое значение не использовать. Хм. Так в чём же разница между функцией и процедурой? Да ни в чём, это инерция мышления. Своеобразное легаси, переползшее в Java, С++ и ещё кучу языков. Вы скажете: "есть же void!" Но проблема в том, что void в них это не совсем тип, и если залезть в шаблоны или дженерики, то это отличие становится заметным. Например, в Java HashSet<T> реализовано как HashMap<T, Boolean>. Тип boolean — просто заглушка, костыль. Он там не нужен, в HashMap не требуется значение, чтобы сказать, что ключа нет. В С/С++ тоже есть нюансы с sizeof(void).


    Так вот, в идеальном языке должен быть тип Unit, который занимает 0 байт и принимает только одно значение (не важно какое, оно одно, и если у вас есть Unit, то это оно). Этот тип должен быть полноценным типом, и тогда компилятор станет проще, а дизайн языка красивее и логичнее. В идеальном языке можно будет реализовать HashSet<T> как HashMap<T, Unit> и не иметь никакого оверхеда на хранение ненужных объектов.


    Кортежи


    У нас есть ещё кое-какое историческое наследие, пришедшее, наверно, из математики. Функции могут принимать много значений, а возвращают только одно. Что за ассиметрия?! Так сделано в большинстве языков, что приводит к следующим проблемам:


    • Функции с переменным числом аргументом требуют специального синтаксиса — усложняется язык. Сделать универсальную функцию-прокси становится сложнее.
    • Чтобы возвратить сразу несколько значений, приходится объявлять специальную структуру или передавать изменяемые аргументы по ссылке. Это неудобно.

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


    Есть некоторые шаги навстречу типа std::tuple в с++, но как мне кажется, подобное должно быть не в стандартной библиотеке, а существовать прямо в системе типов языка и записываться, например, как (T1, T2). (кстати, на тип Unit можно смотреть как на кортеж без элементов). Сигнатура функции должна описываться как T => U, где T и U — какие-то типы. Возможно, кто-то из них Unit, возможо, кортеж. Честно говоря, я удивлён, что в большинстве языков это не так. Видимо, инерция мышления.


    Раз уж мы можем возвращать Union, можно полностью отказаться от разделения выражение/инструкция и сделать, чтобы в языке любая конструкция что-то возвращала. Подобное уже реализовано в относительно молодых языках типа scala/kotlin/rust — и это удобно.


    val a = 10 * 24 * 60 * 60
    val b = {
        val secondsInDay = 24 * 60 * 60
        val daysCount = 10
        daysCount * secondsInDay
    }

    Enums, Union и Tagged Union


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


    Во-первых, язык должен поддерживать легковесное объявление типов-перечислений. Желательно, чтобы в рантайме они превращались в обычные числа и от них не было никакой лишней нагрузки. А то получается всякая боль и печаль, когда одни функции возвращают 0 при успешном завершении или код ошибки, а другие функции возвращают true (1) при удаче или false (0) при фейле. На надо так. Объявление типа перечисления должно быть насколько коротким, чтобы программист прямо в сигнатуре функции мог написать, что функция возвращает что-то из success | fail или ok|failReason1 | failReason2.


    Кроме того, оказываются очень удобными типы-перечисления, которые могут содержать значения. Например, ok | error(code) или Pointer[MyAwesomeClass] |null Такой подход позволит избежать кучи ошибок в коде.


    В общем виде это можно назвать типами-суммами. Они содержат одно из нескольких значений. Разница между Union и Tagged Union состоит в том, что мы будем делать в случаях совпадающих типов, например int | int. С точки зрения простого Union int | int == int, так как у нас в любом случае инт. В общем-то с union в си так и получается. В случае с int | int tagged union ещё содержит информацию, какой у нас int — первый или второй.


    Маленькое отступление


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


    List(x) = Unit | (x, List(x))

    Ну почти как списки в лиспе.
    Если заменить тип-сумму на сложение (неспроста же он так называется), кортеж интерпретировать как произведение, то получится:


    f(x) = 1 + x * f(x)

    Ну или другими словами, f(x) = 1 + x + x*x + x*x*x + ..., а с точки зрения типов-произведений (кортежей) и типов-сумм это выглядит как


    List(x) = Unit | (x, Unit) | (x, (x, Unit)) | ...  = Unit | x | (x, x) | (x, x, x) | ...

    Cписок типа x = это пустой список, или один элемент x, или кортеж из двух, или ...


    Можно сказать, что (x, Unit) == x, аналогией в мире чисел будет x * 1 = x, так же (x, (x, (x, Unit))) можно превратить в (x, x, x).


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


    Короче, типы-суммы в языке нужны, и они нужны прямо в системе типов языка, чтобы нормально сочетаться с типами-произведениями (кортежами). Там получится целый простор для преобразований типа (A, B | C) == (A, B) | (A, C)


    Константы


    Возможно, это звучит неожиданно, но неизменяемость можно понимать по разному. Я вижу аж четыре степени изменяемости.


    1. изменяемая переменная
    2. переменная, которую "мы" не можем менять, но вообще-то она изменяемая (например, в функцию передают контейнер по константной ссылке)
    3. переменная, которую инициализировали и она больше не изменится.
    4. константа, которую можно найти прямо во время компиляции.

    Разница между пунктами 2 и 3 не совсем очевидна, приведу пример: допустим, в С++ нам в объект передали указатель на константную память. Если мы где-то внутри класса сохраним этот указатель, то у нас нет никаких гарантий, что в течение жизни объекта содержимое памяти по указателю не изменится.
    В некоторых случаях нам нужен именно третий тип неизменяемости — например, при чтении объекта из нескольких потоков или при вычислении чего-то, основанного на свойствах полученного объекта. Именно третий тип неизменяемости позволит компилятору проводить какие-то хитрые оптимизации. Пример использования — final поле в java.


    Лично мне кажется, что нюансы с изменяемостью 1-2 типа можно решить с помощью интерфейсов и отсутствующих геттеров/сеттеров. Например, у нас есть неизменяемый объект, который содержит указатель на изменяемую память. Вполне возможно, что мы захотим иметь несколько "интерфейсов" для использования объекта — и тот, который не даст менять только объект и тот, который, например, закроет доступ к внешней памяти.
    (Как нетрудно догадаться, тут на меня повлияли jvm языки, в которых нет слова const)


    Вычисления, производимые во время компиляции — тоже очень интересная тема. На мой взгляд, самый красивый подход используется в D. Пишется что-то вроде static value = func(42); и самая обычная функция явно вычисляется при компиляции.


    Фишечки котлина


    Если кто-то использовал gradle, то, возможно, при взгляде на неработающие build файлы вас посещала мысль типа "wtf? Что мне делать?"


    android {
        compileSdkVersion 28
    }

    Это просто код на языке Groovy. Объект android просто принимает замыкание { compileSdkVersion 28}, и где-то в дебрях андроид-плагина этому замыканию присваивается объект, в контексте которого реально будет запущено наше замыкание. Проблема тут в динамичности языка groovy — среда разработки не подозревает о том, какие поля и методы возможны в нашем замыкании и не может подсветить ошибки.


    Так вот, в котлине есть хитрые типы, и это можно было бы реализовать как-то так


    class UnderlyingAndroid(){
         compileSdkVersion: Int = 42
    }
    
    fun android(func: UndelyingAndroid.() -> Unit) ....

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


    Можно сказать, что это всё синтаксический сахар и вместо этого писать так:


    android { it =>
        it.compileSdkVersion = 28
    }

    но это же некрасиво! А если мы вложим друг в друга несколько таких конструкций? Подход как в котлине + статические типы позволяют делать очень лаконичные и удобные DSL. Надеюсь, рано или поздно всю gradle перепишут на котлин, удобство использования вырастет в разы. Я бы хотел иметь такую фичу в своём языке, хотя это и не критично.


    Аналогично extension методы. Синтаксический сахар, но довольно удобный. Совсем необязательно быть автором класса, чтобы добавить к нему очередной метод. А ещё их можно вкладывать в области видимости чего-нибудь и таким образом не засорять глобальную область. Ещё одно интересное применение — можно навешивать эти методы на существующие коллекции. Например, если коллекция содержит объекты типа T, которые поддерживают сложение с самими собой, то можно добавить коллекции метод sum, который будет только в том случае, если T это позволяет.


    Call-by-name семантика


    Это опять же синтаксический сахар, но это удобно, и вдобавок позволяет писать ленивые вычисления. Например, в коде типа map.getOrElse(key, new Smth()) второй аргумент принимается не по значению, а потому новый объект будет создан только если в таблице нет ключа. Аналогично, функции типа assert(cond, makeLogMessage()) выглядят намного приятнее и удобнее.


    Вдобавок, никто не заставляет компилятор делать именно анонимную функцию — например, можно заинлайнить функцию assert и тогда это превратится просто в if (cond) { log(makeLogMessage())}, что тоже неплохо.


    Я не скажу, что это must have фича языка, но она явно заслуживает внимания.


    Ко-контр-ин-нон-вариантность шаблонных параметров


    Всё это нужно. "Входные" типы можно делать шире, "выходные" типы можно сужать, с некоторыми типами ничего нельзя делать, а некоторые можно игнорировать. Имхо, в современном языке нужно иметь поддержку этого прямо в системе типов.


    Явные неявные преобразования


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


    Где хранить типы объектов?


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


    Значения, ссылки, указатели


    Язык должен скрывать от программиста подробности реализации. В С++ при написании шаблонов возникают проблемы, так как T в шаблоне может оказаться каким-нибудь неожиданным типом. Это может быть значение, указатель, ссылка или rvalue-ссылка, некоторые приправляются словом const. Не могу сказать, как надо сделать, но точно вижу, как делать не надо. Что-то близкое к идеалу по удобству есть в Scala и Kotlin, где примитивные типы "притворяются" объектами, так что всё с чем мы работаем выглядит однообразно и не нагружает мозг программиста и синтаксис языка.


    Минимум сущностей


    Вот чем мне не нравится С# — в язык втащили кучу всего, это всё как-то странно сочетается и повышает сложность языка. (Я могу сильно ошибаться в деталях, так как на С# я писал очень давно и только под Unity) Например, там есть поля класса, проперти и методы. 3 сущности! Они друг с другом не очень сочетаются, можно объявить несколько методов с одним именем, но разной сигнатурой, но почему-то нельзя объявить проперти с тем же именем. Или если интерфейс требует чтобы было проперти, то нельзя в классе просто объявить поле — это должно быть именно проперти.


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


    Ещё пример — слово inline в C++/Kotlin. Не стоит его тащить в язык! И там и там слово inline меняет логику компиляции и исполнения кода, люди начинают писать inline не ради собственно инлайна, а ради возможностей писать функцию в хедере (С++) или делать хитрый return из вложенной функции как из вызывающей (kotlin). Потом в языке появляются forced_inline__, noinline, crossinline, влияющие на какие-нибудь нюансы и ещё более усложняющие язык. Мне кажется, язык должен быть максимально гибким и простым, а те же inline могут быть аннотациями, которые не влияют на логику исполнения кода и лишь помогают компилятору.


    Макросы


    В языке должны быть макросы, которые принимают на вход синтаксическое дерево и преобразуют его. В случае с унылым повторяющимся кодом макросы могут спасти от ошибок и сделать код в несколько раз короче. К сожалению, достаточно серьёзные языки типа С++ имеют ещё и сложный синтаксис с кучей нюансов, возможно, поэтому нормальных макросов там до сих пор так и не появилось. В языках типа lisp и scheme, где программа сама по себе подозрительно похожа на список, написание макросов не вызывает больших проблем.


    Функции внутри функций


    Плоская структура уныла. Если что-то используется только в одном-двух местах, то почему бы не сделать это максимально локально — например, разрешить объявлять внутри функций какие-то локальные функции или классы. Это же удобно: не засоряется пространство имён, при удалении кода функции заодно удаляются и её "внутренние" подробности.


    Substructural type system


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


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


    Зависимые типы


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


    Сборка


    Во-первых, язык должен уметь работать и без стандартной библиотеки. Во-вторых, библиотека должна состоять из отдельных кусочков (возможно, с зависимостями между ними), чтобы при желании можно было включить только часть из них. В третьих, в современном языке должна быть удобная система сборки (в С++ боль и печаль).
    Функции, переменные и классы должны использоваться для описания хода вычисления, это не то, что надо запихивать в бинарник. Для экспорта наружу можно как-нибудь аннотировать необходимые кусочки, но всё остальное должно быть отдано компилятору и пусть он преобазует код как хочет.


    Выводы


    Итак, на мой взгляд, в идеальном языке программирования должны быть:


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

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


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


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

    Поделиться публикацией

    Комментарии 315

      +1
      Новый, интересный zig. Построен на плечах LLVM/LLD. Что удивительно — не поделка, а очень и очень рабочий инструмент.
        0
        Любпытно, по крайне мере. Вот здесь написано много интересного, что-то очень заманчиво, что-то неоднозначно.
        Но покурить стоит.
          0

          Самая большая неоднозначность — парадигма полностью ручного ресурс менеджмента. Т.е. при отсутствии GC они не предоставляют ничего кроме куцего defer.

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

          Ну я больше описал "как я бы хотел", а не "как оно получится после столкновения с реальностью". Первичной целью является получение опыта и знаний. В идеале я бы хотел потеснить lua — сделать маленький гибкий язык, чтобы его можно было встраивать куда угодно. Ещё я хотел попробовать Graal VM, но у меня никак не дойдут руки(


          В скале 2 нет типов-сумм, есть только очень ограниченная поддержка в виде case классов. Есть мелочи типа не устаканившихся макросов и некоторых костылей для обхода ограничений jvm. Есть некоторые моменты, которые, как мне кажется, сделаны слишком сложно. Всякие интересные возможности добавляют в dotty, но я не знаю, как их поддержка повлияет на производительность кода. Не хватает возможности в качестве шаблонного параметра передать число (в scala native это нужно для описания типа массива фиксированной длины). В scala native после компиляции получаются подозрительно большие бинарники. Код (jar файлы) получаются тоже довольно большими, на порядок больше того что в java (именно мой код, без учёта стандартной библиотеки). Вдобавок, чувствуется, что скала создавалась под jvm, это выражается в некоторых ограничениях. Например, для интерфейса c методом print(t: T) и не могу сделать, чтобы один класс реализовал интерфейс одновременно и для T=int и для T=String

            0
            >маленький гибкий язык, чтобы его можно было встраивать куда угодно.
            Знаете, у меня вот прямо сейчас скала выступает именно таким языком, причем наравне с груви (они применяются по очереди). Это называется Spark Shell, и как это ни странно, но то подмножество, которое нужно для создания прототипов — оно достаточно маленькое и простое, в том числе как выяснилось для освоения не совсем программистами.

            >В скале 2 нет типов-сумм
            Мне казалось, что это запланировано в будущей версии (dotty, правда, непонятно, когда она будет).
              +1
              Lua был придуман и развивается именно как маленький гибкий язык для встраивания. В чём состоит необходимость его потеснить, чего ему настолько не хватает в этой роли? Что нового даст этот «язык мечты», что так нужно Lua и более-менее аналогам, типа Squirrel и прочих?
            +4

            В F# интересно с точки зрения множественности аргументов: есть каринг (т.е. функция от двух аргументов, это функция от первого аргумента, которая возвращает функцию с примененным первым аргументом:
            let f x y = x + y
            тогда
            f 2 3 вернет 5
            f 2 вернет функцию прибавляющую 2 к аргументу
            )
            это удобно для функционального кода. (map (f 2) — прибавить двойку ко всем элементам списка. Можно написать map ((+) 2) — операторы тоже можно вызывать как функции)


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


            Их можно вызывать только так:


            System.Console.WriteLine(x, y), где скобки это не часть синтаксиса вызова, а констрирование кортежа.

              +1

              Самое забавное что там и out параметры мапятся в кортеж.
              C#: bool TryParse(string, out T)
              F#: TryParse : string -> (bool, T)

                0

                Интеграция работает по особому и ни кортеж, ни частичное применение не работает. Нужно делать обертки.
                Такой код не сработает:
                let tuple = (x, y)
                System.Console.WriteLine tuple

                +6
                Очень многое, из того что написано, есть в C#.
                Он разок упомянут в статье, но мелко и не конструктивно, поэтому комментировать не буду, ибо не понял проблемы.
                  0

                  Вот пример.


                  class A {
                      public void a(double c){}
                      public void a(int x, int y){}
                      //public int a{get; set;}
                  }

                  Почему я могу сделать несколько методов с одинаковым именем и разный сигнатурой, и это нормально, а если я для проперти попробую использовать то же самое имя, то нельзя? И это не смотря на то, что "под капотом" геттер и сеттер это всё равно методы. Это ограничение мне кажется совершенно искусственным, как и отдельный синтаксис для определения геттеров и сеттеров.

                    0
                    Не смог найти конкретного ответа на ваш вопрос. Допускаю, что в каких то кейсах будет неоднозначное поведение, когда надо например отличить свойство типа Action от метода.
                    ПС: если свойство будет типа Action<\int> (как елочки экранировать на хабре?) его можно будет вызывать буквально как метод — obj.Property(123), и в этом случае вызов между таким методом и таким свойством дадут неоднозначность.
                      0
                      а если я для проперти попробую использовать то же самое имя, то нельзя?

                      Потому что невозможно будет определить, что такое var q = A.a.


                      Это ограничение мне кажется совершенно искусственным

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

                        0
                        Потому что невозможно будет определить, что такое var q = A.a.

                        Не вижу проблемы. Если у класса несколько методов, то тоже невозможно определить, к чему относится A.a При этом всё нормально работает:


                        class A {
                            public void a(double c){}
                            public int a(){return 1;}
                        }
                        
                        public static void Main()
                        {
                            Func<int> a = new A().a;
                            Action<double> a2 = new A().a;
                            var a3 = new A().a();
                        }

                        Проблема в том, что усложнился весь язык.


                        1. Проперти считается отдельной сущностью, хотя реализована через методы
                        2. Геттер будет реализован как метод get_a, но при этом я не могу в классе определить ни одного метода с именем a.
                        3. метод get_a() объявить тоже не получится. (Единственный логичный пункт)
                        4. Метод геттера get_a() нельзя вызвать как метод. Но можно через рефлексию.

                        Л-логика! В C# создали проблемы на пустом месте.


                        Вот пример на скале:


                        class A() {
                            val x = 1
                            def x(s: String): Unit = println(s)
                            def x(arg: Int): Unit = x(arg.toString)
                        }
                        
                        val a = new A()
                        a.x(a.x)
                        val method: String => Unit = a.x(_)

                        Eдинственное, что запрещает язык — сделать метод с именем 'x', который ничего не принимает, поскольку геттер является этим самым методом.

                          0
                          При этом всё нормально работает

                          Не, не работает. Вы не можете сделать var q = A.a. Вы, собственно, не можете этого сделать даже тогда, когда метод a один: "CS0815: Cannot assign method group to an implicitly-typed variable".


                          Вы хотите потребовать, чтобы для свойства a указывали явно тип принимающей переменной? int q = A.a? Разработчики будут против, это неудобно.


                          Проблема в том, что усложнился весь язык.

                          Не, не усложнился.


                          Проперти считается отдельной сущностью, хотя реализована через методы

                          Много что считается отдельной сущностью, хотя реализуется через что-нибудь другое. И что?


                          Геттер будет реализован как метод get_a, но при этом я не могу в классе определить ни одного метода с именем a.

                          Вам уже объяснили, почему. Могу повторить.


                          Метод геттера get_a() нельзя вызвать как метод.

                          А зачем?


                          Л-логика! В C# создали проблемы на пустом месте.

                          Я не вижу ни одной проблемы, если честно. Свойства, по большому счету, все равно (семантически) не могут называться так же, как методы.

                            0
                            Метод геттера get_a() нельзя вызвать как метод.
                            А зачем?
                            Например чтобы писать Select(A.a), а не Select(x => x.a)
                              0

                              Идея, конечно, привлекательная, но не взлетит.


                              Во-первых, метод геттера вам для этого никак не поможет. Давайте проведем эксперимент на обычном методе:


                              public class A {
                                  public int get_a() {
                                      return 0;
                                  }
                              
                                  public void Q(IEnumerable<A> aa)
                                  {
                                      aa.Select(x => x.get_a()); //компилируется и работает
                                      aa.Select(A.get_a); //error CS0411
                                  }
                              }

                              Во-вторых, если сделать так, чтобы это работало (именно через методы, а не через свойства), то что станет с IQueryable.Select?

                                +1

                                В Котлине взлетело, правда там доступ к методу/свойству и "взятие ссылки" синтаксически отличаются: A.get_prop и A::get_prop.

                                  0

                                  А в C# такой синтаксис, и с ним уже ничего не поделаешь.


                                  Ну так а что с expressions?

                                    0
                                    Честно говоря, я не разбираюсь в этой теме, но будет интеесно разобраться. Какие проблемы могут возникнуть?
                                      0

                                      Когда используется Expression<Func<A,B>> e = x => x.q, создается выражение, из которого явно понятно, что q — это свойство. Всякие там милые ORM используют это для создания запросов в БД, опираясь на маппинг этого самого свойства на БД. Если вы сделаете вызов метода, то он будет неотличим от вызова любого другого метода, отследить его до свойства будет нельзя, ORM сломается.

                                        0
                                        А что если вешать на сгенерированный геттер атрибут, который сигнализировал бы о том, к какому свойству он относится?
                                          0

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


                                          Ну и да, ввиду того, что нет инстанса, это все равно все невозможно.

                            0

                            Вот, кстати, прекрасный пример с SO:


                            class A
                            {
                              public Action<int> Q {get;}
                              public void Q (int a) {}
                            }
                            
                            Action<int> q = new A().Q;
                              0
                              Вы хотите потребовать, чтобы для свойства a указывали явно тип принимающей переменной? int q = A.a? Разработчики будут против, это неудобно.

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


                              А зачем?

                              Потому что он есть. Кому это мешает?


                              прекрасный пример с SO:

                              Это уже больше похоже на некорректную ситуацию. Ну так запрещать надо именно такое, а не все совпадения имён!


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


                              В языках типа Groovy/Scala проперти играют роль синтаксического сахара, который использовать не обязательно — можно напрямую звать геттеры и сеттеры. С моей точки зрения происходящее в С# выглядит лишним усложнением.

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

                                Угу. Было свойство Q, можно было писать var q = x.Q. Теперь кто-то добавил к классу новый метод Q, и внезапно весь старый код больше не компилируется. Правда, круто?


                                Потому что он есть.

                                Я спросил, зачем?


                                Ну так запрещать надо именно такое, а не все совпадения имён!

                                Логика для анализа слишком сложная окажется.


                                если бы проперти было доступно как метод get_Q()

                                А это был бы мусор в именовании.


                                а в неоднозначности из примера отдавался бы приоритет методу Q.

                                А почему методу, а не свойству? А если там Func<int>? А если у вас не просто свойство, а индексер?


                                С моей точки зрения происходящее в С# выглядит лишним усложнением.

                                Ровно наоборот, в C# все понятно, если не пытаться подходить к этому с точки зрения скалы.

                                  +1
                                  Это уже больше похоже на некорректную ситуацию. Ну так запрещать надо именно такое, а не все совпадения имён!

                                  А нужно запретить такое объявление класса?
                                  class A<T>
                                  {
                                    public T Q {get;}
                                    public void Q (int a) {}
                                  }
                                  

                                  Или такое использование?
                                  Action<int> q = new A<Action<int>>().Q;
                                  

                                  А как быть с таким использованием?
                                  void Foo<T>(A<T> a)
                                  {
                                    Action<int> q = a.Q;
                                    ...
                                  }
                                  
                                    +1
                                    Второе. Нужно сделать обращение к проперти и взятие ссылки на метод синтаксически различными.
                                +4
                                А зачем кстати одноименные свойства и методы? Обычно у них разные цели и разные названия, ни разу не сталкивался с такой необходимостью.
                                  0
                                  Логически связанные сущности могут иметь одинаковые имена, но то, что приведено выше, совсем-совсем непохожее одно на другое, должны иметь разные имена. Ну, если мы не на конкурсе быдлокода, конечно.
                                    +1
                                    Недавно именно это понадобилось при написании DSL для тестов (factory тестовых данных).
                                    class ApprenticeCustomization {
                                        var group: Group? = null
                                        var groupCustom: GroupCustomization.() -> Unit = {}
                                        fun group(custom: GroupCustomization.() -> Unit) {
                                            groupCustom = custom
                                        }
                                    }
                                    

                                    В итоге выходит синтаксис, где
                                    var customGroup = factory.group { }
                                    var apprentice = factory.apprentice {
                                        group = customGroup
                                    }
                                    

                                    это присвоение уже гового, а вот такой вызов
                                    var apprentice = factory.apprentice {
                                        group {
                                            price = BigDecimal(3_000)
                                        }
                                    }
                                    

                                    это уже дефротная группа, но с переопределенной стоимостью занятий
                                0
                                Потому что у свойств получаются «геттеры» с одинаковой сигнатурой и именем.
                                Методы int Get_A(){} string Get_A(){} тоже объявить не получится.
                                  0
                                  А теперь давайте представим, что у проперти тип не int, а делегат, имеющий сигнатуру совпадающую с одним из методов. И тут уже возникает неразрешимая проблема при попытке сделать obj.a().
                                0
                                void не зря не равен 0. А то массив void был бы равен 0. Что даст неопределенное поведение.
                                  0
                                  А кстати, каким образом?
                                    0

                                    void это абстрактное значение, ничему не соответствующие. Отсутствие значения. Древние математики до изобретения понятия нуля тоже видели в этом проблему.
                                    Если очень хочется, не вижу никаких проблем сделать что-то вида


                                    define MAX_INT _OLD_MAXINT — 1
                                    define void _OLD_MAXINT

                                    Откуда тут неопределенность?

                                      0
                                      Я не очень чётко выразился, размер не 0 чтоб например масив из void не был равен 0.
                                        0
                                        #include <stdio.h>
                                        
                                        int main()
                                        {
                                            void v[2];
                                            printf("Hello World %d\n",sizeof(v));
                                            return 0;
                                        }
                                        


                                        даже не компилируется

                                        main.c: In function ‘main’:
                                        main.c:5:10: error: declaration of ‘v’ as array of voids
                                             void v[2];
                                                  ^


                                        Откуда неопределенность?
                                          0
                                          Эт хорошо что не компилируется.
                                          #include <stdio.h>
                                          class A {
                                          public:
                                          A() = default;
                                          };
                                          
                                          int main(){
                                          A a;
                                          printf("Hello World %d\n",sizeof(a));
                                          return 0;
                                          }
                                          

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


                                            Не могу себе представить операцию сравнения класса с константой. Разве что на javascript ;)

                                            Размер экземпляра класса в вашем примере получается 1. Логично, у класса есть конструктор, и его размер !=0.
                                              0
                                              Конструктор не виляет на размер класа. Он не хранится внутри класса. Внутри класа хранится только vtable. Функции не влияют на размер класса. Это сделано специально, чтоб если сложить экземпляры класса в массив, массив внезапно не стал 0 размера.
                                                +1
                                                Это сделано специально, чтоб если сложить экземпляры класса в массив, массив внезапно не стал 0 размера.



                                                Вряд ли массив 0 размера чем-то хуже переменной 0 размера. ;)
                                                Просто обьектов переменных 0 размера в языке С(++) нет.
                                                  +1

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


                                                  empty-struct.c
                                                  #include <stdio.h>
                                                  
                                                  #define print(FMT, X) printf("%s = " FMT "\n", #X, X)
                                                  
                                                  struct empty {};
                                                  
                                                  int main(void) {
                                                    struct empty a1;
                                                    struct empty a2;
                                                    print("%lu", sizeof(struct empty));
                                                    print("%p", &a1);
                                                    print("%p", &a2);
                                                  }

                                                  output
                                                  >./es-clang
                                                  sizeof(struct empty) = 0
                                                  &a1 = 0x7ffd72123078
                                                  &a2 = 0x7ffd72123070
                                                  
                                                  >./es-gcc
                                                  sizeof(struct empty) = 0
                                                  &a1 = 0x7fff243eac06
                                                  &a2 = 0x7fff243eac07
                                                  
                                                  >./es-pcc
                                                  sizeof(struct empty) = 0
                                                  &a1 = 0x7fff91235df0
                                                  &a2 = 0x7fff91235df0
                                                  
                                                  >./es-tcc
                                                  sizeof(struct empty) = 0
                                                  &a1 = 0x7ffd19cb7a90
                                                  &a2 = 0x7ffd19cb7a90
                                                    0
                                                    Забавно ;)
                                                    Есть, значит, способ сломать стандартный трюк с sizeof(array)/sizeof(array[0]).
                                            0

                                            В С при увеличении указателя на void людя хотят именно перемещаться по байтам в памяти. Если размер void вместо 1 станет 0, то тогда сломается совместимость.

                                              +1
                                              при увеличении указателя на void людя хотят именно перемещаться по байтам в памяти.

                                              хотят ожидают
                                              Тогда логично использовать char*
                                              А то можно представить себе архитектуру, где указатели указывают на машинное слово, а байты — по прежнему 8-битные.
                                                +1
                                                Ну если размер объекта 0 байт, то объекта не существует в памяти, не так ли? И совершенно логично, что операции перемещения по байтам в памяти для такого объекта бессмысленны. Фактически, объекты и массивы типа void, если их ввести в некий язык программирования, могут существовать только на этапе компиляции, для каких-то целей обобщенного метапрограммирования. В скомпилированной программе никаких следов таких объектов оставаться не должно.
                                                Компилятору известно что объект 0 байт, стало быть он может выдать ошибку при попытке получить адрес такого объекта (или возвратить NULL, не знаю как лучше), и т.д.
                                                  0
                                                  > или возвратить NULL, не знаю как лучше

                                                  Вот этого точно делать не стоит, потому что возвращённый указатель может куда-то каститься и дальше разыменоваться, и бум случится в рантайме.
                                                  С другой стороны, даже пустой (в смысле без полей) объект может иметь методы, на которые может захотеться получить указатель, который потом захочется вызвать. И что делать?
                                                    0
                                                    Указатель на метод это указатель на функцию, там проблем нет.
                                                    А если нужен указатель на объект без полей для передачи его в качестве «this», то он все равно не должен использоваться (полей-то нет) — поэтому компилятор может или выкинуть неявный аргумент this вовсе, или передавать null. Если же есть виртуальные методы, по появляется поле vfptr, значит уже не 0 байт.
                                                      0
                                                      Но тогда появляются различия между реализациями пустых и не пустых структур, которые надо везде проводить. Проще сделать фиктивное безымянное поле-байт, которое никому не нужно и всё равно будет исключено при оптимизации, и не делать частных случаев.
                                                        0
                                                        Не факт. Например, если я объявляю массив на 1000 таких структур в драгой структуре, у меня внезапно тратится килобайт памяти, хотя структуры пустые. Можно конечно спросить — зачем делать массив из пустых структур, но такое может получиться случайно при метапрограммировании.
                                                        Вообще ситуация интересная, нужно думать.
                                        +1
                                        Если бы в питоне можно было объявлять лямбды более коротким способом, хотя бы it => it.y > 2, то генераторы списков оказались бы не очень нужными.
                                        Python пропагандирует что код должен быть по возможности простой и хорошо читаемый.
                                        И в плане читаемости генераторы списков намного понятней чем нагромождение лямбд, даже если придумать более короткий способ эти лямбды задавать.
                                        Генераторы списков простые и наглядные, по сути, более компактно записанный цикл.
                                        При этом сохраняется высокая гибкость конструкции:
                                        list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
                                        list_b = [x**3 if x < 0 else x**2 for x in list_a if x % 2 == 0]
                                        # вначале фильтр пропускает в выражение только четные значения
                                        # после этого ветвление в выражении для отрицательных возводит в куб, а для остальных в квадрат
                                        print(list_b)   # [-8, 0, 4, 16]
                                          +1
                                          Заметьте что порядок действий в словах не совпадает с порядком действий в коде(при прямом чтении слева направо). Разве не логичней было б:
                                          list_b = [for x in list_a: if x % 2 == 0: if x < 0: yield x**3 else: yield x**2]
                                          
                                            0
                                            В текущем виде то что справа от for x in list_a — это фильтры, что слева — условие обработки отфильтрованного. Может и не очень логично, но запомнив один раз легко потом вычленять в коде и понимать.
                                            В Вашем случае оба типа условий идут подряд, визуально воспринимать тяжелей.
                                            +2
                                            Вот как раз этот код я бы читабельным не назвал, из-за двойственного использования if — как тернарный оператор в теле и как фильтр в генераторе.
                                            Я много программировал на Scala и Haskell, где генераторы применяются довольно часто. Не всегда это делает код понятнее, иногда явное использование map, filter и flatMap(>>=) более выразительно.
                                              0
                                              Так можно и в Python явно использовать filter, причем даже в генераторе списка:
                                              list_c = [x**3 if x < 0 else x**2 for x in filter(lambda x: x % 2 == 0, list_a)]
                                              

                                              Это аналог моего примера выше с тем же выводом.
                                              НО! Мой первый пример эффективней, так как там исходная последовательность проходится один раз, по ходу прохода идет и фильтрация и преобразование, а в случае фильтра — вначале проход для фильтрации, потом проход для преобразования, иногда это может быть важно!
                                                0
                                                Если на список создается итератор, то filter/map/flatMap будут столь же эффективны, как и генератор.
                                                  0
                                                  (Коментарий исправлен после проверки гипотезы в коде)
                                                  У меня получается самый быстрый вариант на генераторе списка, решение на map/filter/lambda в более чем в 1,5 раза медленей…
                                                  Код для проверки
                                                  import time
                                                  
                                                  list_data = range(0, 10000000)
                                                  
                                                  start = time.time()
                                                  list_huge = [x**3 if x < 0 else x**2 for x in list_data if x % 2 == 0]
                                                  end = time.time()
                                                  print(end - start)  # 1.896183967590332
                                                  
                                                  
                                                  start = time.time()
                                                  list_huge = [x**3 if x < 0 else x**2 for x in filter(lambda x: x % 2 == 0, list_data)]
                                                  end = time.time()
                                                  print(end - start)  # 3.0600476264953613
                                                  
                                                  start = time.time()
                                                  list_huge = list(map(lambda x: x**3 if x < 0 else x**2, filter(lambda x: x % 2 == 0, list_data)))
                                                  end = time.time()
                                                  print(end - start)  # 3.264080762863159

                                                  0
                                                  Я был не прав, прохода второй раз не будет, так как filter — это генератор сам по себе, но тем не менее, данный пример примерно в 1,5 раза медленней, в моем комменте выше есть тесты с примерами.
                                                    0

                                                    А у Haskell компилятор умеет в stream fusion, он и сам все операции оптимально соберет.

                                                +2

                                                К вопросу о лёгкой читаемости кода на питоне....


                                                Это Ruby, если что...


                                                list_b = [-2, -1, 0, 1, 2, 3, 4, 5].select(&:even?).map { |x| x < 0 ? x**3 : x**2 }

                                                А это Julia:


                                                list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
                                                list_b = map(x -> x < 0 ? x^3 : x^2,  filter(iseven, list_a))

                                                list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
                                                list_b = filter(iseven, list_a) |> list -> map(x -> x < 0 ? x^3 : x^2, list)

                                                list_a = [-2, -1, 0, 1, 2, 3, 4, 5]
                                                list_b = filter(iseven, list_a) |> 
                                                         list -> map(list) do x
                                                            x < 0 ? x^3 : x^2
                                                         end

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

                                                  0
                                                  from x in new[]{-2, -1, 0, 1, 2, 3, 4, 5}
                                                  where x % 2 != 0
                                                  let pow = x < 0 ? 3 : 2
                                                  select Pow(x, pow)


                                                  Это C# к слову
                                                    0

                                                    Как-то многословно.


                                                    [ x ^ exp | x <- [-2 .. 5]
                                                              , x `mod` 2 /= 0
                                                              , let exp = if x < 0 then 3 else 2
                                                              ]
                                                      0
                                                      Если убрать явный массив и сделать как у Вас, то с точностью до форматирующих пробельных символов код получается одинаковым по длине — 83...85 символов, в обеих реализациях.
                                                      Знаки препинания в Вашем коде разменялись на буквы в моём. Но буквы может прочесть относительно неподготовленный разработчик, то в Вашем коде, имхо, в каждую закорючку вкладывается свой смысл. Я лично только из контекста того, что делает код, понял различие между x < — … и let x =…
                                                      Но всё это вкусовщина, конечно.
                                                        0

                                                        Вкусовщина, конечно, да.


                                                        Математику, наверное, такая запись будет понятнее. И в обратную сторону: я вот программист, но from x in new ... я понял только тоже из контекста.


                                                        Ну и в linq можно, например, добавить ещё один where-блок после вашего let pow?

                                                          0
                                                          Ну, можно new[]{...} заменить на Range(...) (предполагается using static… Enumerable)
                                                          Условия добавляются прозрачно: ещё одно where пишете и всё.
                                                            +1
                                                            (-2..5).select(&:even?).map { |x| x < 0 ? x**3 : x**2 }

                                                            56 символов. И читаемость никуда не пропала.

                                                              0
                                                              map (\x -> x ^ if x < 0 then 3 else 2) $ filter odd [-2 .. 5]

                                                              Сиподобная тернарка это ну такое. Функции в обратную сторону тоже, хотя при желании можно написать конечно и так:


                                                              [-2 .. 5] & filter odd & map (\x -> x ^ if x < 0 then 3 else 2)
                                                  0

                                                  Попробовал переписать выражение на perl


                                                  @list_b =  map{ x<0? x**3 : x**2 } grep { x%2 == 0 } @list_a
                                                    0

                                                    Не знаю, в питоне лямбды очень не очень.


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


                                                    int[] array = {1,1,1,1,2};
                                                    int value = array.Aggregate((a,b) => a == b ? a : throw new InvalidOperationException("All values should be equal");

                                                    в питоне мне в итоге пришлось делать как-то так:


                                                    def raise_(ex):
                                                        raise ex
                                                    
                                                    ...
                                                    
                                                    value = reduce(lambda a, b: (a if a
                                                                                   == b else raise_(ValueError('All values should be equal'
                                                                                                            ))), array)

                                                    выглядит весьма фигово. Про необходимость в лямбде иногда больше 1 действия сделать я молчу.

                                                      0

                                                      В шарпе можно сделать так:


                                                      int value = array.Distinct().Single();
                                                        0
                                                        Ну это да. Но удобных агрегаторов в питоне вроде я не нашел. Я просто перевел максимально похоже, чтобы было понятно, чего в целом я пытался добиться.
                                                      0
                                                      list_a.filter { it % 2 ==0 }.map { if (it < 0) x*x*x else x*x }
                                                      
                                                      0

                                                      У вас:


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

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

                                                        +2
                                                        Программы предназначены не для реализации алгоритмов, а для решения бизнес-задач, которые как раз ставятся достаточно декларативно. Кроме того, декларативные программы понятнее и легче формально обосновываются.
                                                        +2
                                                        1. Ну вот, получился typescript ;) (почти) с#
                                                        2. Препроцессоры это огромное зло (или по чему я не люблю С++)
                                                        3. Вы в основном сосредоточились на синтаксисе но есть многое, что трудно эмулируется
                                                        К примеру- замыкания, upvalues, try/catch/, yild, некоторое из функциональных или декларативных языков, прототипное наследование, миксины. Модель работы с памятью, с тредами, с параллельными вычислениями, с распределенными вычислениями, сигналинг, изоляции…

                                                          +2
                                                          В C/C++ подход к препроцессору был очень плохо продуман. Но применение макросов в Lisp, Nemerle, OCaml и Rust показывает, что такой подход может быть очень удобным и полезным.
                                                            0
                                                            С OCaml и Nemerle не знаком. Но в остальных случаях, по моему мнению, препроцессор — недостаток выразительных средств языка. Даже в случае кондишн-компайлинга можно обойтись и без него.
                                                            Забыл где лежит большое письмо создателя делфи сишарпа и тайпскрипта, там где он взвешивает +- препроцессора. Для меня все ++ малоубедительны.
                                                              0
                                                              Но в остальных случаях, по моему мнению, препроцессор — недостаток

                                                              Макросы не имеют ничего общего с препроцессорами.

                                                                0
                                                                TH, например, таки не очень жалуют.
                                                                  0
                                                                  Вы правы! Я «забыл»уточнить, что воюю против текстовых макросов. Против макросов, которые разворачиваются на уровне синтаксического дерева я ничего не имею против.
                                                              0

                                                              До идеального в ts нету перегрузки операторов имхо.

                                                              +1
                                                              Генераторы списков — это аналог for в Scala, только менее обобщенное. Основная его фишку не столько замена map и filter, сколько удобное использование flatMap.
                                                              Union-типы, как они ожидаются в Dotty, это не алгебраические типы-суммы. A | A тождественно A, а Either[A,A] несет больше информации. На мой взгляд Union-типы — опасная штука, которая будет порождать неожиданные эффекты при обобщенном программировании (например, код обработки ошибок может начать путать код ошибки и корректные данные, если они окажутся одного типа). Но в теории подход интересен.
                                                              Применение зависимых типов в императивных языках еще плохо проработано. Если меняется значение переменной, в которой хранился размер массива, что должно произойти с массивом? В ATS пытаются подобные проблемы решить, подружив зависимые типы с линейными, но пока далеко не продвинулись.
                                                                0
                                                                Заинтересовался, что такое ATS — выглядит, как будто один автор, или кафедра в каком-то китайском университете пилит понемногу, диковато как-то.
                                                                  0

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

                                                                    0
                                                                    Почему же, я на Idris смотрю, и дичью он мне не кажется. Вы можете сравнить ATS и Idris?
                                                                      0
                                                                      Ну, его ещё меньше людей разрабатывает, в этом-то всё дело.
                                                                +1
                                                                Мне кажется, большая часть перечисленного в статье, уже есть в Nim. Вот несколько примеров:

                                                                Макросы поддерживают работу с синтаксическим деревом.
                                                                import macros
                                                                macro hello(x: untyped): untyped =
                                                                  result = x
                                                                  # Сакральный смысл индексов ниже:
                                                                  # Первый индекс - утверждение (у нас оно одно)
                                                                  # Второй индекс - часть утверждения (для нашего случая под номером 1 будет первый аргумент
                                                                  #      0(1, 2...)
                                                                  # 0: echo("goodbye world")
                                                                  # 1: ...
                                                                  result[0][1] = newStrLitNode("hello world")
                                                                hello:
                                                                  echo("goodbye world") # Превратится в echo "hello world"
                                                                

                                                                Кортежи без предварительного объявления
                                                                proc x(): (int, int) = (4, 2)
                                                                

                                                                Лямбды:
                                                                let f = (x:int)=>x*x
                                                                echo(f(2)) # 4
                                                                


                                                                Удобная система сборки: nimble

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

                                                                  Добавлю к субъективному набору фич идеального языка (для меня) ещё несколько:
                                                                  1) Автоматическое управление памятью: gc или хотя бы RAII. Лучше gc — он нормально обрабатывает циклические ссылки.
                                                                  2) Перемещающий сборщик мусора. На архитектурах с малым (2-4ГБ) максимальным объёмом виртуальной памяти куча иногда фрагментируется, и хотелось бы, чтобы выделенные куски памяти могли перемещаться для дефрагментации свободного места. На 64-разрядных архитектурах такая проблема выражена в меньшей степени.
                                                                  (я понимаю, что gc удобен, но уместен не везде. указанные фичи, как мне кажется, критичны для достаточно "больших" систем)
                                                                  3) (наименее холиварный пункт) Именованные параметры при вызове. Очень удобны вместе со значениями по-умолчанию для конфигурации метода с большим набором параметров.


                                                                  case class A(x: T = a1, y: U = b2, z: V = c3)
                                                                  
                                                                  val a = A(y = b5)

                                                                  4) Близость к железу, хорошая оптимизация компилятором или JIT-компилятором. Когда вы возвращаете из метода пару (Int,Int), она всё-таки не должна аллоцироваться в куче, как в scala, где какое-нибудь val (x,y)=p.coords() в цикле может напрочь убить производительность (вы видите в строке 4 new? а они есть!). Может, этот конкретный пример уже компилируется более оптимизированно, сейчас не проверял. Также вложенные for-yield for(x<-xs if f(x); y<-ys(x) ...) yield ... когда-то неожиданно сильно жрали память.
                                                                  5) Community-фичи: поддержка, документация, обновления, кроссплатформенность, свободные компиляторы и IDE, библиотеки. Недостаточно создать язык, нужно ещё и 100500 доступных в нём библиотек.

                                                                    0
                                                                    Технически они являются просто методами со специальными именами, и их в любой момент можно переопределить.

                                                                    Технически, значение должно где-то храниться, в этом и отличие от методов.

                                                                      –1

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

                                                                        +1

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

                                                                          0

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

                                                                      +2
                                                                      Я бы сказал, что «идеальный» язык программирования должен быть минималистичен, но при этом иметь четкий и однозначный синтаксис. Большинство удобных синтаксических штук должны подключаться с помощью расширений. К примеру вам нужно много классных математических функций — подключили расширение и решаете математическую задачу.
                                                                      Нужна вариантность — подключили и её. Функциональные фишечки — пожалуйста. И т.д.

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

                                                                      Более того такой подход позволит решать задачи из разных областей, а явное декларирование позволит разработчику, не читая код, понять, какие знания ему нужны для понимания данной программы.
                                                                        0
                                                                        Околотемы вопрос, больно не пинайте.
                                                                        Добавление в Python списков с чётко заданным одним типом, вроде: listint, listfloat, listbool или liststr — помогло бы ускорить язык тогда когда это нужно?
                                                                          0
                                                                          В актуальном питоне можно указывать типы, но сам питон их указание просто игнорирует, поэтому указанием ускорить ничего не получиться.
                                                                          Но для вычислений есть специальные типы списков, например numpy добавляет свой тип Array заточенный под числовые данные.
                                                                            0
                                                                            Ну я и хотел спросить/предложить, почему в Python не добавят такие списки (в сам язык или в стандартную библиотеку), чтобы даже без numpy можно было, там где это нужно, указать тип всех элементов в списке.
                                                                              +1
                                                                              Не знаю, но вероятно это не решает никаких проблем за пределами тех задач где всё равно нужен numpy и товарищи, а может потребовать значительной переделки языка и реализации.
                                                                              Надо искать, может что-то такое уже спрашивали на SO или в python-ideas
                                                                          +2
                                                                            +1
                                                                            mashedtaters.net/var/language_checklist.php
                                                                            тут можно сгенерировать сразу готовый ответ по рандому, в большинстве случаев сразу подходит.
                                                                            +4

                                                                            Я всегда хотел несколько фишек из Ada:


                                                                            • constrained types: например, целое число от 1 до 10
                                                                            • by-name type equivalence: типы с разными именами но с одинаковым представлением данных (число или структура с одинаковым набором полей) несовместимы. т.е. type A=Int и type B=Int несовместимы без явного преобразования типов.
                                                                              0

                                                                              Хотели в какой язык?
                                                                              Вторая 'фишка' есть в Go, если я правильно понял описание.

                                                                                +1

                                                                                В идеальный конечно :-)
                                                                                А так я скалист.

                                                                                0
                                                                                constrained types

                                                                                Очень нишевая вещь КМК. Что делать, если в runtime попытались, к примеру, вычислить 8 день недели? Ловить exception?

                                                                                  +2

                                                                                  Да. Лучше же словить исключение и откатить операцию, чем записать в базу или передать в другой сервис мусорные данные?


                                                                                  Главное преимущество таких типов — если параметр функции объявлен с этим типом, то можно быть уверенным, что он находится в нужном диапазоне.

                                                                                    +1
                                                                                    Такие типы легко реализуются с помощью обычных классов в любом языке программирования.
                                                                                  0
                                                                                  constrained types: например, целое число от 1 до 10

                                                                                  Pascal так умеет. Иногда удобно:

                                                                                  Subrange Types
                                                                                  Subrange types allow a variable to assume values that lie within a certain range. For example, if the age of voters should lie between 18 to 100 years, a variable named age could be declared as:
                                                                                  var
                                                                                  age: 18 ... 100;


                                                                                  Subrange types can be created from a subset of an already defined enumerated type, For example:

                                                                                  type
                                                                                  Months = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
                                                                                  Summer = Apr ... Aug;
                                                                                  Winter = Oct ... Dec;

                                                                                  Такие типы легко реализуются с помощью обычных классов в любом языке программирования.
                                                                                  Может и так, однако удобнее, когда фишки есть в языке.
                                                                                    0
                                                                                    Второе есть в котлине, называется inline-классы :)
                                                                                    +2
                                                                                    Очень интересная тема! Огромное вам спасибо за нее, и комментарии очень интересные. У меня на хабре практически все статьи так или иначе не эту тему.
                                                                                    Почти со всеми хотелками согласен. Немножко прокомментирую.
                                                                                    Синтаксическая чистота. Глядя на незнакомый код, программист должен четко понимать — где тут операторы, где ключевые слова, где идентификаторы и т.д. То есть в языке должны быть простые правила разделения всех возможных синтаксических сущностей на категории, и по виду сущности сразу должно быть понятно к какой категории она относится. Это же огромное облегчение для syntax highlighter'ов.
                                                                                    Кортежи. Я в свое время написал аж две статьи здесь, на тему кортежей и своих хотелок с ними связанных. Да, кортежи должны однозначно быть частью языка, они должны лежать в основе синтаксиса языка а не прикручиваться снаружи как это зачастую бывает. По сути в любом языке «последовательность чего-то через запятую» — базовый кортеж, и от этого нужно строить весь синтаксис.
                                                                                    tagged unions Штука полезная, поддержка со стороны компилятора должна быть, но хотелось бы, чтобы чистые перечисления и чистые (низкоуровневые) unions остались.
                                                                                    константы — все верно. Вы очень точно сформулировали про виды констант.
                                                                                    Call-by-name семантика Интересны вопросы реализации. Это может быть рантайм — неявно генерируемая лямбда-функция, или compile-time — тогда это шаблонная функция, принимающая фрагмент кода шаблонным параметром.
                                                                                    преобразования да, идея с явным разрешением (или явным запретом) преобразований очень красивая. Для разных программистов и для разных целей может требоваться разный уровень «неявности» преобразований.
                                                                                    рефлексия — может тоже опцией? хотим сгенерировать для класса метаинформацию — добавляем какое-то ключевое слово перед описанием класса, или ставим глобальную прагму. А кому-то может наоборот нужно оставить в бинарнике как можно меньше следов:)
                                                                                    Значения, ссылки, указатели примитивные типы не должны притворяться, они должны быть объектами — но при этом оставаться примитивными типами! Не понимаю почему так не сделать. Ну и по шаблонам в С++ — это концепты, то есть по сути введение нормальной статической типизации в систему шаблонов. Все те гигантские error'ы, которые вылазят, если в шаблон передать не то что ожидается — прелести динамической типизации:)
                                                                                    Минимум сущностей вот здесь не согласен. Поля, методы и свойства — это разные сущности, пускай и будут разными. Делать все поля приватными насильно — не хочу.
                                                                                    Макросы однозначно да. Я писал об этом статью со своим видением, впрочем с тех пор уже кое-что поменялось, да и в дискуссии выяснились некоторые дополнительные факты — в частности, людям нужен универсальный код, который можно выполнить и в runtime и в compile-time.
                                                                                    Функции внутри функций Ну это вообще очевидная вещь. В расширениях GCC она реализована давно, но в стандарте до сих пор нет. Почему?
                                                                                    Substructural type system пока не очень понятно
                                                                                    Сборка однозначно не так как в С++. Во всех следующих языках все сделано гораздо лучше.
                                                                                      +2

                                                                                      Итак, вы захотели параметрический полиморфизм, ограничения полиморфизма и сабтайпинг (а почему не row polymorphism, кстати?). У меня к вам всего один вопрос из трёх пунктов.


                                                                                      Будем обозначать тот факт, что T — подтип U, как T <: U. Кроме того, для дженерик-функций, параметризованных параметром X, являющимся сабтайпом U, будем обозначать этот факт как ∀X <: U в начале их типа.


                                                                                      Итак, пусть T₁ <: T₂ и U₁ <: U₂.


                                                                                      1. Состоят ли типы ∀X <: T₁. X → U₁ и ∀X <: T₁. X → U₂ в отношении сабтайпинга?
                                                                                      2. Состоят ли типы ∀X <: T₁. X → U₁ и ∀X <: T₂. X → U₂ в этом отношении?
                                                                                      3. Как насчёт ∀X <: T₂. U₁ и ∀X <: T₁. U₂?
                                                                                        +1
                                                                                        Вот за это я и не люблю математику! :-)
                                                                                          0

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

                                                                                            0

                                                                                            Я не уверен, что правильно распарсил.


                                                                                            В математике много неудобных нелогичных решений или в любом произвольном ЯП?

                                                                                              –1

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

                                                                                          0
                                                                                          Состоят ли типы ∀X <: T₁. X → U₁ и ∀X <: T₂. X → U₂ в этом отношении?

                                                                                          Может, ∀X <: T₂. X → U₁ и ∀X <: T₁. X → U₂? Иначе-то очевидно не состоят, т.к. для стрелки вариантность аргументов должна быть разной.


                                                                                          Как насчёт ∀X <: T₂. U₁ и ∀X <: T₁. U₂?

                                                                                          Можно же просто запретить термы, в которых связывается неиспользуемая типовая переменная :)

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

                                                                                            Да, не состоит. Это, так сказать, подготовка перед третьим вопросом.


                                                                                            Можно же просто запретить термы, в которых связывается неиспользуемая типовая переменная :)

                                                                                            А кто сказал, что она в U'шках не используется? ;) Я, конечно, немного неаккуратно поставил вопрос, ибо нельзя сказать, что U₁ <: U₂, не описывая, что в контексте при этом X, но мне было лень описывать понятие контекста и идти в википедию за уникодовым значком для turnstile.


                                                                                            Но вопрос-то таки с подвохом.

                                                                                            0
                                                                                            1. да, левый — подтип правого.
                                                                                            2. нет. (если поменять местами T₁ и T₂, то да)
                                                                                            3. Хм. Тут подвох? Я вижу два варианта ответа, но не могу сказать, какой из них лучше:
                                                                                              можно сказать, что выбор типа X не влияет на U, и потому левый будет подтипом. С другой стороны, если предположить, что T₁ это bottom type, а T₂ и U₁ — нет, то левый не сможет быть подтипом правого.
                                                                                              +1
                                                                                              Тут подвох?

                                                                                              Тут всё сложно, на самом деле.


                                                                                              можно сказать, что выбор типа X не влияет на U

                                                                                              Я не говорил, что U не зависит от X, да и для тайпчекера это неважно: сабтайпинг типов справа от точки надо проверять, имея X в контексте (как показывает хотя бы даже первый пример).


                                                                                              С другой стороны, если предположить, что T₁ это bottom type, а T₂ и U₁ — нет, то левый не сможет быть подтипом правого.

                                                                                              Почему?


                                                                                              Хорошая интуиция для дженериков (или, как сказал бы типотеоретик, для System F) — это просто функции из типов в термы¹. То есть, функции, которые принимают тип и возвращают терм, использующий этот тип, прям как обычные функции принимают терм и возвращают терм. Тогда возвращаемое значение правой функции — подтип возвращаемого значения левой функции, а принимаемое значение правой функции — надтип принимаемого левой. То есть, вполне логично было бы сказать, что они в этом отношении состоят. Ну или, апеллируя к интуиции, функцию справа можно использовать в любом контексте, где используется функция слева.


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


                                                                                              ¹

                                                                                              И в более продвинутых системах типов они даже не отличаются синтаксисом и правилами вывода, вопрос только в сортах в formation rule.

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

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

                                                                                                  0
                                                                                                  > как те же темплейты из плюсов

                                                                                                  а можно с этого момента чуть поподробнее?
                                                                                                    0

                                                                                                    Вы можете написать


                                                                                                    template<unsigned int N>
                                                                                                    struct Foo : Foo<N - 1> {};
                                                                                                    
                                                                                                    Foo<10000> foo;

                                                                                                    и компилятор имеет право сделать сильно меньше 2^32 инстанциаций.

                                                                                                    0

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


                                                                                                    Но это так, эмпирическое наблюдение.

                                                                                                      0

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

                                                                                              –1
                                                                                              Присмотритесь к языку Julia — его создавали как раз переосмысливая хотелки программистов
                                                                                                +3
                                                                                                Вот пишу я 30 лет на С++. В слезах. Периодически поглядываю что нового. Вижу смену модных языков которые меняют как перчатки. Отличаются они набором штучек — чего хочется. Иногда попадаются наборы которые мне лично нравятся. Ну и что? Есть ли причина переходить на какой-то конкретный новый набор?

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

                                                                                                Я бы перешел на аналог С++ с приличным синтаксисом, если бы он имел сравнимую с С++ поддержку. Чего нет. Есть ли еще какая-то причина?
                                                                                                  0
                                                                                                  Dlang был как претендент, но не взлетел.
                                                                                                    0
                                                                                                    Еще был Eiffel и тоже не взлетел. Вот его я хотел попробовать.
                                                                                                      0
                                                                                                      Точнее говоря, до сих пор пытается взлететь «с толкача», неся тяжёлые потери в борьбе со сборщиком мусора.
                                                                                                      +2
                                                                                                      Причина — инерционность человеческого мышления.
                                                                                                      Ну и порог вхождения самого языка. Это же еще и библиотеки/фреймворки, и среды разработки, и отладчики.
                                                                                                        +1
                                                                                                        Игры с новенькими наборами не интересны, это молодежные развлечения
                                                                                                        Ну так «курица, или яйцо». Опытные программисты не переходят потому что «молодежные развлечения», а «молодежные развлечения» потому что не переходят опытные.

                                                                                                        Что бы потеснить C++ мало быть лучшим, нужно быть намного-много-много лучшим. А пока что-то такое изобретут, опытные и молодежь будут страдать.
                                                                                                          0
                                                                                                          Вот пишу я 30 лет на С++

                                                                                                          Я бы перешел на аналог С++ с приличным синтаксисом, если бы он имел сравнимую с С++ поддержку. Чего нет. Есть ли еще какая-то причина?

                                                                                                          Ну вот через 30 лет современные молодые языки получат сравнимую поддержку (особенно по количеству легаси)


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


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

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

                                                                                                            А самое главное — и выкидывать-то особо ничего не нужно, на самом деле.
                                                                                                            Ну вот люблю я темплейтами упарываться — так это ж просто определённый вид DSL с определёнными ограничениями. Оно переносится на раст (судя по тому, что я знаю о расте). Оно, блин, даже на хаскель переносится!
                                                                                                            Ну вот люблю я в байтики и SIMD пердолиться — ну так навыки в оптимизации программ будут полезны и в таком же императивном близком к железу языке, как Rust. Особенно если учесть, что у него тот же llvm'ный бекенд.
                                                                                                            Ну вот есть у меня полтора десятка лет отладки за плечами, так я лучше и глубже знаю машину, многопоточность, работу с памятью, работу с внешними библиотеками на сях, которые неизбежно будут всегда.
                                                                                                            И так можно, на самом деле, долго продолжать.


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

                                                                                                            +1
                                                                                                            Вот пишу я 30 лет на С++. В слезах. Периодически поглядываю что нового. Вижу смену модных языков которые меняют как перчатки. Отличаются они набором штучек — чего хочется. Иногда попадаются наборы которые мне лично нравятся. Ну и что? Есть ли причина переходить на какой-то конкретный новый набор?
                                                                                                            Тоже самое, только Делфи, не так, правда, долго (17-й год). Язык очень нравится. Поглядываю еще что-то. Но так ничего толком и не взлетело. Что бы всё в одном и нативное, без виртуалок.
                                                                                                            0
                                                                                                            Powershell:
                                                                                                            $filteredLst = $lst | where y -gt 2
                                                                                                            Есть скриптблоки — анонимные функции — аналог лямбда.
                                                                                                            Их можно выполнить на месте, а можно передать как параметр функции.
                                                                                                            Каринг тоже можно реализовать методом GetNewClosure():
                                                                                                            function f ( $x ) {
                                                                                                            {
                                                                                                            param( $y )
                                                                                                            $x + $y
                                                                                                            }.GetNewClosure()
                                                                                                            }
                                                                                                            Set-Item function:GetPlus2 -Value (f 2)
                                                                                                            GetPlus2 3


                                                                                                            Читаемость получше чем в Python будет:
                                                                                                            $list_b = $list_a | Where {$_ % 2 -eq 0 } | foreach { If ($_ -gt 0) {$_*$_} else {$_*$_*$_} }
                                                                                                            Хотя с математикой родными средствами не очень, можно использовать .Net-класс Math.
                                                                                                              +2
                                                                                                              Читаемость получше чем в Python будет

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

                                                                                                                Как только вы изучили эти символы пунктуации — да. Потому что ресурсов на то, чтобы распарсить и отличить <$> от <*> и от прочих идентификаторов в тексте, надо меньше, чем fmap от liftA2 id, скажем.


                                                                                                                Математики тоже всё время значки выдумывают, нет бы естественным языком писать!

                                                                                                                  +1
                                                                                                                  В конце концов, языки программирования пишутся для программистов, а не для парсера.

                                                                                                                  Конечно это вкусовщина. Я просто привык, что символ пунктуации в тексте программы это своего рода якорь, за который можно уцепиться глазами. Думаю и так понятно, во что превращается текст программы, почти полностью состоящий из таких якорей.
                                                                                                                    0
                                                                                                                    Я про свой визуальный парсер. Поддержка кастомных закорючек, да ещё и с задаваемыми пользователем приоритетами и ассоциативностью, скорее усложняет компилятор, а не упрощает его.

                                                                                                                    Как раз часто используемые абстракции и операции и имеет смысл выражать в виде подобных якорей.
                                                                                                                    0
                                                                                                                    Как только вы изучили эти символы пунктуации — да. Потому что ресурсов на то, чтобы распарсить и отличить <$> от <*> и от прочих идентификаторов в тексте, надо меньше, чем fmap от liftA2 id, скажем.

                                                                                                                    Ну математики сокращают запись за тем, что обычно с выражениями работают. А так, утверждение о том, что ресурсов требуется меньше — не совсем очевидно. Человек слова обычно воспринимает целиком, как единый объект (если это не ребенок, который учится читать), и в этом случае кажется логичным, что проще различить два объекта которые отличаются, например, 3 буквами из 5, чем 1 из 3.

                                                                                                                      0
                                                                                                                      3 буквами из 5, чем 1 из 3.

                                                                                                                      Это зависит от того, насколько различны графически те буквы, которые различаются.
                                                                                                                      В этом смысле спецсимволы предпочтительнее — они изначально более разные, чем буквы.
                                                                                                                        0

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

                                                                                                                          0
                                                                                                                          О плотной абракадабре, вроде как речи не шло.
                                                                                                                        0

                                                                                                                        Ну, прочитать f <$> c мне существенно быстрее, чем fmap f c, и сразу ясно, что там как.

                                                                                                                          0

                                                                                                                          Выигрыш в читаемости начинается с нескольких операторов:


                                                                                                                          createCustomer <!> idResult <*> nameResult <*> emailResult
                                                                                                                          fmap (fmap (apply createCustomer idResult) nameResult) emailResult
                                                                                                                            0

                                                                                                                            createCustomer |> apply idResult |> fmap nameResult |> fmap emailResult


                                                                                                                            Не надо решать проблему вложенности кучей операторов, достаточно добавить один-единственный пайп и дело в шляпе.

                                                                                                                              0
                                                                                                                              Недостаточно, потому что у операторов таки разная семнатика.
                                                                                                                              0

                                                                                                                              А что делает здесь fmap?

                                                                                                                                0

                                                                                                                                Я не знаю, я только 8 секунд на код посмотрел, так что только предполагаю: заворачивает операцию создания кастомера, чтобы она работала с nameResult, emailResult :: Maybe String или подобными монадами аппликативными функторами.

                                                                                                                                  0

                                                                                                                                  Я к тому, что если функторы с существенно разной семантикой (например maybe vs IO), лучше их явно поименовать.

                                                                                                                                    0
                                                                                                                                    Зачем? В сигнатуре видно будет.
                                                                                                                                      0

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

                                                                                                                                        0
                                                                                                                                        Ну и замечательно! Получили фабрику, которая может работать в произвольных аппликативных функторах. Хоть список айдишников, емейлов и имён ей передай, а она список кастомеров с декартовым произведением полей из них вернёт. Или там LogicT какую-нибудь, но я только вчера её увидел и пока не вкурил, так что не знаю, что там будет.

                                                                                                                                        Не люблю останавливаться, короч. Нужно больше абстракций!
                                                                                                                                          0
                                                                                                                                          Ну и замечательно!

                                                                                                                                          Так чего замечательно, если нельзя узнать, как работает код? Такие вещи допустимы в каких-то библиотечных обертках, но не в бизнес логике. Там и обычный-то полиморфизм запрещать стоит, т.к. полиморфизм = неявные касты. Получается из хаскеля джаваскрипт.

                                                                                                                                            0
                                                                                                                                            полиморфизм = неявные касты

                                                                                                                                            Не равно.


                                                                                                                                            Получается из хаскеля джаваскрипт.

                                                                                                                                            Не получается.


                                                                                                                                            Там и обычный-то полиморфизм запрещать стоит

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

                                                                                                                                              0
                                                                                                                                              Без полиморфизма нет инверсии контроля

                                                                                                                                              Мы вообще-то про параметрический полиморфизм говорили, а не про то, что вы подумали.

                                                                                                                                                0
                                                                                                                                                Инверсия контроля и на параметрическом норм выражается.
                                                                                                                                                  0

                                                                                                                                                  Например?


                                                                                                                                                  Почему нельзя? Берёте интересующий вас инстанс и смотрите, что у него в fmap понаписано.

                                                                                                                                                  Дык откуда я его возьму, если в сигнатуре его нет?


                                                                                                                                                  Вот это я сейчас вообще не понял.

                                                                                                                                                  При неявном касте у нас есть некоторый терм, который, в зависимости от конкретного типа, превращается в другой терм (второй из исходного получается неявным кастом). В случае параметрического полиморфизма у нас также есть терм, который превращается в другой в зависимости от типа. В обоих случаях мы имеем ф-ю (терм, тип) -> терм.
                                                                                                                                                  Характер ф-и просто разный.

                                                                                                                                                    0
                                                                                                                                                    Например?

                                                                                                                                                    Ну вместо оопэшного interface ISomeServiceProvider с пачкой виртуальных методов (и сабтайпинг-полиморфизмом), и конструктором, принимающим ISomeServiceProvider и ISomeOtherServiceProvider, у вас


                                                                                                                                                    class SomeServiceProvider a where
                                                                                                                                                       ...

                                                                                                                                                    с пачкой инстанс-методов. И функция


                                                                                                                                                    doFoo :: (SomeServiceProvider a, SomeOtherServiceProvider b) => a -> b -> ...

                                                                                                                                                    Дык откуда я его возьму, если в сигнатуре его нет?

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


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

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

                                                                                                                                                      0
                                                                                                                                                      с пачкой инстанс-методов. И функция

                                                                                                                                                      Это аналог интерфейса с набором реализаций, инверсия контроля тут откуда возникает?


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

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


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

                                                                                                                                                      Ф-и то явно, а вот полиморфный каст — точно так же неявен. Вы когда пишете fmap, то вы не пишите нигде get_instance(fmap, type), вы пишите просто fmap, и этот get_instance вызывается неявно. Но самое печальное не это. Самое печальное (как и в неявных кастах) это то, что в общем случае неизвестен тот самый аргумент type. То есть проблема не в неявном вызове каста, а в том, что неизвестен аргумент, с которым вызывается каст. Неизвестно, к какому типу кастуется ваш терм.


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

                                                                                                                                                        0
                                                                                                                                                        Это аналог интерфейса с набором реализаций, инверсия контроля тут откуда возникает?

                                                                                                                                                        Вызывающий код пихает нужные реализации.


                                                                                                                                                        Или с другой стороны: как выглядит инверсия контроля в ООП?


                                                                                                                                                        Если для понимания работы кода надо искать точку использования

                                                                                                                                                        «Заворачивает создание кастомера в монаду» — достаточно хорошее понимание принципа работы для этого уровня абстракции, как по мне.


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


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


                                                                                                                                                        Вы когда пишете fmap, то вы не пишите нигде get_instance(fmap, type), вы пишите просто fmap, и этот get_instance вызывается неявно.

                                                                                                                                                        А это меня как раз не печалит совсем. Вот если бы такая запись констрейнта Functor f => не требовала бы, то тогда да.


                                                                                                                                                        Самое печальное (как и в неявных кастах) это то, что в общем случае неизвестен тот самый аргумент type. То есть проблема не в неявном вызове каста, а в том, что неизвестен аргумент, с которым вызывается каст. Неизвестно, к какому типу кастуется ваш терм.

                                                                                                                                                        Почему неизвестен? Вот же ж написано прям в сигнатуре: Functor f.


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


                                                                                                                                                        В случае с "просто монадой" предметной интерпретации нет вообще.

                                                                                                                                                        Значит, это библиотечный код.

                                                                                                                                                          0
                                                                                                                                                          Вызывающий код пихает нужные реализации.

                                                                                                                                                          Для этого надо экзистенциальный тип, а это почти то же что подтипирование, вобщем-то.


                                                                                                                                                          Или с другой стороны: как выглядит инверсия контроля в ООП?

                                                                                                                                                          ну вот так без инверсии: yoba(Data data) = new Solver(data), а вот так с инверсией: yoba(Data data, ISolver solver) = solver(data).


                                                                                                                                                          «Заворачивает создание кастомера в монаду» — достаточно хорошее понимание принципа работы для этого уровня абстракции, как по мне.

                                                                                                                                                          Сказать "заворачивание кастомера в монаду" — это то же самое, что ничего не сказать, то есть этот код не дает программисту никакой информации. А если код не дает никакой информации — то он не нужен, его следует удалить.


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

                                                                                                                                                          Это у кого так с обычными функциями?
                                                                                                                                                          Если по функции непонятно, что она делает, то эта ф-я не нужна, удалите ее.


                                                                                                                                                          А это меня как раз не печалит совсем.

                                                                                                                                                          А меня печалит, т.к. это делает код бесполезным.


                                                                                                                                                          Почему неизвестен? Вот же ж написано прям в сигнатуре: Functor f.

                                                                                                                                                          Так это не дает никакой информации, кроме той, что можно на этой хрени вызвать fmap. То есть, данное ограничение, снова, по факту, бесполезно — мы же и так в коде fmap применяем!
                                                                                                                                                          Но то, что на хрени можно вызвать fmap — в свою очередь, не дает абсолютно никакой информации о том, что делает программа. А код, который не несет информации о том, что делает программа — бесполезный код, как я уже выше отмечал. Его можно просто удалить.


                                                                                                                                                          Значит, это библиотечный код.

                                                                                                                                                          То есть весь полиморфный код — автоматически библиотечный? Ну тогда ок, я же с-но об этом и говорю — в библиотечном коде можно извращаться, а бизнес-логику стоить писать понятно.

                                                                                                                                                            0
                                                                                                                                                            Для этого надо экзистенциальный тип

                                                                                                                                                            Зачем? Вон в коде сверху вызывающий код пихает, а экзистенциальных типов нет.


                                                                                                                                                            а это почти то же что подтипирование, вобщем-то.

                                                                                                                                                            Не соглашусь. Почему это?


                                                                                                                                                            И я вам любую функцию, использующую экзистенциальный тип, перепишу через полиморфную функцию:


                                                                                                                                                            data EType where
                                                                                                                                                              EType :: ErasedType r
                                                                                                                                                            
                                                                                                                                                            foo :: EType -> a

                                                                                                                                                            практически синтаксически переходит в


                                                                                                                                                            foo :: (forall r. ErasedType r) -> a

                                                                                                                                                            И раз вы Пирса читали, ЕМНИП он там тоже описывает синтаксическое преобразование экзистенциальных типов в полиморфные.


                                                                                                                                                            ну вот так без инверсии: yoba(Data data) = new Solver(data), а вот так с инверсией: yoba(Data data, ISolver solver) = solver(data).

                                                                                                                                                            Я не понимаю ни первую, ни вторую строчку. Это типа определение функции yoba, принимающей data и возвращающей Solver(data)? Или что там происходит?


                                                                                                                                                            Сказать "заворачивание кастомера в монаду" — это то же самое, что ничего не сказать, то есть этот код не дает программисту никакой информации.

                                                                                                                                                            А в пределе код функции и не должен давать информации, информацию должен давать тип. Кого вообще волнуют термы? :)


                                                                                                                                                            А если код не дает никакой информации — то он не нужен, его следует удалить.

                                                                                                                                                            А это нужный код. На библиотечном уровне, конечно, но нужный.


                                                                                                                                                            Это у кого так с обычными функциями? Если по функции непонятно, что она делает, то эта ф-я не нужна, удалите ее.

                                                                                                                                                            Ну вот какая-нибудь функция pivot, которая по массиву даёт опорный элемент для квиксорта. Если вы окружающий квиксорт не видите, то вы тоже не знаете, как работает программа.


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

                                                                                                                                                            Но так хоть можно в код не смотреть. И понять, что делает foo :: Applicative f => f Id -> f Name -> f Email -> f Customer я могу вот со скоростью чтения сигнатуры, а терм таки надо проанализировать (и где-то в бекграунде, возможно, вывести тип).


                                                                                                                                                            И, кстати, имея там Applicative f вместо конечного типа, я гарантированно знаю, что там не будет, скажем, >>=. Или не будет специфичной для типа функции. Я знаю, что меня волнует только аппликативный инстанс и его законы. Это ограничение на семантику функции. А ограничения на семантику — это хорошо, я люблю ограничения, больше ограничений богу ограничений!


                                                                                                                                                            Например, там точно не будет вещей вроде fromJust/fromMaybe на Id и прочих параметрах, которые могли бы быть в случае Maybe вместо f. Значит, эта функция не имеет никаких дефолтовых значений для них, скажем. И интерпретация Name не зависит от наличия Id. Сколько данных вам дали ограничения, посмотрите!


                                                                                                                                                            Но то, что на хрени можно вызвать fmap — в свою очередь, не дает абсолютно никакой информации о том, что делает программа.

                                                                                                                                                            Ну почему. Берёт и заворачивает! А потом зигогистоморфный препроморфизм


                                                                                                                                                            То есть весь полиморфный код — автоматически библиотечный? Ну тогда ок, я же с-но об этом и говорю — в библиотечном коде можно извращаться, а бизнес-логику стоить писать понятно.

                                                                                                                                                            А любой код с IoC библиотечный, кстати?


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

                                                                                                                                                              0
                                                                                                                                                              Зачем? Вон в коде сверху вызывающий код пихает, а экзистенциальных типов нет.

                                                                                                                                                              Ну так если рассуждать, в хаскеле сам факт вызова ф-и на инстансе уже DI :)
                                                                                                                                                              Штука в том, что DI должен уметь разрешаться в рантайме.


                                                                                                                                                              Не соглашусь. Почему это?

                                                                                                                                                              Ну по факту. Экзистенциальные типы неотличимы с виду от подтипирования. Теория у них другая, это да, но крякают они точно так же :)


                                                                                                                                                              И я вам любую функцию, использующую экзистенциальный тип, перепишу через полиморфную функцию:

                                                                                                                                                              Так экзистенциальные типы это и есть кусок rank-1+ полиморфизма, что тут удивительного?


                                                                                                                                                              Я не понимаю ни первую, ни вторую строчку. Это типа определение функции yoba, принимающей data и возвращающей Solver(data)? Или что там происходит?

                                                                                                                                                              Тьфу ты, я немного ошибся, конечно. Должно быть:
                                                                                                                                                              yoba(Data data) = new Solver().solve(data) — без DI, йоба создает солвер и применяет его к данным
                                                                                                                                                              yoba(Data data, ISolver solver) = solver.solve(data) — с инверсией, солвер передается, т.о. йоба теперь не зависит от конкретной реализации солвера.


                                                                                                                                                              А в пределе код функции и не должен давать информации, информацию должен давать тип.

                                                                                                                                                              Тип — это тоже часть кода, я сейчас под кодом подразумеваю программу в целом. И тип никакой полезной информации в данном случае не несет.


                                                                                                                                                              А это нужный код. На библиотечном уровне, конечно, но нужный.

                                                                                                                                                              Ну тогда его следует оставлять на библиотечном уровне и не применять в бизнес-логике абстрактные фмапы :)
                                                                                                                                                              Вроде, мы оба с этим согласны?


                                                                                                                                                              Но так хоть можно в код не смотреть.

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


                                                                                                                                                              Это ограничение на семантику функции.

                                                                                                                                                              Но Functor, Applicative, Monad и т.п. вещи не накладывают никаких содержательных ограничений на семантику ф-и! В том-то и дело! Под "заворачивает в функтор" могут подразумеваться практически какие угодно действия. В этом суть. Сказать, что что-то функтор или монада = ничего не сказать в плане того, что конкретно делает код.


                                                                                                                                                              Например, там точно не будет вещей вроде fromJust/fromMaybe на Id и прочих параметрах, которые могли бы быть в случае Maybe вместо f.

                                                                                                                                                              Но для этого всего и тип не нужен. Достаточно на код посмотреть и увидеть, что там нету ни fromJust, ни бинда :)
                                                                                                                                                              Но, опять же, отсутсвтие этих вещей опять-таки вам ничего полезного не сообщает. Вы не можете сделать никаких содержательных выводов о том, что делает функция, исходя из того, что внутри ф-и не используется бинд (равно как и исходя из того, что он там используется).


                                                                                                                                                              А то если продолжать вашу мысль, то любая непонятная без контекста фиговина не нужна

                                                                                                                                                              Все именно так.


                                                                                                                                                              а очень часто даже в бизнес-логике без контекста непонятно, что и нахрена.

                                                                                                                                                              Не бывает.

                                                                                                                                                                +1
                                                                                                                                                                Ну так если рассуждать, в хаскеле сам факт вызова ф-и на инстансе уже DI :)

                                                                                                                                                                Ну, в каком-то смысле да.


                                                                                                                                                                Штука в том, что DI должен уметь разрешаться в рантайме.

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


                                                                                                                                                                Ну по факту. Экзистенциальные типы неотличимы с виду от подтипирования. Теория у них другая, это да, но крякают они точно так же :)

                                                                                                                                                                Ну я тут точно не соглашусь. Почему это кусочек System F похож на сабтайпинг? Ну, собственно, как вы и пишете:


                                                                                                                                                                Так экзистенциальные типы это и есть кусок rank-1+ полиморфизма, что тут удивительного?

                                                                                                                                                                rank-2+, если быть точным, ибо forall под скобочками, но не суть. А удивительно то, что вы при этом находите его похожим на сабтайпинг — ну не получается у меня углядеть эту параллель!


                                                                                                                                                                с инверсией, солвер передается, т.о. йоба теперь не зависит от конкретной реализации солвера.

                                                                                                                                                                Ну это прекрасно ложится на указанный мной ранее подход.


                                                                                                                                                                Ну тогда его следует оставлять на библиотечном уровне и не применять в бизнес-логике абстрактные фмапы :)
                                                                                                                                                                Вроде, мы оба с этим согласны?

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


                                                                                                                                                                Но Functor, Applicative, Monad и т.п. вещи не накладывают никаких содержательных ограничений на семантику ф-и! В том-то и дело!

                                                                                                                                                                Это потому что у вас хаскель вместо λC. У вас никто, кроме вашей совести и ревьювера, не требует, чтобы, скажем, fmap id ≡ id (вернее, ∀x. fmap id x ≡ id x, ибо экстенсиональность внутри λC не доказывается, но не суть). А если функции удовлетворяют нужным законам всех этих функторов-трихомонад, то это уже весьма содержательные и важные ограничения.


                                                                                                                                                                Но для этого всего и тип не нужен. Достаточно на код посмотреть и увидеть, что там нету ни fromJust, ни бинда :)

                                                                                                                                                                Но код большой и сложный, а тип простенький и тупенький.


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

                                                                                                                                                                Ну я уже описал выше же. Что эффекты совсем целиком отделены от мира значений. Что интерпретация одного эффекта не зависит от значения в другом эффекте.


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


                                                                                                                                                                Не бывает.

                                                                                                                                                                Ну без контекста квиксорта расскажите мне, зачем нужен pivot из примера в предыдущем комменте.


                                                                                                                                                                И многое зависит от вашего мнения на тему IoC в бизнес-логике, это ж очень актуально в контексте разговора.

                                                                                                                                                                  0
                                                                                                                                                                  Ну так никто не мешает вызывающему коду в рантайме чего-то там определять.

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


                                                                                                                                                                  Ну я тут точно не соглашусь. Почему это кусочек System F похож на сабтайпинг?

                                                                                                                                                                  Ну вот похож, исторически сложилось. А кусочек ЗТ похож на полиморфные типы. Так уж выходит, что фрагменты одной системы могут быть очень близки к фрагментам другой.


                                                                                                                                                                  rank-2+, если быть точным

                                                                                                                                                                  Под 1+ подразумевалось >1


                                                                                                                                                                  А удивительно то, что вы при этом находите его похожим на сабтайпинг — ну не получается у меня углядеть эту параллель!

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


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

                                                                                                                                                                  Достаточно очевидно, что при написании кода абсолютно любой принцип вида "правильно делать Х" может быть нарушен, если конкретная ситуация того требует. Если конкретная ситуация требует абстракций в бизнес-логике — ну ок, важно, чтобы эти абстракции туда не совались просто "потому что могу", по умолчанию. С другой стороны в библиотечной логике как раз более полезен обратный подход — более универсальный код по умолчанию. Согласны с такой формулировкой?


                                                                                                                                                                  Ну без контекста квиксорта расскажите мне, зачем нужен pivot из примера в предыдущем комменте.

                                                                                                                                                                  С-но, оно выбирает определенный элемент массива вполне определенным методом (которые в реализации pivot'a). Чего мы знаем, что делает pivot. Чего мы не знаем без контекста — это где потом используется результат.

                                                                                                                                              0
                                                                                                                                              Так чего замечательно, если нельзя узнать, как работает код?

                                                                                                                                              Почему нельзя? Берёте интересующий вас инстанс и смотрите, что у него в fmap понаписано.


                                                                                                                                              Та же декомпозиция.


                                                                                                                                              полиморфизм = неявные касты

                                                                                                                                              Вот это я сейчас вообще не понял.

                                                                                                                          0
                                                                                                                          Разве нагромождение символов пунктуации читабельнее обычных английских слов с редкими вкраплениями пунктуации?

                                                                                                                          Это зависит от того, к чему вы привыкли. Кому-то удобнее "X, следовательно, Y", кому-то — X => Y.

                                                                                                                        0
                                                                                                                        Я не настоящий сварщик, но в начале статьи начало попахивать хаскелом, мне вот кажется что он наиболее близо к идеалу, хотя ничего реального на нём не делал.
                                                                                                                          +6
                                                                                                                          мне вот кажется что он наиболее близо к идеалу, хотя ничего реального на нём не делал

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

                                                                                                                            0
                                                                                                                            Мне кажется что причина в таких случаях не в языках, а в поддержке. А поддержка зависит не от качества языка. (про хаскел ничего не могу сказать)
                                                                                                                              +2

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

                                                                                                                                0
                                                                                                                                Мне думается это проблема с образованием — учат непойми чему аля паскаль, а потом уже трудно перестраиваться, да и времени учить что-то сильно отличающеся мало, вот в итоге и получается относительно малое сообщество.
                                                                                                                                По идее именно в студенческие годы и время есть и мозги посвежее, чтобы пихать туда разные концепции, а не учить несколько ничем принципиально не отличающихся языков.
                                                                                                                                Предполагаю, что Python, Haskell, Lisp, Prolog, SQL видимо должны изучаться на хорошем уровне как примеры достаточно разных подходов. Что-то ещё?
                                                                                                                                  +2
                                                                                                                                  Не очень понятно. Почему Паскаль аля чего, а скажем Python должен изучаться?
                                                                                                                                    0
                                                                                                                                    Не очень удачно сформулировал: s/учат непойми чему аля паскаль/учат только чему-то аля паскаль/
                                                                                                                                    0
                                                                                                                                    а потом уже трудно перестраиваться

                                                                                                                                    Если трудно перестраиваться — значит, учили плохо и не тому. Я вот после десяти с гаком лет C# взял в руки Python — и ничего, жив пока.


                                                                                                                                    Предполагаю, что Python, Haskell, Lisp, Prolog, SQL видимо должны изучаться на хорошем уровне как примеры достаточно разных подходов

                                                                                                                                    Я предполагаю, что это зависит от того, кого и для чего учат. Мне ваша подборка кажется произвольной.

                                                                                                                                      +1
                                                                                                                                      > после… C# взял в руки Python

                                                                                                                                      для меня это языки одного класса, принципиальных отличий нет

                                                                                                                                      > Мне ваша подборка кажется произвольной.

                                                                                                                                      Python — традиционной императивное программирование
                                                                                                                                      Haskell — чистое функциональное программирование
                                                                                                                                      Lisp — не знаю как его категоризировать, но это мутант ещё тот, от остального сильно отличается
                                                                                                                                      Prolog — логическое/предикатное программирование
                                                                                                                                      SQL — декларативное программирование в терминах предметной области, по сути пример декларативного DSL
                                                                                                                                        0
                                                                                                                                        для меня это языки одного класса, принципиальных отличий нет

                                                                                                                                        Ну так это для вас. А для меня они очень далеки.


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


                                                                                                                                        Мне ваша подборка кажется произвольной.

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


                                                                                                                                        BTW, Лисп — это как раз чистое функциональное программирование.

                                                                                                                                          0
                                                                                                                                          > BTW, Лисп — это как раз чистое функциональное программирование.

                                                                                                                                          изначально да, но как я понял сейчас там каша из всех концепций
                                                                                                                                            0

                                                                                                                                            Ну так и в Питоне каша.

                                                                                                                                              0
                                                                                                                                              Добавление элементов функционального стиля ничего принципиально не меняет при наличии мутабельности и отсутствии контроля за чистотой функций.
                                                                                                                                                0

                                                                                                                                                Вот потому и каша, а не Истинное Функциональное Программирование.

                                                                                                                                            +1

                                                                                                                                            Там чистота немного в другом смысле. В хаскеле она в смысле управления эффектами, в лиспе — в смысле нетипизированного лямбда-исчисления (что вещи даже в каком-то смысле противоположные).

                                                                                                                                        0
                                                                                                                                        учат непойми чему аля паскаль, а потом уже трудно перестраиваться
                                                                                                                                        Если даже Паскаль плохо даётся, я не знаю что вообще в программировании можно делать? Мне кажется, что с Бейсика/Паскаля можно перейти на любой язык почти сразу.
                                                                                                                                          –1

                                                                                                                                          f..mind перестраиваться как раз и не учат. Учат соответствовать. И все оценки вытекают из того насколько вы близки или далеки от предмета обучения. Если потом вдруг выясняется, что вас учили не тому, то система оценок, зафиксированная в вашем красном дипломе, девальвируется. И хорошо если вы сами это понимаете и не страдаете от комплексов. Отличники этому подвержены реже. Так как им все равно, что изучать и чему максимально соответствовать. И чем циничнее такой спец, тем лучше.
                                                                                                                                          Перестраивание и возврат при неудаче между тем основа поиска решений даже в тупиковых ситуациях. Поддержка и сообщество это сомнительные критерии идеальности. В основе идеальности лежат идеи. Насколько хороши идеи настолько язык идеален и удобен. Удобство это еще одно требование человека к языку.
                                                                                                                                          Lisp хорош, но вы про JSONNET и SmallTalk забыли. Я бы предложил начать изучение языков со структур данных, AST, КС -грамматик. Допустим, не все понимают, что объект это уже упакованная структура данных и поэтому предлагают нисходящие алгоритмы там где должны быть самосборки и т.д.
                                                                                                                                          Возможность воспроизведения объекта ценнее его уникальности.
                                                                                                                                          От того как ты думаешь, вытекает то как ты решаешь задачи. Подход к решению важнее текущих знаний…
                                                                                                                                          Поворчал немного. Возраст, однако