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

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

А куда за тот год, пока я не видел Ruby, с него убежали типы?
Никуда не убежали, но в статические они так и не превратились.
Мне кажется, статья будет неполной без этой, классической уже, картинки:
static vs dynamic typing battles
image

К сожалению, очень многие считают, что статическая типизация и обязательные аннотации типов — это одно и то же. И наоборот, что возможность передать в функцию произвольный тип обязательно означает динамическую типизацию. Отсюда и весь срач. Комментарии к этой статье ничем в этом смысле не выделяются.
НЛО прилетело и опубликовало эту надпись здесь
Имеется ввиду type inference, Когда компилятор догадывается о типах самостоятельно.

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

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

НЛО прилетело и опубликовало эту надпись здесь

Речь о динамически типизируемых языках.

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

"Предупреждён — значит вооружен" :) Очень многие инструменты статанализа, даже встроенные в компиляторы языков дают лишь предупреждения в опасных ситуациях, но не прекращают компиляцию.

Как-то за js обидно
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю.

Знакомое чувство.
Ага, есть такое…
Я тоже благодаря C# признаю только статическую типизацию, но не понимаю, от чего вы страдаете?
От того, что иногда (часто) приходится работать с ЯП, имеющими динамический подход. Вот с ними я и страдаю. Но не прям так уж чтобы сильно.

ЗЫ
А начинал я вообще с изучения Си в самом начале 90х по книге Кернигана и Ритчи, которую я, будучи студентом, случайно купил в славном городе Краснодаре.
Представляете, как я страдаю от самой мысли, что существуют ЯП с другими подходами? :) (шутка).
От лютого непонимания парадигмы F#, например. Очень трудно перестроить мозг с ООП на функциональщину. 12 лет с C# не проходят даром.
Ну не надо путать теплое с мягким. Мы все же про типизацию говорим, а в F# система типов тоже строгая статическая. ООП и ФП — это из другой оперы, и, в отличие от типизации, ООП и ФП не взаимоисключающие, к нашему общему счастью. Многие недавние плюшки C# пришли после обкатки на F#.

Я начинал с C#, недавно влез во фронтенд. Не пониманию страданий автора. Динамика позволяет очень легко писать костыли в самом начале, когда структура сырая и ты сам не уверен, как лучше тут сделать. Затем после нахождения определенного пути, ты просто рефакторишь это в типы и все. В итоге в проекте есть фронтир, где на каждом шагу any. А есть устаканившаяся часть, где все по-полочкам. По-моему очень удобно получается.

А я начал с php и js. И теперь понимаю, насколько им мешает динамическая типизация. Точнее, мешает мне, особенно когда в чужом коде роешься.

Вы уверены, что мешает вам динамическая? Большинство проблем типизации с которыми я сталкивался за почти 20 лет PHP+JS были связаны со слабой типизацией, а не динамической.

Лично мне в 1с именно динамическая мешает. Когда чтобы посмотреть что нужно в метод передать приходится лезть в код этого метода. Особенно весело когда метод может не вернуть ничего, один элемент, коллекцию, или строку с ошибкой и все это одновременно.
НЛО прилетело и опубликовало эту надпись здесь
Ну так оно и логично, есть выбор — мелкий проект, прототип — можно без типов, что-то серьёзное — юзай типы.
НЛО прилетело и опубликовало эту надпись здесь
На уровне языка может только и рантаймово, а вот статанализаторы очень рады. И да, в целом в PHP типизация становится строгой с включенным use strict.
Намешано.
— я пишу на C++/go/js/js+flow/typescript, увлекаюсь Haskell и ReasonML и тп. Я понимаю какие ошибки вы описываете, но не понимаю каким боком тут JS. Писать на C# можно ещё хуже, чем на JS и типы не спасут, можно глянуть www.govnokod.ru/csharp
— JS относится не только к фронту, но и к беку, а вся статья пронизана ФРОНТЕНДЩИКИ ПИШУТ ОТСТОЙ
— в проектах совсем мало ошибок, которые уберёт статическая типизация. Никто не пишет невообразимое количество тестов на type errors в js… так же как на C# никто не пишет проверки ссылок на null повсеместно или вы пишете?
— много чего ещё

У меня вывод один, нужно расслабиться и заниматься свои делом. Новичкам эта статья не поможет, а остальные и так все понимают.
Ну как пронизана, когда речь о только о методах работы с данными? Автор же так же пишет фронт, но на TypeScript, что сейчас совершенно логично делать ибо даёт все описываемые им преимущества.
А вот скажите мне, как типы помогают в фронтенде? Как написать тип, когда одна переменная зависит от другой? Например бэкенд возвращает что то вроде:

{
  id: 10,
  some_flag: true,
  other_flag: false,
}


Например, если `other_flag: true`, то возвращается еще один параметр `other_param: 'Some string' `

{
  id: 10,
  some_flag: true,
  other_flag: true,
  other_param: 'Some string',
}


Каким образом можно описать зависимость данных в типах? Только не надо писать: «Поставь `other_param?: string`». Это не решение проблемы.

Вот так можно же:


{
  id: number;
  some_flag: boolean;
  other_flag: false;
} | {
  id: number;
  some_flag: boolean;
  other_flag: true;
  other_param: string;
}
И сколько нужно создавать под-типов одного типа? Данное решение является самоубийством!
И сколько нужно создавать под-типов одного типа?

Очевидно в данном случае два.


Данное решение является самоубийством!

Любое решение является самоубийством, если его применять везде и для всего.

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

Не путайте «говнокод» с извращением. То, что предлагает наш уважаемый коллега mayorovp, является извращением. Такое решение «пригодно» (очень с натяжкой можно использовать это слово) для маленьких систем, где нет больших данных. Представьте себе объект с 200 атрибутами и более 30 внутренних зависимостей. Перечислять все возможные состояния объекта (делать под-типы) будет только человек, не имеющий большого опыта в программировании и дебаге. Очень хочу посмотреть на этого человека, когда появится первый баг.

Также, очень заметным является то, что наш уважаемый коллега mayorovp, имел дело только с типизацией в JavaScript. Если бы у него имелся опыт работы с типами в других языках (например функциональных или в языках с сильной типизацией), я уверен, что он бы такое решение не предложил.

Во-первых, не надо про меня ничего выдумывать. Я имел дело и с C#, и с Haskell.


Во-вторых, объект с 200 атрибутами и 30 зависимостями просто нуждается в декомпозиции. Само существование такого объекта — признак лютого говнокода. Но даже если на бэкенде сидят странные личности, которые не видят никаких проблем в таких объектах — Typescript позволяет провести декомпозицию исключительно на уровне типов с помощью упомянутого мною оператора &. Разбиваем этот мега-тип на 10 типов (именованных!) с 20 атрибутами и 3 зависимостями каждый — и вот уже все не так страшно.


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

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

1) Как вы и писали — декомпозиция, т.е. подкачка данных с других источников (api endpoint). Но это несет за собой такую неприятную вешь, как отклик сервера и количество соединений с ним.
2) Сделать один api endpoint, где будет вся информация.

Что лучше, «засорить» сервер кучей маленьких requests или сделать один request, где будут все данные? На эту проблему нужно смотреть с высоты «нужд» и производительности системы, в частности бэкенда. То, что для вас является «говнокодом», на самом деле есть потребность.

«Я имел дело и с C#, и с Haskell.»
Что-то вообще, ну прям вообще не ощущается у Вас. Могу предположить, что у вас мало опыта (работы с типами) в этих языках и что основным вашим языком является JS.

То есть вариант "сделать один api endpoint, но с адекватным контрактом" вами не рассматривается в принципе?


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

Могу сказать тоже самое про вас. Может быть, вы вместо пустых обвинений возьмете да расскажете как это делается на том же C#?

«Могу сказать тоже самое про вас. Может быть, вы вместо пустых обвинений возьмете да расскажете как это делается на том же C#?»

Коллега, вы вероятно ошиблись. Я с С# никогда не работал и никогда этого не утверждал.

«один api endpoint, но с адекватным контрактом»
Поподробнее, пожалуйста.

Ну тогда приведите как это делается на том языке, с которым вы работали!


И пример с того объекта на 200 свойств и 30 зависимостей тоже приведите.

А почему вы думаете, что я знаю решение? Проблема относиться к фронтенду (увы, я его писал не в Haskell) и я никогда не писал, что у меня есть решение. Я знаю о существовании проблемы, мы наступили на все грабли, на которые только могли наступить. В итоге, после несколько месяцев мучений, мы просто вырубили «flow» в этом куске кода (где работаем с этим мега-объектом), и в ручную делаем валидацию. Идеального решения этой проблемы я так и не нашел.
Поэтому я так активно принимаю участие в этой дискуссии и мне очень любопытно наблюдать за решениями, которые вы предлагаете! Разница только в том, что все эти решения мы уже пытались внедрить, но ни одно решение не дало хорошего результата (нагрузки на сервер, читаемость/поддержка/добавление кода и т.д.). В итоге мы остановились на решении, которое вы называете «говнокодом».

Очень хочется узнать о «один api endpoint, но с адекватным контрактом»

Вы можете сообщить хоть какие-нибудь подробности о своих затруднениях? Хоть один инвариант объекта из тех 30? Примерную структуру объекта на сервере? Правила формирования? Почему вы ожидаете что все остальные способны угадать ваши проблемы?




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


type RawData = {
  id: number;
  some_flag: boolean;
  other_flag: boolean;
  other_param?: string;
}
function hasOtherParam(x: RawData) : x is RawData & { 
  other_param: string;
}
{
    return x.other_flag;
}
По своей сути — это композиция типов и это означает, что новый тип не определен для других функций. В целях переиспользования кода (и композиций функций), все типы должны быть определены перед использованием. А это в свою очередь ведет к «types hell» — миллиону вариаций (типов) одно и того же объекта, и разобраться с этим человеку — почти не реально (что в свою очередь никаким образом машине не мешает, только пару байт сверху).

Спустя некоторое время, я все более склоняюсь к идее, что можно «вертеть типами» во все стороны, чтобы правильно реализовать задачу, но есть задачи, которые невозможно типизировать способом, который удобен программисту. DRY (do not repeat yourself), читаемый код, поддерживаемый код — это пожалуй самые уязвимые места этой проблемы.
А это в свою очередь ведет к «types hell» — миллиону вариаций (типов) одно и того же объекта

Вы можете написать типы с T | undefined (aka T?), тогда тип будет один.


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


Мне кажется, что все ваши упомянутые выше "грабли" были исключительно из-за того, что у вас не было человека, который бы нормально умел в используемый инструмент (flow в вашем случае). Еще вариант — дело было давно, когда во flow еще не было discriminated unions.

Только не надо писать: «Поставь other_param?: string». Это не решение проблемы.

Почему не решение?

Это является «кривым» решением в JS. Мой вопрос касался типов в общем. Не стоит использовать реализацию типов в JS как нечто стандартное, универсальное. Такое явно не прокатит в Хаскелле и вероятно в других языках тоже. Перестаньте смотреть на JS как на «Cвятой грааль», эталон.
Это вообще не JS, это TS. Не надо путать.
Я ничего не путаю. Даже автор статьи написал: «TS — всего-лишь надмножество JavaScript.». Каким бы крутым не был язык TS — он все равно компилируется в JS. И самым слабым звеном здесь является JS. Какой бы «syntax sugar» нам не давал TS, все равно все переводится в JS.

Каким бы ни был крут Haskell, он все равно переводится в машинный код. Какой бы «syntax sugar» нам не давал Haskell, все равно все переводится в машинный код.

Коллега, причем тут это? Или это не удачная попытка «перевернуть» мои слова против меня?

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

«ибо есть транспилеры/компилеры во что угодно»
Согласен. Есть даже интересный проект на github.

«Вопрос, с чем мы работаем, и какие гарантии оно дает.»
Тогда, коллега, задам наводящий вопрос: «Почему TS компилирует в JS а не в машинный код?»
Почему TS компилирует в JS а не в машинный код?

Где востребовано, туда и компилируется. Будет сильно нужно — будут через LLVM компилировать в бинарники. Why not?

«Где востребовано, туда и компилируется.»
Вы сами ответили на свой вопрос! Поздравляю! Сила цепи определяется самым слабым звеном, что в нашем случае является JS.

Г — Логика.

Очень конструктивно. Жаль, что не могу минусовать.
У вас странное представление.

В ассемблере нет никаких типов. При этом хаскель в него компилируется. Вопрос, теряем ли мы все гарантии хаскеля от того, что компилируем его в васм? Приобретем ли мы что-нибудь (кроме производительности), если сделаем машину, способную нативно исполнять хаскель-код?
Давайте я спрошу по другому:

Гарантирует ли «Static type checking» в TypeScript тот-же результат при «Dynamic type checking» в Javascript в Runtime?
То, что у вас все компилируется в TypeScript, совсем не означает, что в Javascript (Runtime) все будет работать. Слабым звеном является Javascript.
То, что у вас все компилируется в TypeScript, совсем не означает, что в Javascript (Runtime) все будет работать.

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

«Приобретем ли мы что-нибудь (кроме производительности), если сделаем машину, способную нативно исполнять хаскель-код?»

Я понял вашу мысль и вероятно не совсем понятно выразил свою.
«Вопрос, с чем мы работаем, и какие гарантии оно дает.»
Тогда, коллега, задам наводящий вопрос: «Почему TS компилирует в JS а не в машинный код?»

Вполне себе компилируется

Вы и C++ можете в JS транспилировать.

С каких пор такая зависимость должна решаться на уровне типов?


Описываете обычный тип состоящий из всех четырёх параметров. При десериализации параметр other_param, в случает отсутствия, останется равной null. А вот уже в коде вы же перед использованием параметра проверите, param_flag равен true или нет?

Почитайте про зависимые типы

Спасибо, прочёл. Но как это в коде реализуется?
Видимо тут пример неудачный, потому что, как уже говорил, я сходу вижу конкретный тип состоящий из 4 параметров. Просто в одном случае мы получим other_param равной null, а в другом — какому-то значению. И уже в коде мы в любом случае будет проверять other_flag равен true или нет перед использованием.


Или в динамических языках это делается как-то по другому? Мне (я серьёзно) интересно даже узнать.

Спасибо, прочёл. Но как это в коде реализуется?

Код знает какие типы могут быть. И может делать выводы о гарантиях. В typescript прочитайте про discriminated unions и type guards


https://www.typescriptlang.org/docs/handbook/advanced-types.html

Хм, ну type guards — это просто проверка типа получается?

Например, как в C#
Аналог instanceof:
if (some is Foo foo)
{
   // Do something with foo.
}

Аналог typeof:
if (some.GetType() == typeof(int))
{
   // В данном случае это заменимо на is.
}


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

А discriminated unions прям как в C#. Разве что выглядит чуть по другому:

Почти копия из TypeScript
interface IShape { }

interface ISquare : IShape
{
    double Size { get; set; }
}

interface IRectangle : IShape
{
    double Width { get; set; }
    double Height { get; set; }
}

interface ICircle : IShape
{
    double Radius { get; set; }
}

static double area(IShape shape)
{
    switch (shape)
    {
        case ISquare square: return square.Size * square.Size;
        case IRectangle rectangle: return rectangle.Width * rectangle.Height;
        case ICircle circle: return Math.PI * circle.Radius * circle.Radius;
        default: return double.NaN;
    }
}


Такая похожесть, скорее всего из-за того, что C# (судя по вики) повлиял на TypeScript.

Одна компания разработчик, один автор языков… Хмм… Да c# похоже все-таки повлиял на тайпскрипт!

Нет, type guards — это вынос проверки типа во внешнюю функцию, применяются совместно со смарткастами. Аналогов в C# у этой техники нет, за отсутствием смарткастов.

Такая похожесть, скорее всего из-за того, что C# (судя по вики) повлиял на TypeScript.

Тут все как раз наоборот — это в C# гварды как в typescript (нормальный паттерн-матчинг не осилили, вот и прилепили гвардо-костыли) :). А в typescript — они как в Racket.

А я два типа из двух и трёх параметров и проверкой типа перед использованием. Код типа


function parseResponse(data) {
  return some_flag ? new SomeClass(data) : new AnotherClass(data);
}

const response = parseResponse(data);
if (response instance of SomeClass) {
  console.log(response.id, response.otherFlag)
} else if (response instanceof AnotherClass) {
  console.log(response.id, response.otherFlag, response.anotherParam)
}

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

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

C таким типом, как выше приводили:


{
  id: number;
  some_flag: boolean;
  other_flag: false;
} | {
  id: number;
  some_flag: boolean;
  other_flag: true;
  other_param: string;
}

не проверить флаг будет нельзя. И ЗТ никаких не требуется.

НЛО прилетело и опубликовало эту надпись здесь
И что? Этого недостаточно чтобы называться завтипом…
НЛО прилетело и опубликовало эту надпись здесь

Ну это не ЗТ.


type Yoba = {
    id: number;
    some_flag: boolean;
    other_flag: false;
} | {
    id: number;
    some_flag: boolean;
    other_flag: true;
    other_param: string;
};

function yoba(x: Yoba) {
    console.log(x.id) // ok, ид есть в обеих ветках

    console.log(x.other_param) // не компилируется

    if (x.other_flag) {
        console.log(x.other_param) // компилируется
    } else {
        console.log(x.other_param) // не компилируется
    }
}

здесь нет зт, это по сути аналог паттерн-матчинга. просто если при стандартных АТД когда у нас data YobaType = C1 | C2, то C1 и C2 не являются сами типами, это просто конструкторы. А вот в случае типов-объединений — это именно типы самостоятельные, по-этому конструктор C1 возвращает один тип, конструктор C2 — другой тип, оба эти типы — подтипы типа YobaType. С-но в хаскеле когда мы находимся в какой-то из веток паттерн-матчинга мы имеем типа YobaType (т.к. типов C1/C2 нету, сузить тип в ветке матчинга протсо не на что), а вот в ts компилятор может сузить тип до более конкретного, т.к. соответствующие типы есть.
Ну и да, тут автолифтинг, то есть значения вроде true, false, undefined, 125, "yoba" автоматически лифтятся в соответствующий тип-синглтон, когда мы используем эти литералы в типовом контексте (вроде же кстати в хаскеле чот такое тоже прикручено в каком-то расширении).
То есть в объявлении типа выше true/false — это, с формальной точки зрения, именно лифтанутые типы-синглтоны, а не значения.

НЛО прилетело и опубликовало эту надпись здесь
Ну так этот паттерн с синглтонами — это как раз классический способ выражения ЗТ в языке без них нативных, но с GADT (например) и DataKinds.

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


Я знаю, что тут есть другая точка зрения, но сам считаю, что зависимые типы — они только тогда, когда есть фичи, которые без лямбда-P не реализуются. Причем, да, получается, мы без лямбда-Р иногда можем делать некие вещи, которые с практической точки зрения мало отличимы от того, как ведут себя зт в определенных кейзах, тут спору нет.


А YobaType как записать можно?

Ну так объединением и записывается, неразмеченным. Допустим, у нас есть два типа A и B, мы сперва каждому типу добавляем тег в виде типа-синглтона, т.о. получаем типы-кортежи <tag1, A> и <tag2, B> и потом эти типы объединяем через обычное (неразмеченное) объединение, получаем YobaType = <tag1, A> | <tag2, B> (опускаем всякие несущественные нюансы со структурным подтипированием, тем что у нас поля на самом деле будут именованные а не безымянный как в кортеже и т.п.).
В случае когда мы при помощи АТД строим тип-сумму, то мы выполняем эти два действия в один шаг, по-этому типы <tag1, A> и <tag2, B> у нас просто не проявляются, то есть операция суммы — примитивная. В данном же случае она не примитивная и выражается через другие операции — умножения и объединения, в итоге у нас типы-компоненты объединения существуют.
В примере выше у нас tag'ом выступает поле other_flag. Ну и тот факт, что внутри true-ветки внутри if мы знаем какой конкретно подтип у значения — он тождественен тому факту, что в ветке паттерн-матчинга мы знаем, что значение может быть рпазобрано паттерном этой ветки. Просто в случае хаскеля мы этот факт (что значение х может быть разобрано паттерном Y) не можем выразить в виде утверждения вида "значение х имеет тип Y", т.к. нету подходящего типа Y. А в тс такой тип находится.


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


так что тут х-ль соснул у ts.

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


filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];

и тут с-но вопрос, что значит это "value is S"? а это значит, что данная ф-я является гвардом (т.е. предикатом над T и истинность этого предиката влечет за собой сужение типа Т до типа S). Иными словами, мы можем подать на вход фильтру массив (A | B)[], гвард isA в качестве фильтра и получим массив A[] на выходе.


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


function isZero(x: number): x is 0 {
    return true;
}

const yoba = [1, 2, 3, 4, 5].filter(isZero);

yoba: 0[] :)

НЛО прилетело и опубликовало эту надпись здесь
Например?

Ну классическое, мы сможем как-нибудь описать Vec[N], создать вектор длины N с соответствующим типом, где N считано из файла, и потом сделать type-safe доступ по индексу?

НЛО прилетело и опубликовало эту надпись здесь
В TS — разумеется, не сможем, потому что к тому моменту, когда нам известен размер массива, никакого TS уже нет.
И как там с этим в TS — сможем?

Нет.


В хаскеле вот сможем.

А можно результат посмотреть, с-но?

НЛО прилетело и опубликовало эту надпись здесь

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


ЗЫ: кстати, для работы еще нужна ф-я length специальная, т.к. без нее вы просто не сможете сконструировать рефл для массива, длина которого в компайлтайме не известна. Какой у нее тип будет?

А что это вы тут такое пытаетесь сделать?

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

fixed size array? Так в TS есть кортежи, которые он чекает. Или хочется именно циферкой задавать размер?
Думаю, именно цифрой. Чтобы также возможно было написать типизированную версию addElement, как вот тут
А, ну на TS такое не реализуемо из-за отсутствия арифметики времени компиляции. Хотя уж куда-куда, а к TS исполнение времени компиляции было бы прикрутить проще всего. Но они там себе на уме, опыт других языков не используют — проходят всю эволюцию самостоятельно.
fixed size array? Так в TS есть кортежи, которые он чекает.

Размер кортежей известен во время компиляции.


А, ну на TS такое не реализуемо из-за отсутствия арифметики времени компиляции.

По факту она (конкретно арифметика) и в примере на хаскеле не совсем чтобы важна.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Эта ветка — лучшая иллюстрация того что статическая типизация вредна для большинства проектов.
НЛО прилетело и опубликовало эту надпись здесь
Что именно очень просто?

Решить обсуждавшуюся задачу.

Так, и в чём она состояла?))
НЛО прилетело и опубликовало эту надпись здесь
Нажмите посмотреть ветку. Разрыв между исходной задачей и тем что в итоге решили я и имел в виду )
НЛО прилетело и опубликовало эту надпись здесь
> ведь от обсуждения исходной задачи мы перешли к довольно отвлечённому разговору о том, что считать зависимыми типами
Вот на этом заканчивается любой type-driven development :)
НЛО прилетело и опубликовало эту надпись здесь
Я кстати так и не понял, как это возможно. Мы же не знаем длину. Соответственно, никаких статических проверок делать не можем, ну просто потому что статическая — по определению до рантайма, то есть не зависящая от конкретных данных, а только от множества их допустимых значений.
НЛО прилетело и опубликовало эту надпись здесь
Я кстати так и не понял, как это возможно.

Компилятор может убедиться в том, что к тому моменту, как мы сделали arr.ref(i), мы где-то выше убедились, что i < arr.length. При этом нам в этом случае не важно, что мы не знаем какой актуально arr.length. Главное, знать, что i — меньше.
В данном случае мы передаем в ref дополнительный аргумент, тип которого является утверждением о наличии такой проверки (выше это тип k <@ n). Если мы можем сконструировать значение данного типа (тип населен), то это является доказательством данного утверждения (типы = утверждения, термы = доказательства, ака измофоризм Карри-Говарда). С-но, чтобы вызвать ф-ю нам надо иметь соответствующее доказательство. Если проверки нет — мы данное значение просто не сможем построить, с-но и ф-ю вызвать не сможем, т.к. она будет требовать аргумент, который мы не можем ей дать.

Ясно, спасибо. Примерно так и думал, но до конца сформулировать не смог.
«С каких пор такая зависимость должна решаться на уровне типов?»
А с каких пор она НЕ ДОЛЖНА решаться на уровне типов?

«Описываете обычный тип состоящий из всех четырёх параметров.»
Вы работали с Haskell? Если бы работали, то не констатировали эту чепуху. То, что это реализуемо в JS (flow, typescript), не означает, что такое прокатит в других языках, где есть типы.

Мой изначальный вопрос ничего общего не имел с js.
НЛО прилетело и опубликовало эту надпись здесь
Благодарю за Idris, не знал о таком. Жаль, что эти псевдо-эксперты типизированных языков, которые накидали мне минусов, типы использовали только в JS и думают, что знают о типизации все. По вам видно, что вы — человек с опытом и знающий.
Я не фронтендер ни разу, но когда возникает необходимость, делаю примерно так, если исходить из вашего примера:

function MyType() {
    this.id = -1;
    this.some_flag = false;
    this.other_flag = false;
    this.other_param = '';
    
    this.map = function (entry) {
        this.id = entry.id;
        this.some_flag = entry.year;
        this.other_flag = entry.grade;
        if(this.other_flag)
            this.other_param = entry.other_param ;
    };
}


Так как обмен данными формализован, набор полей которые может вернуть сервер известен, описываю тип в котором есть все поля которые могут придти, и после приема данных вызываю map, в нем можно доделать нужные трансформации и присвоить к полям типа который будет использоваться уже в JS. Ну и докинуть все нужные проверки.
И какую проблему решает данный этюд?
Позволяет привести к строгому типу принятые данные. И добавить при необходимости валидацию.
Строгий тип не проблема, хоят в некоторых случаях может быть решением. Про валидацию не понял, сервер отвечает
{
    ...
    this.other_flag = false;
    this.other_param = 'Deal with it';
}

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

К сожалению без "as any" и "// @ts-ignore" в TypeScript пока не обойтись.
Потому что существуют библиотеки вроде React, в которых типизация поставляется отдельно (в пакетах "@types/..."). И часто типизация написана неаккуратно или отстает от последней версии библиотеки.

НЛО прилетело и опубликовало эту надпись здесь

Специально для таких случаев существует Declaration Merging https://www.typescriptlang.org/docs/handbook/declaration-merging.html. Не нравится как описан какой то тип из тайпинов? Допиши сам!
А ставить any или того хуже @ts-ignore это подкладывать грабли себе же самому

А хотите пример? React useRef(). На flow он выглядит так (исходник):


function useRef<T>(initialValue: T): { current: T };

В @types/react так:


function useRef<T>(initialValue: T): { current: T; };
function useRef<T>(initialValue: T | null): { readonly current: T | null; };

А теперь попытаемся открыть в TypeScript пример из документации


const intervalRef = useRef();
intervalRef.current = setInterval(() => {});

Для Flow все нормально, он даже типы выведет. А TypeScript скажет: [ts] Expected 1 arguments, but got 0. И пока саму библиотеку и ее типизацию поддерживают разные люди, такое будет встречаться регулярно.


Или вот прекрасная библиотека mobx-state-tree. Все в ней хорошо: и реактивность, и снапшоты, и статическая типизация, и проверки типов в runtime. Но похоже, что ее TypeScript интерфейсы не являются публичным API. Несмотря на то, что они эскпортированы из модуля. Да и сама библиотека написана на TypeScript.


Внезапно, между двумя минорными версиями 3.5.0 и 3.6.0 мы видим:


- export interface IMSTMap<C, S, T> {
+ export interface IMSTMap<IT extends IAnyType> {

Ну и как таким пользоваться?


Это все не проблема TypeScript как языка. Это проблема экосистемы в целом. Я до такого докопаюсь. Возможно, напишу свои декларации. Буду их скрупулезно поддерживать. Но придет мой коллега из лагеря динамической типизации, и скажет: "Почему я должен тратить время на все это?"

НЛО прилетело и опубликовало эту надпись здесь

Так не используйте такие библиотеки, какие проблемы?

все там нормально у реакта с тайпингами :)
Типы рождаются не из ключевого слова class/struct/type а из вашей головы. Когда вы думаете о задаче, вы сводите ее к набору категорий, взаимодействующих по определенным правилам. Это относится не только к программированию, это способ мышления вообще. Если мы думаем об обществе, мы не думаем о каждом человеке по отдельности. Поэтому никого не удивляет что у человека есть метод «спать», а у общества нет. Поэтому, даже используя JS я создаю категории объектов которые типами не называются лишь номинально.
Вы начали с Шарпа и строгая статическая система типов потребовала от вас умения проводить свои мысли в порядок. Те же кто начал со скрипта и не имел врожденной склонности к порядку получили в голове кашу, причесать которую очень сложно. поэтому их код похож на доску конспиролога.
Добавлю ещё что ООП и типизация перпендикулярные понятия. ООП получается если прикрутить код к данным (объекты носят свой код с собой). Это можно сделать на любом современном языке. Где-то проще, где-то сложнее.
Можно написать достаточно удобную и типизированную программу даже на классическом Бейсике с двухбуквенными имеными переменных и отсуствием структур. А можно — кашу на любом «строгом» языке типа Ады.

Хотя корелляция между и есть, но разница между подходами, описанными в статье — примерно как между строителем мостов типа нашумевшего Крымского и строителем мостов через ручьи. Где можно натянуть несколько верёвок, прикрутить к ним палок — и всё.

Попытка построить таким путём мост длиной в 10 километров приведёт к тому, что его шторм унесёт в море задолго до того, как вы закончите его строить… а попытка строить мост через ручей «по феншую» приведёт к тому, что он обойдётся в 100 раз дороже… хотя и стоять будет дольше — но это не всегда оправданно.
aikixd
Добавлю ещё что ООП и типизация перпендикулярные понятия. ООП получается если прикрутить код к данным (объекты носят свой код с собой).
Истинно так. ©
В JS незачем тащить никакое ООП как стандарт. Есть внешняя JS библиотека (а может их и много существует), используя которую, вы можете «породить» такое ООП (со своими правилами «наследования», «использования кода объектов») которое только придумаете сами!

Незачем «приколачивать» конкретную реализацию ООП в стандарт JS. Это уже(!) смысла не имеет никакого. — Хватит, что бессмысленный «const» ввели в стандарт JS.

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

Понимаете ли какое дело… Будь у этих ребят в голове каша, мы бы не видели весьма сложных и нетривиальных библиотек ни на js, ни на питоне, ни на чём-то подобном. Но мы их ВИДИМ. И КАК они разработаны, для меня, как приверженца типов и статики, полная загадка… Возможно я чего-то в этой жизни не понимаю. И в этом мне дико хотелось бы разобраться, хотя сам скорее всего так со статикой и останусь.
Те кто писал эти библиотеки, действительно талантливые программисты. И библиотеки эти создают некий порядок в проектах, который хоть как-то спасает. И заодно прочности у них хороший, так как насилуют их нещадно.
Он же не пишет, что у всех программистов на динамически типизированных языках в голове каша. Каша у тех, кто не может в самодисциплину. Статическая типизация дисциплину навязывает, а динамическая — нет, приходится самому.

Откуда вы знаете, может это private метод, к которому просто никто не обращался)

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

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

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

На самом деле, думаю, что вы совсем не одиноки. Я уже как-то скидывал однажды картинку из доклада Романа Елизарова про развитие ЯП, скину еще раз, так как она весьма показательна.
Топ 20 языков программирования и их типизация
image

Чертой отделены языки, вышедшие в 20 и 21 веках. Здесь отлично видно, что индустрия в последнее время однозначно делает выбор в пользу статической типизации. Добавим сюда факты, что в своем развитии php пришел к строгой типизации, в python вроде добавляли статическую типизацию, в мире фронтенда набирают популярность ts, dart, flow, в реакте сделан тайпчекинг. Ну, и наконец, учтем тяготение императивных современных языков к функциональщине, где есть стремление выражать возможные состояния через типы и сама система типов еще более строгая.

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

Системы типов в мейнстрим языках становятся всё более развитыми и позволяют всё большую гибкость (= меньше напрягают, больше помогают), поэтому остаётся всё меньше поводов выбирать динамическую типизацию.
НЛО прилетело и опубликовало эту надпись здесь
Интересная точка зрения.
В свою очередь, моя интерпретация вашей интерпретации, связанной с экспоненциальным ростом экосистемы Javascript, заключается в том, что без вагона костылей на Javascript написать ничего вменяемого ни у кого не получается. Отсюда и перманентные попытки придумать серебряную пулю.
Что является следствием того что JS был много лет единственным языком для фронта. В свою очередь его архитектура является следствием того, что нужно было обеспечить быструю интерпретацию на слабых машинах конца девяностых.
Интересно девки пляшут… И как же по-Вашему динамическая и никак не выводимая из контекста типизация помогает быстро интерпретировать код? Наоборот, в рантайме перед каждой операцией придётся ещё проверить, что именно в переменой лежит, если надо сделать какие-то умолчательные преобразования и т.д. А вот то что для подобного языка гораздо проще и быстрее написать интерпретатор, это да. Сильно подозреваю, что с js именно так оно и было.
Сильно подозреваю, что с js именно так оно и было.
А не надо подозревать. Брендан сам рассказывает, что у него было 10 дней на всё-про-всё.

Получился, конечно, уродец тот ещё… но могло быть и хуже…
Костыли js это результат того, что языку приходится решать задачи выше его компетенции, и только во фронте, как тока браузеры на 100% договорятся и будут следовать последней версии спецификации никаких костылей не будет.
TS на бэке используют только консерваторы типа автора статьи.

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

> В то время как тот же Javascript быстро развивается

В чем это выражается? JS за последние 5 лет что-то принес остальному миру, новые идеи, практики? Или он растёт только относительно себя же?

> экосистема растёт экспоненциально

Рост экосистемы ценен тогда, когда каждый элемент работает из коробки, а вместе дают синергетический эффект. А когда «часто типизация написана неаккуратно или отстает от последней версии библиотеки» — это рост в сторону бесконечного решения специфической «проблемы ромба» из за несовместимости кусочков инфраструктуры и вечных войн тупоконечников с остроконечниками, в каждой итерации заканчивающихся созданием новых, несовместимых со всем остальным, кусочков. И переписывания старых на TypeScript.
А есть ли за всем этим синергетический эффект? Или только экспоненциальный рост сложности?
В чем это выражается? JS за последние 5 лет что-то принес остальному миру, новые идеи, практики? Или он растёт только относительно себя же?

Вот тут пожалуй с Вами не соглашусь. Да, развивается. И пожалуй да, быстрее чем всё прочее вместе взятое. Поглядите на библиотеки! То что js ИСКЛЮЧИТЕЛЬНО гибок, этого у него не отнять. Гибче пожалуй только Forth(хотя на мой взгляд это гибкость самоубийцы). И именно потому что бурно развивается, мне очень хочется понять ПРИЧИНЫ такой дикой популярности… Чисто любопытства ради.
Поглядите на библиотеки!
На какую стоит посмотреть, чтобы увидеть что-то новое и интересное?

Пока что всё, что я видел — это, в основном, новые способы сделать то, что раньше можно было уже сделать и так — только с новым синтаксисом и порождением большего количества кода в результате.
Вы имеет ввиду библиотеки вроде left-pad?

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

С риторическими — прекрасно можно!

  1. Можно пример?
  2. Как вы определяете считается вопрос риторическим или нет? Решаете ли вы тем самым за вашего собеседника ожидает ли он ответа на вопрос? Если да, то по какому собственно праву?
Можно пример?

Так выше, тот самый пост, на который вы отвечали. Риторические вопросы являются вопросами синтаксически, но при этом содержательно — они являются утверждениями. А с утверждениями можно соглашаться либо нет:


Ритори́ческий вопро́с — риторическая фигура, представляющая собой вопрос-утверждение, который не требует ответа.

такие дела


Как вы определяете считается вопрос риторическим или нет?

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


Если да, то по какому собственно праву?

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

js ИСКЛЮЧИТЕЛЬНО гибок

В чем же состоит ИСКЛЮЧИТЕЛЬНАЯ гибкость самого JS, которая оказывается вдруг не свойственна остальным языкам общего назначения?

Полагаю автор комментария выше имел ввиду, что в JS в runtime вы можете вытворять практически всё, что угодно, кроме перегрузки операторов (её нет). Всякие там proxy, символы, defineProperty с хитрыми полями, getter-ы и setter-ы, toPrimitiveValue|toString|valueOf и пр. позволяют с виду простой очевидный донельзя код заставить делать что-то совершенно иное. Чем-то сродне define true false но в runtime. Весь этот хаос может как позволить написать супергибкие решения, так и опрокинуть вас в пучину хаоса с использованием минимума кода. Мне кажется, что JS это язык с беспредельным количеством анархии. Этакий godmod. Даже прототипная система наследования устроена так, что вы можете менять предков, поля\методы предков в мановению щелчка пальцев. Вы можете легко переопределять почти любые объекты и методы из "стандартной библиотеки". Ну в общем если в двух словах — уровень допустимого беспредела неограничен. И всё это в runtime. Хорошо это или плохо — судите сами.


За другие языки "общего назначения" не скажу. Полагаю таких языков полно.

Не знаю насчёт «общего назначения», но, когда я увидел, как можно вывернуть наизнанку код на Ruby, вмонтированный в RPG Maker (обычный Ruby, только немного урезанный), мысли были в общем и целом схожие.
в JS в runtime вы можете вытворять практически всё, что угодно,… Всякие там proxy, символы, defineProperty с хитрыми полями, getter-ы и setter-ы, toPrimitiveValue|toString|valueOf и пр.

В этом нет ничего уникального. Ладно, если на C/C++ это будет выглядеть колдунством над сырой памятью, но на языках с полноценным рефлекшном, вроде C#/Java это будет происходить в управляемой среде с контролем типов.

Вы можете легко переопределять почти любые объекты и методы из «стандартной библиотеки».

Манкипатчинг вроде считается плохой практикой и часто используется, емнип, для костылей, вроде полифилов. Но это локальная проблема веб-фронтенда.
но на языках с полноценным рефлекшном, вроде C#/Java

Я не знаток C#/Java, но разве рефлексия в этих языках про это? Беглый гуглёж пишет: "В мире .NET рефлексией (reflection) называется процесс обнаружения типов во время выполнения". Да и судя по примерам там сплошные: IsGeneriсParameter, GetNestedTypes, InvokeMember, GetType и подобные штуки. Т.е. скорее анализ существующих методов. Я выше писал про вещи соооовсем другого порядка.

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

Т.е. скорее анализ существующих методов.

Развитые типобезопасные аналоги in, hasOwnProperty и isPrototypeOf и прочих.

Я выше писал про вещи соооовсем другого порядка.

Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.
Конечно, синтаксически языки отличаются, но обеспечить ту же семантику возможно. Например, в JS Object — это по сути Dictionary, написать ему обертку с прототипами, про модификацию свойств, итерирование, упоротый кастинг в соответствующие примитивы и будет так же весело.
Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.

Если бы я ещё хоть что-то понимал в C#. На мой взгляд все тьюринг-полные языки позволяют решать все задачи. Никто же не мешает написать на brainfuck операционную систему.


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

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


Другой вопрос — а оно надо вообще? Вся эта анархия. Оставлю его за кадром. Речь шла о гибкости. И да, JS ну прямо очень гибкий. Можно построить какого угодно, даже неудовлетворенного желудочно, кадавра из практически любого объекта, что попадётся под ноги. Любителям извращений JS подходит на пятёрочку.

Ну и работать это будет только на вашем отдельно взятом велосипеде, верно?

Чтобы решить мою задачу, мне пригодится мой велосипед)

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

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

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

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

Но даже если упоролся, у меня нет причины заниматься манкипатчингом в рантайме, я сделаю правки в исходниках библиотеки.
То есть нельзя. Аминь.
Вообще, строго говоря, можно.
Рефлексия позволяет сделать всё, включая прямую генерацию IL-кода.
Я не идеально разбираюсь в процессе, но из кажущихся решений — можно подменить вызов метода своим методом.
Но надо понимать, что это code injection со всеми связанными с этим особенностями, и относится больше к области хакинга.
Мне это понадобилось ровно один раз — в стандартной библиотеке работы с сетью при https запросе внутри происходит два http запроса, и между ответом первого и отправкой второго не прокидываются куки. А мне очень надо было. После быстрого гуглежа и попытки — я получил ошибку с крашем стека приложения и сделал всё совершенно иначе.
После чего открыл для себя возможность поправить и перекомпилять уже собранную сборку через дизассемблирование парой хитрых инструментов, и больше вообще не запаривался этим вопросом.
> поля приватные

Это рефлекшену разве мешает?

Ну а вообще, можно хоть Microsoft Fakes в боевой код притащить. Но могут побить.

Вот например в эко-системе redux необходимо работать с immutable-данными. Каждая манипуляция над данными приложения должна менять весь store-приложения цельно (привет ФП). По сути это что-то вроде 1 переменной на всю "программу". А т.к. JS это не Haskell, то код, позволяющий этого добиться, выглядит… несимпатично, если сказать мягко. Та же картинка, я полагаю, со всеми императивными языками. Так вот, в JS отдельно взятые авторы написали велосипед, позволяющий писать визуально мутабельный код, оставив его под капотом иммутабельным. Работает это за счёт использования механизма Proxy. В итоге:


store.list[12].someValue = 12;
// =>
const newList = [...list];
newList[12] = { ...list[12], someValue: 12 };
const newStore = { ...store, list: newList };

Мы казалось бы явным образом задали 12 элементу массива в поле someValue число 12, а на деле произошло нечто совершенно другое. Просто каждая манипуляция с store трекалась, и вместо реальных объектов всё время подсовывались Proxy-обёртки, которые все операции записи превращали в down-up rewrapping.


Дичь? Ну наверное. По словам автора это даже довольно быстро работает. В C# получится написать такой костыль?

Насчет скорости и "довольно быстро работает" тут еще подумать надо, но синтаксически вот так сделать получится без особых проблем:


var newStore = store.Set(s => s.list[12].someValue, 12);

Ну вы сравнили. У вас какой-то Immutable.JS получился. Даром не нужен :)

А всё не так. В реальном redux-reducers коде у вас только эти 9 символов, помноженных на каждую мутацию, и останутся. За ними мало что разглядеть можно будет. В гробу я видал такую гибкость и наглядность. redux-reducers в себе хранят по сути всю бизнес-логику приложения. Т.е. весь треш и угар как раз здесь. Чем более наглядно здесь описана логика, тем проще с этим работать. Так что да, более чем принципиальны. А если ещё вспомнить, что это поделие (immutableJS) тормозит, то я вообще не понимаю, зачем им кто-то пользуется.

А почему вы решили, что предложенный мною вызов будет тормозить так же как ImmutableJS?

Ну тут я погорячился, да :)

Звучит действительно дико)
Решение влоб — через обертку над стором, который бы внутри пересоздавался на каждое изменение. Основная проблема, над которой придется пободаться — это поддержка синтаксиса, ибо для поддержки кастомного сеттера проксировать необходимо весь граф объектов внутри. Но это должно быть решаемо и сильно зависит от исходного API доступа к свойствам.

ЗЫ Но, да, в .NET вряд ли появится redux, ибо глобальное состояние — это отличный маркер плохого дизайна и за это там гоняют тряпками. У JS-сообщества это еще впереди.
Так вот, в JS отдельно взятые авторы написали велосипед, позволяющий писать визуально мутабельный код, оставив его под капотом иммутабельным.

А какой толк от того, что код где-то там иммутабельный, если он выглядит мутабельно?

Мутабельный код в JS выглядит проще, писать его легче и быстрее, читать и понимать тем более. За счёт простоты и наглядности в нём потенциально меньше багов. При этом в redux нам нужен именно иммутабельный код, для нормального функционирования системы. Этот инструмент призван помочь усидеть на двух стульях сразу. Дёшево и эффективно писать бизнес-логику не лишаясь преимуществ flux-подхода.


Справляется или нет он с этой задачей — не знаю. Я не пробовал это в деле. Запомнил как пример самой яркой дичи в JS для продакшна, что мне только попадалась.

Вообще-то тут речь идёт не о «мутабельном коде», а о «мутабельных переменных».

Работать с которыми сложнее, отлаживать сложнее и понимать сложнее.

Но при большом желании всё это делается и на C++ (через шаблоны) и на C#/Java (через reflection)

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

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

Почему бы тогда просто не писать просто мутабельный код, который будет просто мутабельно работать?


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

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

Что? Я вас не понимаю. Иммутабельность нужна для… иммутабельности? Что?


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

Что? Я вас не понимаю. Иммутабельность нужна для… иммутабельности? Что?

Я говорю, не важно как у вас что работает ВНУТРИ. Пусть оно хоть миллион раз будет мутабельно — это не важно. Иммутабельность ценна именно интерфейсом — тем, как мы работаем с данными.
И если мы работаем с данными мутабельно, то никакого толка от того, что где-то внутри там что-то иммутабельно — нет, все профиты сразу исчезают. С другой тсороны, если у нас интерфейс выглядит как иммутабельный, хотя внутренняя реализация мутабельна — мы все профиты иммутабельности имеем. С-но это и есть идеальный вариант "на двух стульях" — пишем иммутабельный код, который по факту мутабельно работает. Попытка же сделать наоборот — это попытка отобрать худшее.

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


У редьюсеров иммутабельный интерфейс — он требуется редуксом. А вот внутри редьюсера можно уже писать как угодно — при условии сохранения интерфейса.

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

И к чему вы это?


У редьюсеров иммутабельный интерфейс — он требуется редуксом.

Только стейт не редьюсеры меняют, а диспатч, который мутабельный.

В смысле не важно, как оно работает внутри? Что вы такое говорите? Как может быть не важным то, что по факту исполняется? Это как раз в первую очередь важно.


Затем, как вы можете потерять "профиты" от иммутабельности, если под капотом работает иммутабельность. Куда они денутся? Если вместо мутации объектов вы используете новые, то ссылочная целостность никуда не девается, pure-функций не становятся impure-функциями.


Ну и касательно — писать иммутабельный код, который на самом деле под капотом мутабельный… Ух. Я себе если честно даже представить себе не могу, что это. И уж тем более не могу представить зачем.


Я вас решительно не понимаю. И кажется не только я )

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


F# в пример. Под капотом, там все мутабельное, как завещала CLR. Иммутабелным все делает язык (интерфейс к коду).

В смысле не важно, как оно работает внутри? Что вы такое говорите?

В том и смысле, что не важно.


Как может быть не важным то, что по факту исполняется?

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


Затем, как вы можете потерять "профиты" от иммутабельности, если под капотом работает иммутабельность. Куда они денутся?

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


Ух. Я себе если честно даже представить себе не могу, что это.

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


И уж тем более не могу представить зачем.

За тем, что имеются все плюсы иммутабельности, и при этом работает быстро.


Если вместо мутации объектов вы используете новые

Так это под капотом где-то они новые. Вам-то какая разница? С точки зрения вашего кода они как старые.

Druu, я думаю вам стоит ознакомиться с документацией Redux поближе. И возможно с документацией к React.pureComputed | React.memo. На случай если вам вообще интересны эти библиотеки и вы планируете использовать их на деле.


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

Про «экшоны», наверное, я писал.
Собственно, вот такие, прямо из документации по Redux.

Мне всё понятно. Пишите чистую функцию, внутри которой используете мутабельные переменные, хотя бы классический императивный for (i=0; i<param.length; i++; а не функциональные map, reduce, etc. и получаете код, который снаружи выглядит как иммутабельный функциональный (чистая функция), а внутри сплошная мутабельная императивщина.

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

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

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


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

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

Это гарантируется на уровне системы типов, то есть если кто-то на старую константу ссылается — то такой код просто не будет скомпилирован из-за ошибки типов.
Гуглите clean и linear types, если интересно.

Хороший пример это утилита jq.
Внутри у неё все функции имеют чистый вид: значения принимаются, а новые возвращаются — но при использовании проверяется, единственная ли это ссылка, если да, то изменения производятся по месту.
НЛО прилетело и опубликовало эту надпись здесь
Толк — в интерфейсе. И в общем инварианте redux «ссылочная эквивалентность старого и нового значения равносильна отсутствию изменений».
Кстати, не планируют ещё для immutable splice массивов сделать такой же spread-синтаксис, как для объектов?
Тогда бы весь этот код выродился во что-то типа
const newStore = { ...store, list: [...list, 12: {...list[12], someValue: 12 } ] };

Хрен редьки не слаще. Глаза от этих точек и скобок разбегаются :)

Даже если переносы строк оставить?
const newStore = {
  ...store,
  list: [
    ...list,
    12: {
      ...list[12],
      someValue: 12
    }
  ]
};
Для изменения одного единственного значения, конечно, выглядит многословно. Но если и под это будут пилить сахар, то боюсь уже диабет получить.

я бы вот так написал


const newStore = {
  ...store,
  list: list.map((item, index) =>
    index === 12 ? { ...item, someValue: 12 } : item
  )
};
Зачем перебирать все элементы?
есть же .slice()
list: [...list.slice(0,12), {...list[12], someValue: 12 }, ...list.slice(13)]

Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.
Все можно, можно даже сборку декомпилировать и заново собрать, если она в IL, но это адский труд по сравнению с
someObj.existingFunction = function() {
    console.log("Ha-ha!");
}
Если вам нужен дизайн с подменой функций — в c#
someObj.existingFunction должен иметь тип Func или Action.
и вы спокойно сможете подменить функцию.
Либо делегат (delegate ), либо event (он всё равно подтип делегата). Всё это является указателями на функции и может быть переприсвоенно, либо в случае с event — на него можно подписаться-отписаться любым своим обработчикам.

Хотя всё это вы и сами знаете.

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

«когда в руках молоток, все предметы начинают напоминать гвозди»

Это у вас пример решения. А зачем вы такое решение используете?
Я такие решения не использую, хотя по молодости как-то переопределил WebForm_что-то что-бы какую-то хитрую логику прикрутить. Но это не отменяет факта, что JS позволяет творить невообразимые вещи.
> JS позволяет творить невообразимые вещи.

Ок, записал в колонку «недостатки»
Понятие «предка» объекта в C# и JS разное. JS-объекты ссылаются на общего предка (который может ссылаться на своего предка и далее по цепочке), в то время как в C# объекты полноценно включают в себя экземпляры объектов всех родителей (отдельно для каждого ребенка).

Обеспечить похожую семантику (map-объектов и прототипного наследования) — это как раз не проблема.
Если у вас это изначально запланировано, то конечно не проблема. В JS для этого ничего делать не нужно — берешь и меняешь prototype.
Естественно) Особый класс JS-ных проблемы с сопутствующими решениями для C# сначала должны как-то появиться.
Моей целью было показать, что такую же семантику обеспечить можно, это не убер-фича JS, которой нигде больше нет. А то, что фактически разный код будет работать по-разному — это достаточно очевидно на мой взгляд (=
НЛО прилетело и опубликовало эту надпись здесь

Что если мы хотим подменить Count у стандартного класса List<T>? Просить майкрософт поменять свойство на делегат?

НЛО прилетело и опубликовало эту надпись здесь
А не надо этого хотеть

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

Не говоря про то, что это используется в тонне фич того же дотнета, начиная от всяких DynamicProxy (есть такой класс, реализует все интерфейсы. в WCF активно используется), и заканчивая всякими Moq/MS.Fakes/…
НЛО прилетело и опубликовало эту надпись здесь
Вы ответили не на тот вопрос. По сути вопрос «как расширить или подменить объект или поведение объекта, когда автор не предусмотрел такой возможности». Код на C# делает совершенно не то, что код на JS.

Может я зря вмешиваюсь, но если копать в эту сторону, то и в JS можно написать такой код, который хрен поменяешь. Например в замыкание вам не пролезть никак. Так иногда private методы и значения реализовывают.

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
Фризит, но не всё.
> Вы спросили что нельзя сделать в шарпах — вы получили ответ.

Мне кажется, это некорректный ответ. Ну как и, например, «чего нельзя сделать на JS — интерфейс».

Да, в языках разная механика внедрения изменений в поведение объектов. А ещё синтаксис разный.

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


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

> Смысл в том, что в шарпах вообще нет механики внедрения изменений, если автор их не предусмотрел

Так у вас проблема с автором, я не с языком.

Понимаете, в C# поведение List всегда неизменно. Изменение происходит на уровне подмены на другую реализацию IList. Хоть DynamicProxy, хоть черт в ступе — но это будет другой тип, с другим поведением. Поведение старого типа на уровне инстанса поменять нельзя, а просить этого некорректно. И внедряется уже не метод, а объект. Но в JS метод — тоже объект (так что это в JS получается нельзя иметь свойства, отличные от Func/Action), так что тут всё пока равнозначно. Куски объекта в JS вы тоже не можете подменять.

Хак с Object.freeze — это сродни записи приватного свойства через рефлекшн.

Но если у свойства тип List, а не IList, то тут уже в руки надо брать Emit… Ну или брать паяльник и идти к автору (кстати, майкрософт по делу запретил перегружать Count).

Это не значит, что чего-то нельзя, это значит, что язык поощряет использование собственных парадигм (и своевременное использование паяльника). А вы хотите, вместо исправления рецепиента (как надо бы) исправлять хаком сам внедряемый класс, то есть на C# писать как на JS.

А ещё в JS можно написать

someObj.existingFunction = function() {};
someObj.existingFunction = 6;

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

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


Понимаете, в C# поведение List всегда неизменно. Изменение происходит на уровне подмены на другую реализацию IList. Хоть DynamicProxy, хоть черт в ступе — но это будет другой тип, с другим поведением. Поведение старого типа на уровне инстанса поменять нельзя, а просить этого некорректно. И внедряется уже не метод, а объект. Но в JS метод — тоже объект (так что это в JS получается нельзя иметь свойства, отличные от Func/Action), так что тут всё пока равнозначно. Куски объекта в JS вы тоже не можете подменять.

Я прекрасно понимаю. И даже согласен, мне не нравится анархия, которая дается таким образом.


Тем не менее, если такое понадобилось, то JS дает больше средств.


Я помню, как у нас люди работали с SharePoint, так там пришлось сборку декомпилировать и подменять в IL-коде константы, потому что майкрософт захардкодили там коннекшн стринги и имена своих таблиц, которые иначе нельзя было изменить. В итоге патчили хекс-редактором сборки, и загружали обратно, с красной пометкой "НИКОГДА НЕ ОБНОВЛЯТЬ И НЕ ЗАМЕНЯТЬ". Костыль, конечно, но вот один случай когда такое понадобилось у меня в коллекции есть.

> Тем не менее, если такое понадобилось, то JS дает больше средств.

Так их не больше, они просто доступнее.

Ну то есть, основные кейсы по сложности мало отличаются, фактически только синтаксисом.

А когда проблема не в программировании, а в SharePoint — это точно проблемы с автором. Я в hex-редакторе драйвер принтера правил, который лишнюю команду слал. Он был на каких-нибудь сях написан, это был очень плохой язык программирования, там даже IL кода нет. Причем, на каком языке печатать ни пытайся — тот язык тоже плохим становится. Даже JS.

Так что я предлагаю ограничиться тем, что проблемы языка заканчиваются вашими (доступными вам для изменения) исходниками и BCL. А проблемы сторонних продуктов — уже нет.
НЛО прилетело и опубликовало эту надпись здесь
Допустим, есть Алиса из отдела биллинга и Боб из отдела доставки. Алиса получает объект, патчит его, передает в функцию, написанную Бобом. А в этой функции Боб тоже патчит этот объект, но уже на свой лад. Поведение системы становится непредсказуемым.

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

Можно. Но не нужно. Либо вы разрабатываете всё, соблюдая некоторые «правила игры» — и тогда статическая типизация автоматически напрашивается (как простой способо переложить часть забот о «соблюдении правил игры» на компилятор) — либо вы всё разрабатываете под лозунгом «что хочу, то и ворочу, главное — чтобы костюмчик сидел». И влазите через задный проход в чужие функции, правите чужие фабрики, перекрываете Object.freeze и т.д. и т.п.

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


Ну, как пример, решили перейти с LEMP(HP) на .Net +C#. Вроде что-то делается для кроссплатформенности активно самой MS, но в целом пока впечатления по новостям, что такой переход будет означать полный перевод инфраструктуры разработки и эксплуатации на Windows и другие продукты MS типа VS, IIS и, не дай бог, SQL Server, что уже будет далеко не локальным техническим решением, облегчающим жизнь разработчикам, а глобальным экономическим, административным и кадровым решением.

Мы сейчас говорим не о случае, когда у вас 15 жуткое легаси и 15 уровней костылей. А когда вы начинаете новый проект (как топикстартер).

Переходить на Windows не нужно: в Linux есть куча типизированных языков. Go, Java, тот же TypeScript.

А что культура разоработки нужна другая — это да. Об этом и статья.
Да даже без жуткого легаси — запускаем проект новый, знаем как строить инфраструктуру для динамического языка, но не знаем как для статического. Ну или для динамического на этом собаку съели, а для статического только учебные проекты по туториалам выкатывали, ни одного подводного камня не встретив в тепличных условиях.

Я про конкретный C#. Допустим только его команда более-менее знает или готова изучать.

И не вижу я никакой другой культуры разработки. Вот в чём она другая? Только он конкретных людей зависит. Один в динамически типизированном обложится аннотациями, тайп-хинтами, тестами и статанализаторами с максимально строгими настройками, а другой навставляет any/variant везде и настройки строгости на минимум.
Какая чушь, .NET Core прекрасно работает на Linux, как и SDK с компилятором, имеет встроенные сервер, можно вообще без сервера запускать, поддерживается Apache, Nginx, имеет нормальные драйверы под MySQL, PostgreSQL, MongoDB, работающие под IBM DB2, это из только из личного опыта, плагины для кучи редакторов, включая Sublime и конечно VS Code.
Поаккуратней с выражениями, пожалуйста. Ощущения не могут быть чушью. Заметил, что вы опустили VS. Она тоже портирована под Linux?
VS code портирован и содержит все необходимые инструменты для разработки. Как уже обсуждалось, опыт работы с точки зрения редактора там примерно такой же как в VS благодаря omnisharp. Ну и есть тонны расширений для всего на свете, что для веба очень полезно.
То есть IDE под Linux для C# так и нет. Разве что IDE
Mono.
vscode вам недостаточно IDE? Рефакторинг, подсказки, отладка, тестирование, чего только нет. Чего конкретно не хватает? Ну кроме заветного IDE в описании на википедии.
Того, что MS приводит в качестве плюсов VS :)
Спросим еще раз. Что конкретно? Чего конкретно нет в vscode, что вам необходимо, чтобы писать под .Net Core. MS может что угодно приводить в качестве плюсов. Чего вам конкретно не хватает. Если вы просто демагогии ради тут проблемы выдумываете, то так и пишите.
Есть Rider от JetBrains. Он сравним по функционалу с VS для C#. Под капотом тот же R#.
Ощущения могут быть чушью, если они никак не коррелируют с реальностью. Нет, VS не существует под Linux, как и многие другие программы, как нет и многих программ под Windows. Этот никак не отменяет возможность .NET разработки под Linux.

Уже 2 года разрабатываю исключительно неткор под докер (убунту образ, естественно, стандартный microsoft/dotnet:2.0-runtime). Я пишу в студии, коллеги на маке в райдере. При чем тут разработка на типизированных языках и Windows? Причем взяли самый майкрософтовский язык C#, и даже с ним оказывается можно полноценно писать на любой ОС.


MS SQL во-первых может быть на другой машине нежели приложение, во-вторых многие пользуются постгресом без каких-либо проблем, привет Entity Framework.


IIS тоже давным-давно устарел, уже много лет как вышел Kestrel.


Ну и так далее....

В общем-то можно подменить, но с некоторыми оговорками:
1) С дженериками без указания конкретного типа не сработает.
2) Придётся лезть в unsafe.
3) Оптимизация может загубить всю подмену, если в одном и тоже методе вызывать смену метода у типа и использовать этот тип. Я не спец по .NET, но видимо причина во встраивании.

Реализация уже существует: Harmony
А конкретно метод DetourMethod

Пробный запуск
static void Main(string[] args)
{
    MethodBase methodToReplace = typeof(List<int>)
        .GetProperty("Count")
        .GetMethod;
    MethodBase methodToInject = typeof(Program)
        .GetMethod("returnNegative", 
            System.Reflection.BindingFlags.Static | 
            System.Reflection.BindingFlags.NonPublic);

    // Это тот самый метод из ссылки выше.
    DetourMethod(methodToReplace, methodToInject);

    Check();
}

static int returnNegative()
{
    return -1;
}

private static void Check()
{
    List<int> list = new List<int>();

    // Вернёт -1.
    Console.WriteLine(list.Count);
}

Да, это известные схемы, в частности была отличная статья на хабре с иллюстрациями (тыц). Но это все «чит» и обход C# хаками нижележащей системы CLR.
А как это реализовано в JS?

Весь «чит» тут в подмене ссылки на скомпиленный метод, если бы не защита памяти, то даже PInvoke не пришлось бы использовать и всё в пределах C# (хоть и в unsafe) можно было бы делать.
А вообще рефлекшн — это C# или CLR?

Или typeof(System.Int32) — ещё C#, а typeof(System.Int32).Attributes — уже CLR?

typeof это C# а System.Int32 это CLR :)

К рефлекшену есть доступ из C#, а например хак `ICorJitCompiler` нужно писать на плюсах (на шарпах не сделать вообще), и подключать в шарпы уже сборку на них.
Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.

Любую задачу можно сделать на любом языке, потому что в худшем случае сначала пишете на языке А интерпретатор В, а потом пишете оригинальную программу на В и все работает. Только это не та мощность, которая нас интересует.
В Java при помощи рефлексии можно не только получить информацию о классе/методе/поле, но и изменить их
Не знаю, какие вы примеры смотрели, на MSDN, например, все совершенно прозрачно — GetProperty(«DontDoIt»).GetValue(myObject);

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

Как сказал товарищ Free_ze если вы пользуетесь таким функционалом, вы либо совершенно точно понимаете, что вы делаете, и иначе это не сделать (Mocks-библиотеки, IoC-контейнеры, AOP-фреймворки), либо вы претендуете на свой зал славы на говнокод.ру.

Вы точно внимательно прочитали о чём мы с Free_ze говорили? Мне кажется, что нет. Вот GiAS похоже прочитал не по диагонали и упомянул суть ― в JAVA это не readonly.

Я, конечно, напрямую не упомянул SetValue, но он там тоже есть. Если бы вы внимательно прочитали мой ответ, то увидели бы, что я привел примерами Mocks, IoC, AOP — далеко не readonly примеры. Все это в шарпе существует.

Если на то пошло, то рефлексия в статически типизированных языках в разы сильнее этой идеи «обратись к методу по строке его имени» — поэтому там и существуют пресловутые IoC-контейнеры и Mock-библиотеки. А в JS приходится довольствоваться spy(«myMethod») и иже с ним.

В PHP, в слабо динамически типизируемом языке, есть и IoC- контейнеры, и Mock-библиотеки.

Потому что есть рефлексия, которая появилась в PHP5-7 в рамках тенденции к переходу на типизацию и ООП.

Типизация в PHP всегда была. Слабая динамическая. Есть тенденция к переходу на более строгую динамическую. Из 23 лет своей публичной истори 18 лет PHP живёт с ООП, с версии 4.


И без стандартной рефлексии вполне можно обойтись обычным кодом на PHP — это, по сути, не фича языка, а обычная библиотека.

Окей, я, очевидно, ушел в другую степь. Я никоим разом не имел в виду, что в языках со слабой динамической типизацией есть какие-то проблемы с рефлексией. Я говорил, что данные проблемы есть в JS, и что хотя код вида
var x = new X()
x['myMethod'] = () => console.log('hi')

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

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

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

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

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


По поводу нельзя отличить "методы" (функции!) от свойств без "typeof" — а в чём проблема то? И почему typeof лишний. Ничего не понимаю. И да, функции заменят на свойства (другие виды свойств), и наоборот. Код который проверяет тип свойства перед его применением — рядовой код. Скажем какой-нибудь фильтр может быть задан строкой (привет lodash), RegExp-ом, объектом или методом. Если строкой, то по ней создаётся метод. Ну просто сплошь и рядом в JS такое. Как вы могли этого никогда не видеть. Удобно же. В нединамических языках это вроде называется "полиморфизм", а у нас утиная типизация. Она распространяется не только на параметры функций, но и на объекты с их полями.


В JS методы это first-class-citizen. Т.е. просто разновидность значения. И работают с ними, соответственно точно также. Я так понимаю, вы их куда-то отдельно выносите, и хотите для них отдельного API? Ну так он в пару строк пишется.


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


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


Ещё, если придираться, то наверное, какие-нибудь грабли с Proxy есть. Но в эту сторону я не копал.


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

if (IsValid(someArg) == false)
{
    throw new ArgumentException(nameof(someArg), $"Invalid value.");
}

В JS так нельзя, что, конечно, минус, но довольно слабый аргумент. Остальные примеры мимо кассы. Действительно мощный концепт, который вряд ли в JS появиться, это Expression. Но это заменяется псевдоязыками и транспайлероми.

Попробовал бегло разобраться с тем, что такое nameof в c# и только запутался. Судя по всему примерам, если написать не nameof(anything), а просто "anything", то получится тоже самое. Но я не думаю, что такой механизм кому-нибудь мог бы понадобиться. Что оно делает на самом деле? Результатом это штуки можно управлять? Можно написать nameof(a) и получить не "a"?


Expression штука забавная. Но оно наверное и правда в JS не появится из-за того, что есть eval и его родной брат new Function. Правда на фоне Expression это более костыльный подход. Неспроста он зовётся eval.

В C# повсеместно применяется авторефакторниг, и после переименования anything в something вам не нужно вручную менять везде «anything».
Expression это не просто исполнение выражений, вы можете написать на C#, с проверкой типов и прочим, а на выходе получить… да хоть JS, можете склеить несколько выражений и т.д., при должной сноровке инструмент невообразимо мощный.

P.S.: как вы nameof(anything), eval и т.п. форматируете?

Да, в таком разрезе это выглядит полезным. Интересная штука. Обе из них.

Наверное, всё же, не в С#, а в заточенных под него IDE. В каком-нибудь vim без полноценной поддержки языка вы вряд ли получите те же гарантии правильности рефакторинга, которые есть в средах, заточенных под C#, а особенно использующих его родной анализатор (не знаю, возможно ли это, но со стороны MS было бы логично использовать "кишки" компилятора C# в VS)

Это да, но компилятор все равно проверит что указанный в nameof идентификатор существует.
C# давно перешел на открытый roslyn с возможностью его кастомизации своими расширениями. А вкупе с omnisharp дает той же vscode и вообще любому другому редактору такие же возможности по работе с C# кодом, что и у полноценной студии.
Поддержку языка абстрагировали в langserver.org. Vim поддерживает lsp, судя по сайту. Есть реализация lsp для C# под названием omnisharp.

github.com/OmniSharp/Omnisharp-vim

Ну т.е. править структуры можно и на лету. Это другое дело. И да, я не телепат )

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

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

Согласен. Это отличный способ отстрелить себе ногу и должен применяться с осторожностью даже в строго типизированных языках. А в JS за такое надо как-то наказывать и желательно жестоко.
НЛО прилетело и опубликовало эту надпись здесь

Вы про синтаксический сахар или про возможности языка? Что из того, что вы перечислили нельзя реализовать в коде, Кроме перегрузки операторов (про неё я как раз упомянул). Про рефлексию смешно, да. Шампунь для лысых.


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

НЛО прилетело и опубликовало эту надпись здесь

С такой формулировкой соглашусь. Я сам тот ещё "сладкоежка". Согласен, что в JS есть не все виды сахара, которые доселе были придуманы. Но за последние 5 лет их добавили столько, что спецификация языка, наверное, выросла раза в 4. И на подходе новые и новые. Уж кому-кому а JS разработчикам на острый дефицит сахара жаловаться не приходится. Я вот с нетерпением жду pipe-оператора.

я тоже не понимаю плюсов динамической типизации

Ну вообщем-то есть один плюс: компиляторы/интерпретаторы для ДЯП писать гораздо легче :)

Блин, то же самое написал! Честное пионерское, когда писал, Вашего коммента ещё не видел! :))))
У вас интересные доводы, я отчасти с вами не согласе, может потому что из фронтенда. Хотелось бы увидеть мир вашими глазами, так сказать. Поэтому попрошу, можете посоветовать книги по ооп и статике. Что можно поизучать и посмотреть.
Если хочется попробовать со статикой поработать, то вам надо в первую очередь выбрать язык со статической типизацией, а учитывая ваш опыт фронтенда — то попробуйте написать десктоп или мобильное приложение, заодно посмотрите как с помощью статически типизированных языков разрабатывают интерфейсы. Например, можете взять C# + WPF.

Из книг можете взять следующие:
1. Грэди Буч «Объектно-ориентированный анализ и проектирование с примерами приложений».
2. Роберт Мартин «Принципы, паттерны и методики гибкой разработки на языке C#» — там описываются принципы SOLID, которые очень важны в ООП (особенно Inversion of Control, IoC).
3. Сергей Тепляков «Паттерны проектирования на платформе .NET» — описание классических паттернов банды четырех с использованием C# + SOLID. На мой взгляд, эта книга написана поинтереснее, чем у GoF.

Кроме того, если выберете определенный язык, то в книгах и мануалах по этому языку всегда будет описание того, как работать с типами, наследованием, полиморфизмом.
Языки с динамической типизацией — суть исключительно скриптовые.
Динамическая типизация несет социальныую функцию — привлечение к программированию непрограммистов. С ростом сложности задач каждый сам приходит к статической типизации.
Серьезно?! Какой-нибудь erlang смотрит на вас с легким недоумением :-)
А откуда он на нас смотрит? Из какого проекта? Просто интересно…
WhatsApp к примеру. Ну и ejabberd соответственно.
Эрланг в телекоме имеет место, там же и был придуман изначально.
При всей популярности WhatsApp… Он скорее по разряду «привлечение к программированию непрограммистов». Потому что идея использовать номера телефонов вместо ников — это круто, это они молодцы, а вот то, что оно потом не может ни жить на двух устройствах, ни даже бекап сделать инкрементальный… как раз указывает на то, что с идеями — у них хорошо… с программистами плохо.
Использование номера телефона к языку программирования не имеет отношения. Да и как видим, полтора миллиарда пользователей использование номера устраивает.
То есть перечисленные претензии к вотсапп это личные проблемы очень маленького количества гиков. Не нравится, не используй.
Не нравится, не используй.
не работает, когда
полтора миллиарда пользователей
то, что оно потом не может ни жить на двух устройствах, ни даже бекап сделать инкрементальный… как раз указывает на то, что с идеями — у них хорошо… с программистами плохо
Про невозможность жить на двух устройствах — это была принципиальная позиция Яна Кума.

<сарказм> Оу, то есть это не ограничение эрланга?? </сарказм>

Есть большая разница между «не может» и «не хочет».
Приложение — именно «не может». А уж какие там высокие идеи у них были, из-за которых пользователям мучиться приходится — не так важно.
Но язык тут ни при чем.
с программистами плохо
И тогда причём тут программисты?
А это это всё сотворил? Марсиане?
Про телеком вам уже намекнули.
Из того, что «ближе»… rabbitMQ или какой-нибудь couchDB, например.
RabbitMQ
Есть сложные задачи, в которых нет однозначного алгоритма, а его еще надо найти. Писать такие на статических языках — ад.
Было бы интересно взглянуть на пример такой проблемы.
Например, поиск плагиата. Я решал его, когда это еще не было мейнстримом.
Полагаю, штуки вроде плагинной архитектуры на динамических языках по естественным причинам работают удобнее. Я в свое время с MEF намучился, полагаю, на каком-нибудь жс/тс было бы сильно проще.
Есть сложные задачи, в которых нет однозначного алгоритма, а его еще надо найти. Писать такие на статических языках — ад.

Честно признаюсь, ни разу не замечал. Хотя занимаюсь подобными достаточно часто.

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

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

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

У вас есть алгоритм. Вы его делаете, со всей типизацией. Он не работает или издержки неприемлемы. Вы его выкидываете и начинаете заново.

И так раз пять.
У вас странные представления о том как пишутся алгоритмы.
У вас странные представления о моих представлениях
Похоже тут обычная несогласованность терминологии. Насколько я понимаю, речь про некую исследовательскую работу.
Т.е., утрировано, дано «Абляция», а в результате надо получить «Прецессия».
1. Придумываем «как решать»
2. Реализуем
3. Что-то не устраивает — скорость, качество, потребление памяти, удобство использования, косяки с граничными случаями, вообще не работает, etc.
4. goto 1

И, как мне кажется, в таких случаях таки да — проще прототипировать на объектах JS или ассоциативных массивах PHP, чем каждый раз переделывать статически типизированную модель данных.
В описанном сценарии типизация не виновата никак вообще. Возможно, алгоритм херовый, возможно, у постановщика задачи в голове моча и он не знает, что требовать и как продукт будет развиваться, возможно, программист херово знает язык, возможно задача в принципе не решаема. Ни динамическая, ни статическая типизация к этому отношения не имеют.

О каких издержках кстати речь? Что код тормозит? Или процесс разработки слишком долгий? Или памяти много потребляется?
Статические аннотации требуют лишней работы. Если программа не дает результата и требует переделки, вся эта работа становится лишней. По-моему, это очевидно.

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

Чем вам поможет типизация?

Введете тип Text и его метод Process?

Введете типы String и Word? А нужны ли они? И если нужны, то как должны быть реализованы? А если реализация оказалась неудачной, но просто заменить ее внутренности, не затронув верхние слои, не получается?
Введу, я еще и тип «предложение» введу, который будет массивом указателей на свои «слова». :)
Типизация мне поможет выстроить иерархию объектов, написать короткие и безопасные методы, поможет избежать мне ошибки «не туда».
А еще как очень правильно написали выше — типизация структурирует и приводит в порядок мысли.
Мне кажется, основная проблема статических типов, с которой борется gatoazul — это не собствено типы, а [де]сериализация и маппинг. Чего греха таить, с сериализацией статических типов зачастую всё обстоит несколько сложнее, чем с джаваскриптом или каким другим "-скриптом". Вместо жонглирования на весу строками и простыми/ассоциативными массивами приходится нудно обмазывать свои классы всяким вспомогательным кодом: метаданными, сериализаторами, мапперами, DTO и проч., чтобы только завелась загрузка/сохранение данных. А если рефлексии нет, то всё это ещё и полностью вручную.
Да нельзя выиграть во всем, но я согласен сделать чуть больше работы, что бы быть уверенным что мой код работает стабильно и именно так как я задумал.
НЛО прилетело и опубликовало эту надпись здесь
Статические аннотации требуют лишней работы.

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

Отнюдь не все статически типизированные языки требуют аннотировать свои типы.

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

Таварисчь наверно имел в виду ситуацию, когда задача сильно изменчива. Тогда возможно и да. Типонезависимые синтаксические формы высокого уровня возможно лучше будут выглядеть на чём-то динамическом. Хотя я правда для для подобной задачи (моделирование алгоритма БПФ Кули-Тюки с целью его конвейерезации и запихивания в FPGA) успешно юзал С++. Кстати это был вообще первый случай, когда добровольно и сознательно заюзал классы, до того писал в чисто процедурном стиле. А ведь есть ещё современные языки с выводом типов, такие как Rust! На чем-то подобном вообще всё должно ложиться мягко и пушисто…
Динамическая типизация несет социальныую функцию — привлечение к программированию непрограммистов.

Уже несколько раз здесь писал, повторю ещё раз. Готов был бы в это поверить, если бы не сложность библиотек, написанных на js, питоне и прочем динамическом. Это говорит о том, что скорее мы, сторонники статической типизации чего-то не понимаем.
Посмотрите лучше не на сложность библиотек, а на сложность решаемых с их помощью задач. Ну вот, хотя бы, пресловутый новый дизайн GMail: огромное количество кода, «сложнейшие» библиотеки… и что в результате?

Вот чего всё эта бурная деятельность породила? Более скруглённые уголки? Сравните, хотя бы, с Eclipse или Intellij Idea… ну или функциональность Google Docs сравните с MS Office 95 (и то и то — 12 лет с первой версии)?
Ну вот, хотя бы, пресловутый новый дизайн GMail: огромное количество кода, «сложнейшие» библиотеки… и что в результате?

Ээээ батенька… Это Вы ещё яндекс-почты не видели! Вот где жопа так жопа… У меня почта на яндексе с 2004-го года, постепенно дозреваю, чтобы от неё отказаться. Ноут хоть и не самый современный, но и далеко не слабый. Тормозит так, что материться хочется! Впрочем думается это скорее проблема не js, а говнокодеров. И скорее всего она одна и та же что для гугла, что для яндекса. Смешно. Помню как повсеместно отказывались от флеша (мне тогда было очень грустно ибо AS3 я очень любил и часто юзал как язык моделирования перед реализацией на С или в FPGA). Одной из претензий было что говнокодеры заполонили все тырнеты своими говнобаннерами, и в результате нормальному юзеру стало невозможно серфить. Ну хорошо. Отказались. Сейчас та же жопа, но только с js. Проблема в ЧЕЛОВЕКЕ, а не в технологии…

Вот чего всё эта бурная деятельность породила? Более скруглённые уголки? Сравните, хотя бы, с Eclipse или Intellij Idea… ну или функциональность Google Docs сравните с MS Office 95 (и то и то — 12 лет с первой версии)?

Абсолютно согласен. Но тут проблема того же порядка. Когда из-за какой-то мало кому нужной фичи прикладывают титанические усилия, и в результате как Офис 95 тормозил на 486DX, так теперь Google Docs тормозит на i5. Нет, вру. Сейчас всё круто! Ибо тормозит в онлайне, в броузере! Прогресс твою мать! :)))
А вообще мне кажется человечеству давным-давно пора несколько притормозить с онлайном, гаджетами и прочей информационщиной. И заняться чем-то скучным и неинтересным. Например колонизацией океана. Или космоса. Иначе боюсь добром это не кончится. Живём всё-таки в реальном мире…
это скорее проблема не js, а говнокодеров. И скорее всего она одна и та же что для гугла, что для яндекса

Парочка из крупнейших IT-компаний не могут найти «не говнокодеров», но к JS это не имеет отношения, конечно же.

Было уже несколько статей объясняющих тормоза gmail. И там всё объяснялось не уровнем разработчиков, а системой поощрения и мотивации. В общем менеджментом. Пожалуй JS к этому отношения и правда не имеет. Не исключено, что на стороне backend-а там тоже "всё плохо", но это покрывают железом.

Пожалуй JS к этому отношения и правда не имеет.
На первый взгляд — нет. А вот на второй… Если посмотреть на библиотеки для «классических» языков (ну там Qt или даже, прости госсподи, MFC или Swing) — то там не происходит ежегодных «революций», заставляющих всё переделывать в 100500й раз… а программы пишутся и правятся… и есть ощущение, что больша́я (если не бо́льшая) часть всей этой деятельности вокруг JavaScript — как раз рождена из тех же самых побуждений, что и тормозной интрефейс GMail…

Ну так и в ruby\python\php нет ежегодной революции. Дело не в дин. типизации. Это на JS навалилось так много народу и новых задач за относительно короткое время. По сути была пустыня с кактусом, пришёл прораб и сказал — здесь должен быть город на 40 миллионов человек, роща и огромное озеро, и заверте… Как тут без революций.

Парочка из крупнейших IT-компаний не могут найти «не говнокодеров», но к JS это не имеет отношения, конечно же.

Вам уже ответили, причем на мой тупой и неоригинальный дилетантский взгляд — очень логично. Добавлю от себя. В веб-дизайне (что клиентском что серверном) на мой взгляд процент говнокодеров вообще сильно выше среднего по IT. Только это особенность не js или php, а рынка труда. Просто вакансий по вебу приходится наверно сотня на одну про мои любимые FPGA. И куда идёт человек, полгода назад решивший стать программистом? Правильно! В веб-прогерство. Потому куча народу постоянно пробует в этом свои силы. В том числе народа совершенно левого, не пригодного для программирования вообще. С другой стороны народ, почувствовавший к IT вкус, из веба вобщем-то рассасывается. Ибо задачи там обычно не сильно творческие. Я например люблю веб-программирование только из-за его «отзывчивости». Я могу сделать проект, который увидят миллионы людей и я могу вступить с ними в какие-то отношения. Но всё это только потому, что пишу я исключительно для себя (ну хорошо, признаюсь, что собираюсь кое-что из своих поделок продавать). Работать же предпочитаю в совершенно другой области.
Эти компании имеют достаточно ресурсов, чтобы отсеивать кандидатов при малейших сомнениях, поэтому я не думаю, что проблема избытка проходимцев там стоит настолько остро.
Другое дело, что JS своим дизайном не способствует росту скилла «рабочей командной лошадки». Получаются именно «ниндзи», творцы с полетом мысли, когда код пишется быстрее, чем обдумывается ТЗ (как тут уже неплохо подметили), которым путь в другие области и языки уже практически заказан (ибо слишком много шаблонов придется рвать, а тут уже неплохо кормят).
В Google Docs реализовано одновременное редактирование документов несколькими пользователями в почти реальном времени по сети.
Ничего подобного не было ни в Office 95, ни даже 2007. По-моему, сложность реализации этого не меньше, чем форматирования ячеек в таблице.
Проблема в том, что эта фича как раз была реализована ещё 12 лет назад — и сделали её за полгода (или, может, даже меньше). И за прошедшее время весь «прогресс» не привёл ни к появлению стилей, ни к поддержке 3D-чартов, ни к чему-либо ещё из полезных фич, про которые бывшие пользователи MS Office спрашивают.

Зато не то два, не то три раза изменили весь дизайн и переписали код с нуля, неизмеримое число раз «игрались с цветами» и прочее.
Функционал действительно очень ограниченный и не особенно развивается, но нельзя говорить, что collaboration за 12 лет не менялся. Еще когда приходилось им активно по рабьоте пользоваться (лет 5 назад), я замечал, что стабильность и корректность мерджа изменений несколько раз заметно улучшалась. Что сейчас, тяжело сказать. Кроме «неопознанных капибар» ничего не заметил.

Так Gmail-то как раз — про статическую типизацию. Исходно там были всякие Java+GWT и Closure со включённой строгой типизацией при компиляции. Финальный JS, который грузится на клиент — машиногенерённый, как байткод Java, который, к слову, тоже не особо statically typed со своими дженериками через object.

байткод Java, который, к слову, тоже не особо statically typed со своими дженериками через object

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

Вытащить информацию о типе можно и в питоне, например. Питон — строго типизииованный, но не статически. К слову, в стиле duck typing можно херачить на, наверное, любом языке с достаточно мощным reflection'ом, например на Go.

Ничего вы не вытащите.


Причем, все настолько плохо, что по сути вы можете с этим столкнуться и во время компиляции (А не только в рантайме):


private interface IFoo<T>
{

}

private class A {}
private class B {}

private class Foo implements IFoo<A>, IFoo<B>  {
}
Я сейчас беру GMail не в качестве «динамического языка», а в качестве «бурного развития JS». Потому что бурную деятельность я вижу. А вот развития — нет. Есть попытка как-то решить проблему изначально ужасного дизайна наваливаением многих уровней костылей поверх всего того, что уже было порождено за долгие годы. Причём в результате ничего подобному тому, что призошло при переходе, скажем, Turbo Pascal CRT => Turbo Pascal Turbo Vision => Delphi (когда на каждом этапе количество библиотек возрастало, но зато количество кода, который нужно писать руками для получения сравнимого результата падало) я не наблюдаю.

Наоборот — чем дальше в лес, тем более сложными становятся решения для тех же задач.

По мне — так это скорее регресс, а не прогресс…

Значит Gmail — плохой пример в данном конкретном случае. Ибо бурная деятельность тут происходит не на уровне JS, а как раз на тех уровнях, где статическая типизация. И JS мог развиться до любых высот, но есть груз легаси в инструментах компиляции и потребность запускаться на legacy-браузерах.

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

Ну дык кол-во кода для написания одного и того же на портянке jquery+велосипеды или, скажем, backbone+any_template_engine радикально упало. В разы. А сам код стал проще. Местами даже с элементами FP.


Наоборот — чем дальше в лес, тем более сложными становятся решения для тех же задач.

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


Или вы всецело имеете ввиду? Ну я думаю со времён изобретения MVVP и vDom люди только и делают, что экспериментируют с уже известными подходами и технологиями, пытаясь найти что-то оптимальное. Скажем команда React в одном из новых релизов обещает COMMIT (прямая работа с DOM) фазу разбить на подзадачи с использованием iddleCallbacks. Что в этом плохого?


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


Не понимаю вашего пессимизма. Есть кривой и косой gmail. Это следствие менеджмента в Google. Можно его же сделать быстрым как метеор. Благо технологии позволяют. Подходы все известны. Никакой магии нетребуется. Но надо чтобы кто-то взялся за это. Но ведь премию и повышения дают только за редизайны.

Меня например лично печалит, что когда мне поставили задачу "определить, есть ли на страницу google pixel", мне пришлось селениум в проект затаскивать, просто чтобы понять, есть ли некоторая подстрока на странице. А все потому, что я не могу просто сделать GET урла и поискать текст, ведь всё, что я там найду это <html><body><script src='app.js' /></body></html>.

Ну так себе проблема, если честно. Вот поставят вам задачу, определить используется ли библиотека X в бинарном приложении Y, там никакой Selenium вам уже не поможет. Хорошо если исходник найдёте. Ну или есть навыки джедая в дизасемблерах. А если нет? :)

Да ничего страшного, достаточно просто открыть файл на диске и поискать имя библиотеки. Где-то в импортах оно будет светиться.

Ну так себе проблема, если честно

ДА нет, нормальная проблема. Раньше можно было вызвать GET и получить страницу. А теперь ты вызываешь GET и получаешь непойми что. Семантика HTTP как по мне поломана на прочь. Тебе нужен целый рантайм и куча несвязанных вещей чтобы появился контент. Не даром в HTTP урлы называются «ресурсами», на которых хранится контент. А сейчас это тупо заглушки, в которых нифига нет.
Семантика HTTP как по мне поломана на прочь

Да её вообще уже к лешему послали. Как и почти все остальные устои. Тут где-то что-то мелькало, что мол Google от URL хочет отказаться, а вы про гипертекстовый фидоинтернет :)

Веб перестал быть «набором html файлов», и это естественная эволюция. Семантика нифига не поломана, сделайте GET/POST запрос к любому API и получите предсказуемый результат в формате JSON/XML.

Да, но любой запрос (до недавнего времени) содержал полезные данные.


Теперь же запрос страницы отдает заглушку-лоадер, который и будет загружать непосредственно страницу.




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

Это не техническая заглушка типа редиректа, это полноценный html

Да нет, заглушка и есть. Это не мешает ей быть валидным HTML-файлом, но контента в ней нет.

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

Кое-где она поломана, да, но общий подход по GET / получить html-страницу-заглушку не ломает семантику. Это вполне себе ресурс — A network data object or service that can be identified by a URI. Про какой-то определ'нній вид контента речи нет.

НЛО прилетело и опубликовало эту надпись здесь
Как подгрузить библиотеку, не зная её имени? Это было бы странно.
НЛО прилетело и опубликовало эту надпись здесь
Я привык к дотнету, там все зависимости исключительно динамически подгружаются :)
Сравнение графов функций. Часто есть строки. Ну и инлайнится довольно редко. В общем, решаемо, если только использование библиотеки не ограничивается каким-нить leftpad из огромного фреймворка.
Языки с динамической типизацией — суть исключительно скриптовые.

А потом в результате в соседней теме спрашивают почему софт тормозит? habr.com/post/431282

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

.

.

.

.

.

.

.

.

.

.

.

Ну вот, хотя бы, пресловутый новый дизайн GMail: огромное количество кода, «сложнейшие» библиотеки… и что в результате?

Вот чего всё эта бурная деятельность породила?

Тормоза и отжирание памяти конечно породила!
Тормоза и отжирание памяти конечно породила!

Ещё раз. Это не проблема js. Это проблема с одной стороны тупых говнокодеров, не думающих об оптимизации от слова совсем. С другой стороны текучки у фирм-разработчиков, когда кто-то начинает работать, потом увольняется оставив кучу говнокода, в котором толком никто не может разобраться. Затем приходит другой, нарастив сверху один слой точно такого же говнокода. Затем третий и т.д. В итоге несложная веб-страничка превращается в море жидкого дерьма. Которую по-хорошему надо бы переписать с нуля, но во-первых лень, во вторых жаба душит. В третьих это проблема человечества, слишком увлёкшегося IT и тратящего на это кучу усилий, абсолютно не соответствующую полезности этой деятельности. А когда коту нечем заняться, он как известно яйца лижет. Что мы и наблюдаем.
P.S. Минуснул не я. Честное пионерское! :)
С одной стороны имеем язык, спроектированный совсем не для тех задач, для которых его применяют. С другой — недальновидный бизнес, желающий максимально сэкономить на каждом этапе. В итоге действительно получается порочный круг, где всех душит жаба, плохое решение все прочнее вмуровывается в фундамент, а общая ситуация напоминает баян про продажу 20 долларов за 200.

Вспомните, как появились V8, HipHop, KPHP и другие подобные проекты. Когда язык поощряет пресловутую «свободу самовыражения», писать костыльный код на нем становится легко и приятно. Говнокодеры радуются тому, что им теперь удобно работать, и вкладывают миллионы человеко-часов в решение прикладных задач. В итоге у бизнеса есть система, которая худо-бедно выполняет свою функцию, но переписать ее как следует уже экономически невозможно, и приходится изобретать умопомрачительно сложные способы оптимизации.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Ого, вот это требования. «Настоящий» разработчик, видимо, должен держать весь проект в голове и писать сразу идеальный код?

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Держать в голове то, что можно отдать компилятору на автоматическую проверку, вообще не нужно.
«Проблема в JS» ≠ «проблема JS».
HTML тоже называли недостаточно строгим и хотели перейти на строгий XML. Не взлетело. С JS, думаю, если бы его попытались сделать «дисциплинирующим», вышло бы то же самое.

Жалко, что не взлетело.


«Проблема в JS» ≠ «проблема JS».

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

Если у кучи людей регулярно возникает проблема с Х, то проблема не в людях, а в Х.
А если у кучи людей регулярно возникают проблемы с математикой или с грамотностью — то проблема, конечно же, в математике и в грамоте, я правильно понимаю?
Это должет быть не одномерный, а двухмерный: динамическая — статическая, слабая — строгая. И тогда JS будет в самом углу. Да и почему все заканчивается Хаскелем? Есть еще более строгие языки типа Idris, Agda.
Потому что в том докладе были взяты топ 20 языков (учитывались такие рейтинги как TIOBE, PYPL, RedMonk (stackoverflow + gihub)). То есть это языки, которые наиболее часто обсуждают и используют.

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

Я тоже. Вот поэтому-то мне и было бы крайне любопытно услышать серьёзные аргументы сторонников динамической типизации. То что они есть, я не сомневаюсь. Уж больно нетривиальные вещи пишут на том же js. К сожалению то что пока прочел от них в комментариях, увы детский лепет. Правда читал выборочно, сейчас начну сплошняком. Ну и очень надеюсь что подтянутся люди, серьёзно работающие на js.
Уж больно нетривиальные вещи пишут на том же js.
А какие именно? Нет, мне правда интересно.

Я вижу огромное количество бурной деятельности и огромное количество… не хочу ругаться матом… но вот чего-то, где я мог бы понять — почему оно такое большое и сложное… не вижу. Вижу поделки, похожие на то, что в прошлом веке делались с помощью Visual Basic или Delphi — только с примерно в 100 раз большими требованиями к ресурсам и со в 100 раз большим количеством кода.
А какие именно? Нет, мне правда интересно.

Признаюсь абсолютно честно. Я тут особо не специалист. Я таварисчь в основном очень и очень низкоуровневый. FPGA, микроконтроллеры, реальное время и т.п. Сходу так приходит из того чем пользовался и что дико понравилось (а пожалуй даже где-то как-то удивило) это jsplumb (рисование графов) и d3.js (визуализация графиков и диаграмм).

Вижу поделки, похожие на то, что в прошлом веке делались с помощью Visual Basic или Delphi — только с примерно в 100 раз большими требованиями к ресурсам и со в 100 раз большим количеством кода.

Вот с этим остаётся только согласиться. Сейчас ваяю потихоньку некий чисто свой проектец. Пишу на ts. Так из всех библиотек у меня там только require.js и то только потому, что без неё непонятно как грузить то безобразие, которое ts собирает в единый файл. Даже jquery не использую. И rich text editor у меня свой (он довольно специфический). Глянул в своё время на angular, это просто ужос, ужос, ужос. Дерьмовина типа hello world тащит два с половиной мегабайта всякого говна! У меня весь проект (на данный момент) меньше 200 килобайт! Так что да, ощущение того что всё сделано через жопу, самое что ни на есть явственное. Но повторяю ещё раз, я человек из совершенно другой области. За веб-программированием наблюдаю с интересом и даже кое что пишу. Но оценить профессионально не в состоянии, для этого в этом надо жить. Но на мой тупой дилетантский взгляд со стороны, развивается всё очень и очень бурно. Хотя согласен, качество порой сильно оставляет желать…
НЛО прилетело и опубликовало эту надпись здесь
Честно говоря не знаю как это называется, в конфиге задается генерация в виде atm-модулей. Раньше ts вообще всё лил в один файл по умолчанию, а теперь вон чтобы слилось в один и оттуда стартовало, приходится немного постучать в бубен. Давно на ts ничего не писал, с версии 1.8. А тут сразу третья…

А куда слияние в один файл пропало-то? Просто указываете outFile вместо outDir и наслаждаетесь жизнью же...

Он для этого требует тип модуля AMD и в таком виде и льёт. Простым способом стартануть оттуда при загрузке, у меня не получилось. Хотя честно говоря особо и не разбирался. Работает через require.js и ладно. Благо сама require.js всего 17 килобайт.
Хм, и правда. Но формат там довольно простой, можно было и без require.js обойтись
Вам возможно будет полезна MAM архитектура, где не нужно париться по поводу сборки и запуска — просто пишете код в соответствии с простыми и естественными соглашениями.
НЛО прилетело и опубликовало эту надпись здесь
На Go честно говоря не работал. Но подобное бы мне определенно очень сильно не понравилось. Может Вы с оптимизацией что-то напутали?
При чем тут оптимизация? Обычная статическая линковка со стандартной библиотекой, на том же С++ получается то же самое.

Вся разница — в том, что бинарники на go не настолько часто передаются по интернету как javascript-бандлы.
Блин, ну кто же стандартные либы линкует статически! Не, можно конечно, вопрос ЗАЧЕМ?
Потому что философия Go это ноль зависимостей. У Go бинарника под линукс будет пустая секция импорта, все на вызовах ядра. Он не зависит ни от каких стандартных библиотек даже статически, все свое. В этот размер входит весь рантайм со сброщиком мусора и прочим. У каждого языка свои особенности.
Честно говоря никогда об этом не слыхал. Но с Go я никогда и не работал. Кстати у FLTK точно такая же философия. Но он статически добавляет чуть больше 30 килобайт(если мне не изменяет память, давно уже на нем не писал).
НЛО прилетело и опубликовало эту надпись здесь
Она очень удобна для быстро меняющегося фронта. Не для хардкорной бизнес-логики, которая в множестве переехала в браузер, а для UI, маппинга, viewmodel, раутинга, анимаций — все равно это все как правило долго не живет и постоянно переписывается, статическая типизация там только обуза и дополнительный источник мерж-конфликтов меняющегося boilerplate кода.
Я сам .NET дев, но при всей любви к статический сильной типизации на бэке, до появления анонимных типов и dynamic С# временами подбешивал необходимостью в очередной раз менять одноразовую фигню потому что-то переехало в другое место на странице, и количеством DTO.
С одной стороны да, согласен. С другой несколько спорно. У меня пожалуй было два проекта, такого типа, о которых Вы говорите. Первый — некая хитрая плата сбора данных на FPGA. Чтобы не паять туда микроконтроллер, управляющий процессор я реализовал прямо в той же FPGA. Следующим шагом было написать для всего этого безобразия ассемблер. Но поскольку задача постоянно менялась и система команд под неё постоянно оптимизировалась, ассемблер я сделал таблично управляемым. Просто в некий файл в специальном формате забивалась текущая система команд и на основе неё компилился код.
Второй случай — когда я тяжело болел форексом, писал некую софтину для финансовой аналитики. Серверная часть писалась на С++ в виде некоего ядра и набора плагинов. А отображалось всё в броузере. Проблема была точно такая же, как в первом проекте с FPGA. Я не знал заранее, что и как я в следующий момент захочу изобразить. И решена была точно так же. Вместе с данными в броузер отправлялся некий заголовок в текстовом формате, их описывающий. И всё работало хотя и не на пять с плюсом, но на твердую четверку.
Мораль сей басни такова. В любых, самых изменчивых проектах, всегда есть некоторое относительно стабильное ядро. Скорее всего не совсем простое. Управляемое некими командами. Вот это-то ядро и имеет смысл реализовывать тщательно, не скупясь, на чем-то хорошо помогающем отлавливать ошибки (в частности статически типизированном). А все изменения выносить в некий управляющий этим ядром простой язык. Думаю (хотя могу ошибаться, ибо не в точности знаю Ваших задач) на Вашем месте я точно так бы и поступил. Хотя Вам конечно виднее, на истину я никак не претендую.
Анонимные типы — это статические типы. А зачем dynamic в UI?
Но анонимные типы их не нужно описывать.
А зачем dynamic в UI?
Например
    return View(new
    {
        User = user,
        Upjachki = upjachki
    });

...

@model dynamic

@foreach (var upjachka in Model.Upjachki)
{
}

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

Option<Upjachki> чем не подходит?


Зато не будет проблем с Model.Upjacki.


Динамики вообще не нужны. В языки испокон веков был Hashtable (позже Dictionary), который делает все, что нужно. Никаких проблем вместо foo.a.b написать foo["a"]["b"]. Зато исчезает куча проблем как с производительностью, так и с неявностью, что есть у объекта, что нет, где упасть может, а где нет.

Тем, что его нужно описать. Когда у вас сотни страниц и одноразовых DTO, начинает напрягать. Зачем Option, кстати? Явность foo[«a»] ничем не лучше foo.a, так же замечательно падает.
Тем, что его нужно описать.


И хорошо. Описано в одном месте, все инварианты проверены только на границе. Дальше можно безопасно пользоваться. Слышали про защитное программирование? Удобная концепция.

Когда у вас сотни страниц и одноразовых DTO, начинает напрягать. Зачем Option, кстати?

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

Явность foo[«a»] ничем не лучше foo.a, так же замечательно падает.

Ничем не лучше, только вот во всей программе (кроме той, что работает непосредственно со входом) ничего падать не будет. В самом худшем случае упадет при десериализации, это значит, что на вход подсунули чушь, которую программа не ожидает. Она может по счастливой случайности смочь обработать и этот новый тип, но чаще ошибка вылезет где-нибудь в километре от того места, где реально произошла, и ищи потом ветра в поле.
Я в целом с вами согласен, но весь этот boilerplate…
Хорошо бы иметь что-то типа @model returned by Foo.Bar, тип то фактически описан.
Ну вон я на F# пример привел, как раз то, что нужно, по идее.
@model returned by Foo.Bar, тип то фактически описан.

TS так умеет.

Часто достаточно посмотреть на TS описание типов для простой вроде JS функции и преимущества будут видны.
Язык Си создан в 70-е — и в нём нет никакого ООП.
Язык QuickBASIC (компилятор, не путать с интерпретатором QBASIC), созданный в середине 80-х, позволяет одной директивой заставить разработчика декларировать типы всех переменных, и там тоже нет ООП.
При этом современный статически типизированный язык Crystal, например, который по задумке тотально ООП, не только в коде самого компилятора очень часто процедурный, но и позволяет без проблем писать в процедурном стиле.
При этом z = x + y в большинстве статически типизированных языков запросто приводит к неверному результату, и упомянутый тут язык Java долгое время таскал внутри себя баг в алгоритме бинарного поиска, когда результатом m = (l + r)/2 было число намного меньше l. Ибо Int склонен переполняться (впрочем, как и float, там просто разрядности мантиссы становится недостаточно), а проверить это разработчикам компиляторов не досуг.
При этом многие интерпретаторы динамически изменяют разрядность переменной при переполнении.
Я, собственно, к тому, что в статически типизированных языках нет ничего «современного», они появились вообще-то раньше динамических, в статически типизированных языках совершенно не обязательно использовать ООП (и совершенно не факт, что в динамически типизированном языке можно не использовать ООП), ну и да, в конечном итоге мне совершенно непонятно, причём тут вообще проектирование классов и статическая типизация.
P.S. И да, если убрать колоссальный пласт «неправильного» кода без ООП на том же языке Си — боюсь, что автору не пришлось бы написать свой пост, ибо нечем и некуда.
Добавлю, что Smalltalk — отец ООП — имел динамическую типизацию. Статическая типизация ООП а ля С++ — это более поздний костыль, призванный увеличить производительность за счет переноса связывания на время компиляции.

А при чём тут ООП вообще? Типы — это далеко не только классы.

Картинка не полна без разделения ещё и на строгую, и слабую.
Неполна она потому что находится в контексте доклада, а там строгость/слабость не была важна.
К сожалению, данные API в runtime TS мне никак без валидатора не поможет прочекать (в отличие от тех же нормальных статических языков). Консистентность кода в TS? ровно до того момента когда надоест бороться с быстро развивающимися библиотеками (а тайпинги для них отстают), придет манагер и скажет: «что-то долго» и т.п. => вы свалитесь к any.
Так что, давайте оставим статическое статическому, а динамическое динамическому и не будем пытаться сделать из одного другое и наоборот
any, в случае надобности, вы воткнёте для чужих библиотек, а для своих скорее всего обойдётесь без него.
бороться с быстро развивающимися библиотеками (а тайпинги для них отстают)

Сейчас все больше и больше "быстро развивающихся библиотек" пишут сразу на ts.

github.com/gcanti/io-ts добавит вам валидацию данных и статические типы при успешной валидации.
Вместо any нужно использовать unknown и тогда без проверок не получится использовать объект неправильно.
Тот способ мышления, который вы описываете, скорее можно назвать «data driven» и «bottom up», это не связано напрямую с типами.
Вот, например, отличная презентация с демонстрацией такого подхода к дизайну (что забавно — тоже на примере игры), притом человек пишет на Clojure — языке с динамической типизацией (но строгой иммутабельностью).
Это не значит, что динамически типизированные языки не нужны
Единственный объективный плюс динамической типизации — можно очень легко и быстро слепить наивный интерпретатор для данного языка. Для разработчика, использующего такой язык, плюсов по сравнению с достаточно гибкой статической системой типов нет.
Тут недавно на хабре была пара статей, где человек делал стековую машину на Haskell. Отрезвляет своей сложностью.
Реализовать интерпретатор с состоянием на платформе, которая ориентируется на отсутствие мутабельного состояния и побочных эффектов, да еще и эффективно — это сама по себе крайне сложная задача.
Ну как нет? Нет необходимости описывать все типы. Гибкие системы типов обычно позволяют описать что угодно, но вот это описание может быть очень и очень громоздким, по сути write only.
Описание типов — это одновременно каркас и документация системы. Если даже в ней сложно разобраться, то как вы вообще предполагаете работать с такой системой без нее, в состоянии «черного ящика»?

Возможность не описывать некоторые типы в статически типизированных языках тоже бывает: например, в C# есть dynamic, в Typescript — any. Но в хорошем коде это допустимо только в качестве локального исключения из правил, когда нужно вкрутить какой-нибудь незначительный костыль.
в C# есть dynamic, в Typescript — any.

В dynamic/C# не означает потерю информации о типе в рантайме, в отличие от any/TS.

Разве в TS информация о типе в рантайме как-то теряется?

Разумеется) TS со своими чудными типами живет только на этапе компиляции, потом он деградирует до JS.
А C# проверяет типы всю дорогу, dynamic лишь отключает проверку типов на этапе компиляции, а в рантайме они продолжают бросаться экепшнами, если что-то пошло не так.

Иначе не было бы type guard'ов и то как костылей.

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

Как я их понимаю, на этапе проверки типов компиляции, анализатор делает предположение, что он возвращает реальный результат о типе как instanceof.


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

Так ведь наличие методов потому и можно проверить, что информация о типе не потерялась, и ее можно вытащить через Object.keys, Object.getOwnPropertyNames или оператор in...

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

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

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

А где я писал про «средства проверки типа»? Информация о типе — не то же самое, что средства проверки типа.

А информация о структуре типа — не то же самое что информация о типе.

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

Фраза в конце порадовала)).

Про нейронную сеть интересно. А она точно нейронная? На каких данных обучалась?


Я посмотрел на пару твитов, и это больше похоже на подготовленные человеком ответы:


  • Context in #reactjs is a crutch – никто так не пишет, в результатах гугла только этот самый твит. Больше похоже на русского программиста, который буквально перевел слово "костыль" на английский.


  • Есть твиты с картинками. Откуда нейронная сеть их возьмет? Поиск по картинкам в гугле ничего не дал, а в Яндексе находит только этот же самый твит. Получается, что ИИ сочинил текст, сделал его скриншот и вставил в твит? Слишком фантастично.



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

Хабрачани, а почему все забывают, что динамическая типизация, практически, была всегда? В С++ всегда использовалась динамическое выделение памяти и в ней можно было творить что угодно. Главное не выйти за рамки и не забыть закрыть. Но её же не зря придумали? Просто нужно понимать когда притягивание за уши статический типизации переходит все рамки разумности — тогда используй принцип «Сделай сам». Хотя при таком подходе мой приятель обвиняет меня в раздвоении личности. Но это мой путь. Я всегда стараюсь использовать статическую типизация, но когда нужно передать, большой блок данных в другую подсистемы, то я могу и породить трёхэтажную не типизированную структуру. В разработке это, как правило, получается быстрее, да и адаптировать её при дальнейшем развитии проекта в неизвестном направлении — проще. Так что считаю что используя только статическую типизация вы себя искусственно ограничиваете. Хотя начинать свой жизненный путь, конечно, нужно со статический системы. Иначе — каша в голове обеспечена.
Это не динамическая типизация, а низвоуровневый доступ к памяти. Такой доступ нужно делать явно, и применяется он только в очень специальных ситуациях.
В c++ специальные ситуации уже начинались при создании двухмерного массива… И конечно понятно, что это не динамическая типизация. Но подход то у меня в голове остался, я просто пытаюсь сказать, что в полной свободе от типов, в некоторых ситуациях, есть свои прелести.
Ну я тоже не сторонник абсолютно жестких систем типизации — когда между int и bool заставляют явно преобразовывать. Формально они правы конечно… но соглашение ноль==false, не ноль==true кажется вполне однозначным.
Вариант С/С++ (впрочем с некоторыми улучшениями — что-то я бы усилил, что-то ослабил) мне нравится больше всего (хотя наверное здесь есть фактор привычки).
> но соглашение ноль==false, не ноль==true кажется вполне однозначным.

А зачем ноль преобразовывать в false? Откуда взялся этот ноль со значением false?
В доисторические времени типа bool не существовало. Его симулировали на макросах брав за основу тип int. Любое число отличное от нуля было true, а если ноль то false.
Ну да, но я так понял человек хочет сейчас зачем-то иметь такое преобразование на уровне языка.
Удобно, многие проверки записываются короче типа `if (count)`, а не `if (count != 0)`. Ещё удобно преобразование null к false, особенно для nullable объектных типов

Ага, особенно "удобна" вот такая форма условного оператора: if (count=0) :-)

Это не «удобно», это типа сэкономили 3 символа (на самом деле нет).

А «удобно» — это когда при виде `if (count)` не возникает вопросов:
— Это проверка на «проинициализирован ли count»?
— А count вообще проинициализирован?
— А count где-то инкрементируется? Точно один раз?

Вот `if (items.Any())` — удобно. Ни переменной лишней не надо, ни следить за ней.

Это даже не вспоминая про удобство `if (count = 0)`…

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

> Это не просто экономия, это паттерн, распознаваемый с одного взгляда.

Тяжёлое детство, лишенное витаминов, восьмибитные игрушки, паттерн, распознаваемый с одного взгляда…

> паттерн сравнение со значением

Только семантика у него не про сравнение, так ещё и потерю выразительности в удобство запишите.
Вам распознаваемый, кому-то нет. Я оператор "!" снова начал использовать только из-за нытья других разработчиков, но где я могу задавать правила, все по прежнему пишут boolVal == false, потому что "!" маленький и незаметный.

Сравнение в bool как по мне вообще атавизм из си, вызванный как раз этой проблемой, как и йода-стайл if (5 == x)

Удобнее было бы сделать скобки необязательными. В символах получается столько же, а читаемость возрастает на порядок.
Явно преобразовывать между int и bool, говорите?

В мою бытность студентом (шесть лет назад) у нас был компьютерный класс. Ubuntu на терминалах, первый урок — как перейти в консоль и выйти из случайно запущенного vi, код пишется в mcedit… Первые эксперименты (примитивная fork-бомба, завесившая намертво терминал), первые куски кода, про которые никто не понимает, как это работает, и прочие радости юного кодера.
Одной из этих радостей был gcc, настроенный лично завкафедрой. -Werror — наш лучший друг, в этом я убедился ещё тогда; особенно, когда количество активных предупреждений начинает потихоньку переваливать за рамки очевидного.

Писали мы какую-то программу для работы с массивами. Уж не помню, что такое требовалось, но помню, что решил поместить индексы к массиву во второй такой же массив (типа double). Никакой арифметики с ними не производилось — только перестановки. В конце же, соответственно, требовалось взять элемент первого массива, индекс которого лежит в нужном месте второго массива. Казалось бы, дело простое, да?
arr[indexes[i]]
Ta-damm! Ошибка — за давностью лет уже и не вспомню, какая именно, но я, глядя на неё, решил попробовать явное приведение:
arr[(int)indexes[i]]
Фиг вам! Та же самая ошибка.

… В общем, конечная строка кода выглядела так:
arr[(int)(float)indexes[i]]
И удаление любого из двух явных приведений типа приводило к выбросу предупреждения (ну а в силу -Werror — к ошибке компиляции).

Явно преобразовывать между int и bool, говорите?..

P.S. А ещё я про этот случай периодически вспоминаю при работе с Rust. Со всеми этими
let int: i32;
let float: f32;
...
let double: f64 = (float as f64) * (int as f64);
Но здесь хотя бы понятна суть: весь язык устроен так, чтобы не давать выстрелить себе в ногу, обрезав результат под выходной тип.
соглашение ноль==false, не ноль==true кажется вполне однозначным.

Это кажется.

Возьмём логический тип. Пусть false – это 0. Тогда ~0 (не ноль) — это 0xffffffff или -1 в дополнительном коде. Применяя как операции «&», «|», «^»,, так и операции «&&», «||» мы получаем правильные результаты.

Для числового типа это не так. Возьмём значение 1 и значение 2. И то, и другое — это true. Применив к ним операцию «&» (побитовое «и»), получаем 0, т. е. false. Получается, что true & true равно false. Для операции «^» тоже можно подобрать примеры противоречий.

Вывод: между типами int и bool разница есть.
НЛО прилетело и опубликовало эту надпись здесь

По старому соглашению, a && b это сокращенная запись a & b, где b не вычисляется, если a false. Аналогично с |.

В С-подобных языках && и & два совершенно разных оператора.

Для bool-like типов данных они должны давать один и тот же результат.

А если в выражении вызов функции? А если функция с сайд-эффектом?
Какая разница? Вопрос про тип данных.

Насчет функций, то в случае && вторая часть не выполняется, если вернулось false, а в & обе части выражения вычисляются. Но я это уже говорил буквально двумя строчками выше…
Разница в том, что && это не сокращенная запись &, это два совершенно разных оператора. В .NET даже определены для разных типов, можно 3 & 5, но не &&.

Я не зря про bool-like говорил.


Для остальных типов побитовое И может быть любым.


если у вас есть


bool a = ...
bool b = ...
bool c = a && b;
bool d = a & b;

результаты c и d должны совпадать для любых a,b.

В C/C++ int bool-like (хотя что это вообще такое?)

#include <stdio.h>

int main()
{
    int a = 1;
    if (a)
    {
        printf("I'm a and I like true!\n");
    }
    
    int b = 2;
    if (b)
    {
        printf("I'm b and I like true as well!\n");
    }
    
    bool c = a && b;
    int d = a & b;
    printf("bool c = a && b: %04X\n", c);
    printf("int d = a & b: %04X\n", d);
    if (!d)
    {
        printf("I will not argue with the standard");
    }
}

Я надеюсь, вы все так пишите, удачи с отладкой.
Так никто и не спорил, что В С эта часть стандарта откровенно плохая.
Все в порядке с этой частью стандарта, хотите стрелять себе в ноги — стреляйте.
Есть момент, который подрывает веру в статическую типизацию. Допустим, пользователь вводит строковые данные. Эти данные надо обработать функцией «распознать(строка)». Эта функция должна вернуть значение числового типа (если пользователь ввёл число), дату (если введённые данные похожи на дату) или исходную строку (если ничего распознать не удалось). Статически определить такую функцию не получится. Функция должна будет возвратить не только значение, но и типовую переменную, поскольку тип заранее не известен.

Все возможно. Функция без проблем может вернуть number | string (Typescript), Either Int String (Haskell) или std::variant<int, std::string> (C++).

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

У нас получается не статическая типизация, а кустарная (вручную написанная) динамическая типизация.

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

Да написать-то так можно. но получается потом не по фэншую. Дальше придётся писать проверки типов

В динамике вам точно так же придётся писать проверки для обработки собственно значений


У нас получается не статическая типизация, а кустарная (вручную написанная) динамическая типизация.

Типы-суммы это очень даже статическая типизация. Вы статически знаете, что значение внутри или типа А, или Б. Назвать её динамической — примерно как назвать int динамически типизированным, просто потому что там могут лежать разные числа.

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

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

Статическая проверка — это способ установить a single source of truth для области значений и допустимых операций у каждой величины, используемой в программе. Альтернатива — размазывание этого знания по всему коду и проблемы с рефакторингом.
Альтернатива — размазывание этого знания по всему коду и проблемы с рефакторингом.
Какие проблемы ваще? Смотрите: со статической типизацией вы заменяете тип возврата — и потратите три дня на то, чтобы заменить на всех уровнях все функции.

Закроете одну таску, да ещё и нагоняй получите за то, что возились три дня с плёвой задачей.

А с динамикой-то как хорошо: за день замените первые три функции и закроете таску. А потом — спринты, багфиксы, доводка и прочее. И всё такое клёвое — прям можно показывать заказчику и денежку требовать. Ясно даже за что. Вот за то, что вот эта формочка перестала чушь типа $$$$ вместо $100 показывать и за то, что вон там в списке новый тип появился.

Ну и что, что результат втрое дольше порождается? Зато заказчик видит, что работы — вагон! Прогресс — прогрессирует, аджайл — аджалирует, все довольны. Даже заказчик, который деньги платит. Чего ещё, собственно, вам нужно?

Вот я не понял, это сарказм или нет?

Я так подозреваю, что это сарказм для разработчика и абсолютно серьёзная мотивация для его руководителя.
НЛО прилетело и опубликовало эту надпись здесь
Конкретный подход сильно зависит от проекта. Это может быть несколько рефакторингов, кончающихся тем, что в конце меняют один этот метод. Может быть новое API с пометкой старого как deprecated. Может быть даже, в крайних случаях, один commit на 10'000 строк.

Но суть в любом случае та же: три дня идёт работа, которая не порождает видимого выхлопа — а потом, за один день, появляются сразу все запрошенные фичи. Если вы знакомы с эффектом айсберга, то понимаете, что с точки зрения бо́льшей части современного менеджмента — это ужасно: «они что-то там делают, а я не понимаю что… наверное баклуши бьют». Если у вас каждый день происходит «бурление» и что-то в проекте меняется — они это вспринимают гораздо легче.

Даже если всё происходящиее, с точки зрения программиста, напоминает, извините, кипячение дерьма, при котором всплывают и лопаются пузыри — это воспринимается лучше, чем когда просиходит что-то «в глубине» без видимых результатов.

Да собственно это хорошо видно везде. Посмотрите на современное описание Turbo Pascal 2.0: Turbo Pascal 2.0 — почти такой же, как и Turbo Pascal 1.0 со слегка изменённым интерфейсом (палитра серая с белым вместо желтой с серым), несколько незначительных изменений в компиляторе.

Сравните с описанием 1984го года: политра «серая с белым» не упоминается вообще (как слишком минорное изменение), зато есть такие пункты:

  1. Поддержка overlays
  2. Поддержка динамической кучи (да-да, в Turbo Pascal 1.0 её не было… вернее была сильно урезанная версия).
  3. Дополнительные команды редактора
  4. Работа с цветом
  5. Поддержка графики (только CGA в данной версии)
  6. Работа с окнами
  7. Работа со звуком

Ничего так «незначительные изменения», да? Особенно первые два?

Собственно популярность динамические языки обрели, когда изменились критерии. Когда прогрессом стало считаться вот это вот «пузырение», когда можно продемонстрировать «прогресс» заказчику ежедневно. А то, что конечный результат получается хуже и дольше… ну кого это волнует?
НЛО прилетело и опубликовало эту надпись здесь
Только в случае с динамикой раздолбанность будет неявной и чтобы выявить причину поломки понадобятся тесты/дебаггер, а в случае со статикой раздолбанность будет явной, так как ломать и бросать исключения Вы будете своими руками.
Потому в первом случае вы можете с чистой совестью сказать, что «таска закрыта», а во втором — нет.

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

А что потом делать с этим значением? Мне правда интересно, как новичку.
Я просто не совсем себе представляю код в котором мы из метода получаем: или число, или дата, или строка, а после свободно работаем с этим значением без каких либо проверок типа.

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

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

Если же вам все-таки надо вызвать определенную логику обработки в зависимости от типа, то это например в C# это можно быть сделано вот так
public void Recognize(string value)
{
    DateTime date;
    int number;
  
    if (DateTime.TryParse(value, out date))
    {
        HandleDate(date);
    }
    else if (int.TryParse(value, out number))
    {
        HandleInt(number);
    }
    else
    {
        HandleString(value);
    }
}

Для абстрагирования от типов можно также можно использовать ad-hoc полиморфизм (перегрузка операторов или методов), параметрический полиформизм (дженерики) или полиморфизм подтипов (абстрактные классы, интерфейсы). Инструментов куча.
И вообще, что это у вас за поле такое в интерфейсе, которое может быть и строкой, и числом, и датой?
Да ну, чушь. Вот 2018.12 — это число? Вы уверены?
Console.WriteLine(decimal.Parse("2018.12", CultureInfo.InvariantCulture).ToString());
Console.WriteLine(DateTime.Parse("2018.12", CultureInfo.InvariantCulture).ToString());

Не знаю, где это может понадобится, для синтаксического разбора что-ли. Но для этого более вменяемые инструменты существуют.
В C# если совсем прям не париться, то можно вернуть object и в рантайме проверять тип на выходе. Только вот, зачем? Я не помню в практике задач просто распознать. Я всегда знаю заранее, что конкретно я хочу распознать. Если я распознаю то не знаю что, то зачем мне вообще это делать, пусть строкой остается. Когда парсится какой-нить JSON, то я все равно проверяю все типы на выходе, чтобы быть уверенным, что мне пришли правильные данные, а не строка, в которой может быть какой угодно тип.
Прекрасно получится, например в Kotlin. Примерно так
sealed class RecognitionResult 
data class RecognizedNumber(val value: Int): RecognitionResult()
data class RecognizedDate(val date: Date): RecognitionResult()
data class RecognizedString(val str: String):RecognitionResult()

fun recognize(input: String): RecognitionResult = TODO("implement")

fun main(args: Array<String>) {
    when(val result = recognize(args.firstOrNull() ?: return) {
        is RecognizedNumber -> print(result.value)
        is RecognizedDate -> print(result.date)
        is RecognizedString -> print(result.str)
    }
}


Всё статически определено, проверено и обработано. Профит.
Пользуйтесь
abstract class UserInput<T>
{
    public string Input { get; set; }

    public abstract T Value { get; }

    public abstract bool IsValid { get; }
}
И как этим пользоваться? Вызов функции «распознать(строка)» можете показать?
​Если вам нужен синтаксический анализатор — используйте синтаксический анализатор, опишите правила и распознавайте ваши строки. Вашу функцию можно использовать как одноразовое решение, но надо понимать, что это костыль, потому что habr.com/post/431250/#comment_19430178
В остальных 99% случаев вы знаете требуемый тип. И не забываем про
про whitelisting by default.
А предыдущие 2 комментатора смогли осилить ТЗ, а не рассказывать, что вместо пары if надо синтаксический анализатор настраивать.

> Вот 2018.12 — это число? Вы уверены?

Я уверен, что неуверенность в этом только вам по ночам спать мешает.
Да, число. Если бы дату хотели ввести — ввели бы увереннее, например «2009-06-15T13:45:30.0000000-07:00»
А где вам такое понадобилось? Мне за всю практику понадобилось ровно один раз, где пользователи могли писать что-то типа sum(workdays({requests.submited}, 11/03/2015)) * 8. Первым делом мы выкинули багованные велосипеды неосльщиков, взяли PEG и описали нормальную грамматику. А вы для чего тип ввода определяете?
Ну, задание не я придумывал. Но, например, указывать «вчера» как "-1" наравне с «28.11.2018» с подобным парсингом в (datetime|int) где-то под капотом вполне могу представить.
Я бы наверно regex написал в этом случае. Тем более что JS Date 28.11.2018 все равно не понимает, а -0.01e+2 вполне себе валидное число, только оно вам такое надо?
Вы опять какую-то свою проблему решаете.

> -0.01e+2 вполне себе валидное число

Понимаете в чем дело, ввод "-1" делают что бы быстро(!), не включая мозг, ввести дату «вчера». Если вам быстрее ввести -0.01e+2, то и оно корректно обработается — флаг в руки, чем бы дитя не тешилось.

То у вас синтаксический анализатор, что бы можно было ввести как минимум sum(workdays({requests.submited}, 11/03/2015)) * 8, то нельзя -0.01e+2… Не понимаю.
Извините, самое главное забыл, функция в любом случае должна возвращать дату или невалидное значение (e.g. undefined or null), так что ваш пример мимо. Так можно какой нибудь реальный пример, где требуется возвращать разные типы?
> функция в любом случае должна

у вас какая-то принципиальная позиция противостоять тому, что в ТЗ написано?
Не понял, это входное значение или внутренне представление? Если входное значение — результат дата. Если внутреннее представление, то зачем это может понадобится? Что это за вечное «вчера»?
А NaN — это true или false? A -0? A Infinity?

Да просто заучиваете всю табличку и всё. Заодно тренировка памяти!

Динамически типизированному LISP скоро 60 лет.
И сколько крупных приложений на нем написали за все эти годы?
понимая под LISP семейство языков (Common LISP, Scheme, Clojure, ...):

Emacs, Maxima, некоторые части AutoCAD (и там есть встроенный интерпретатор LISP), полётная система Boeing 747, Apache Storm, Datomic (база данных), бэкэнд Walmart, оригинальный Reddit, HackerNews,…

Немного, но достаточно, чтобы доказать, что писать крупные приложения на нём вполне возможно.
Полноты по Тьюрингу и очень упорного программиста достаточно, чтобы написать что угодно на чем угодно. Было бы странно, если бы на LISP не было вообще ни одного серьезного приложения — сама по себе идея очень заманчивая. Однако всего пара десятков проектов за шестьдесят лет — это все-таки показатель.
О честном сравнении не может быть речи в условиях, когда:
  • 99,9% всех образовательных программ в школе и институте уже 40 лет (если не больше) обучают с самого начала только структурному программированию (в последнее время ООП)
  • всякие пиарщики ездят по ушам менеджерам, а преподают люди давно отошедшие от разработки (или никогда ей не занимавшиеся), в итоге образованием и управлением занимаются люди, до сих пор пересказывающие сказки из 90-х про ООП
  • на самых популярных платформах по подготовке школьников по программированию и проведению олимпиад (informatics.msk.ru, Codeforces, contest.yandex.ru) есть даже PHP и OCaml, но нет ни одного диалекта LISP
Осталось только понять, что из этого причина, а что — следствие…
99,9% всех образовательных программ в школе и институте уже 40 лет

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

Как проходят? Как у некоторых: по языку в семестр? Пользы от такого зоопарка никакой, только каша в голове.
Как проходят? Как у некоторых: по языку в семестр? Пользы от такого зоопарка никакой, только каша в голове.

А надо 5 лет лисп учить?

А что вы понимаете под учить? Синтаксис можно объяснить за 5 минут, а вот начать думать по-другому, научиться решать задачи по-другому — это требует время.
Я все языки проходил по одному в семестр, C только два, да и то второй был системное программирование с обязательным условием писать на C. И был Lisp и даже Prolog.
Вести из параллельной вселенной (=

Полистайте https://lisp-lang.org/success/
Проекты на Лиспах писали еще в то время, когда никаких Гитхабов не было.

Начнем с того, что IDE, за использование которых ратуют многие комментаторы, появились именно для Лиспа. Ну и еще куча других концепций, которыми вы сейчас пользуетесь.

Помните, как Маяковский писал о Хлебникове? Есть поэты для читателей, а есть поэты для поэтов.
Не поймите неправильно. Для своего времени LISP содержал в себе множество революционных идей, однако идеальным все же не был. Поэтому с тех пор появились более практичные инструменты, которые взяли хорошее и исправили остальное.
Это не динамическая типизация. Динамическая типизация в вашем примере — если бы у вас все аргументы и возвращаемые значения всех функций были void *.
Не void*, а {type,void*}, как std::variant
НЛО прилетело и опубликовало эту надпись здесь
Вы писали на C++ под nodejs или ещё что? V8::Value похож на std::variant, точно не на any. В any можно положить все, что угодно, а std::variant это предопределяет. v8docs.nodesource.com/node-0.8/dc/d0a/classv8_1_1_value.html
НЛО прилетело и опубликовало эту надпись здесь
Покажите как адекватно можно сложить два std::any? Возможно я не понимаю как это делается. Для std::variant все просто — static_visitor и простым перечеслением, что делать в странных ситуациях, когда нужно int и строку сложить. Я использовал этот подход, когда писал игрушечные языки. Он удобный, простой и даже, неожиданно, быстрый, если не нужно делать тяжелых конвертаций.
НЛО прилетело и опубликовало эту надпись здесь
При всем моем уважении, это всё не про статическая vs динамическая типизация, а про сильная vs слабая.

Все то, что так вас раздражает — это все не от «динамики» типов, а от их «weakly typed» :-) Которая, вполне себе, может быть и статической при этом. Обратное, кстати, тоже верно… в том смысле, что динамическая типизация ортогональна сильной. И языки с сильной динамической типизацией вполне себе есть.

Так что, «динамика» тут вообще не причем… даже в плане «статических проверок». Динамическая типизация, совсем не означает их отсутствия. А вот слабая типизация (хоть даже и статическая) — в лучшем случае — сводит их к, скажем так, слабо полезной формальности.

P.S. Весьма сильно улыбнуло противопоставление «динамики» и ООП. На всякий — вообще ортогональные вещи.
P.P.S. И да… *система типов* — это, судя по всему, не совсем то, что вы думаете.

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

Я вот тоже сторонник статической типизации. И поскольку статью писал человек мыслящий также как я, то его точка зрения вполне понятна. Но крайне интересно было бы прочитать аналогичную статью, написанную разработчиком на JS. Именно с развернутой критикой статической типизации и демонстрацией преимуществ динамической, на реальных примерах. Ну или хотя-бы развернутый комментарий. Мне кажется, такое погружение в противоположную точку зрения здорово расширяет восприятие, ну и вообще интересно.
Не JS, но от автора динамического языка, который был очень хорошо продуман. youtu.be/2V1FtfBDsLU?t=1610 (временная метка на то место, где он говорит о разных ошибках, в т.ч. связанных с типами и почему это не так важно)

А в этом интервью Рич на вопрос о динамической типизации ответил, что «уважает право разработчика делать что угодно»

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


Есть какая-то функция, например создание модального диалога


function createDialog({title, content, footer, onSubmit, onDismiss, ...andMore}) {
  // do something
}

В типизированном подходе вам понадобится описать интерфейс DialogOptions и перечислить эти поля еще раз. А если внутри появится еще функция createDialogBody(), которая будет принимать только часть параметров, то ей понадобится еще один интерфейс. Эта писанина утомляет.


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


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

Согласен на все 100%.
Непонятно что вы имеете в виду с интерфейсами. Я бы просто написал (псеводкод на некотором типизированном языке, «void=>void» — тип для функции, не принимающей никаких параметров и не возвращающей никакого значения)
function createDialog(string title, string content, string footer, void=>void onSubmit, void=>void onDismiss) {
  // do something
}

Меня вот просто выводила из себя (до желания стучаться головой о стенку) ситуация, когда я смотрю код на php или js, там какая-то функция с аргументами, или просто какие-то переменные, и я НЕ ПОНИМАЮ, каких они типов. Чего мне ждать — числа, строки, структуры (если структуры то какой, с какими полями каких типов), функции (опять же с каким списком аргументов)? Что я имею права с ними делать а что нет? Ну хорошо, что в вебе в основном строки. В основном — но не всегда. Вот честно, не представляю как вы с этим живете :)
Но сколько на отписывание структур нужно времени? А если эта функция используется одним разработчиком в тестовом проекте и в одном месте?
Да нисколько. В моем примере нет структур, а просто у каждого аргумента добавлен тип — можно рассматривать это как часть имени. Структуры это уже посерьезнее, когда понятно что код становится достаточно общим и универсальным, «закрепляется» в проекте как часть архитектуры.
Я тоже не люблю лишнюю писанину. Но только ли в этом вопрос?
А если эта функция используется одним разработчиком в тестовом проекте и в одном месте?
То нужно её тело вставить в то место, где она используется и не парить нокому мозг.
Как-то, помню, уже с 8 летним опытом писал на JS на Ember. Но тут по интернет и работа закончилась. Вот так и живём.

У такого размазывания по аргументам есть и обратная сторона. Функции с большим числом аргументом считаются плохим кодом и предлагается избавляться от них созданием DTO. Вот эти DTO как раз утомительно писать, особенно в Java, где на каждое поле еще и геттер/сеттер нужно создать. Да, в редакторах есть макросы, но все равно получается простыня кода просто для того чтобы передать данные из одного модуля в другой.


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

Читаем документацию, вставляем параметры как там описано. Еще современные редакторы (IDEA и VSCode) понимают декларации Typescript и показывают их для JS кода. Таким образом можно облегчить себе работу с библиотеками, не обкладывая при этом типами весь код целиком.


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

> Функции с большим числом аргументом считаются плохим кодом и предлагается избавляться от них созданием DTO.

Плохой код — это не когда много аргументов, а когда много зависимостей. DTO — это так, украшательство. Ну, точно можно отложить до «когда понятно, что проект в целом на верном пути».

> А сколько времени займет построение графика по данным из JSON на том же C#?

Вы, наверное, подразумеваете, что надо типы создавать под JSON, и это долго? В том же C#, внезапно, есть dynamic.

dynamic stuff = JsonConvert.DeserializeObject("{ 'Name': 'Jon Smith', 'Address': { 'City': 'New York', 'State': 'NY' }, 'Age': 42 }");
string name = stuff.Name;
string address = stuff.Address.City;
Это действительно внезапно! Потому что теперь я не понимаю автора статьи еще больше. Если в C# есть dynamic, что чего плохого в том, что в Typescript иногда используется any?
Ещё в C# есть goto, но в реальной жизни не встречается.

dynamic достаточно экзотическая штука, неудобная и опасная. Ну вот для «накидать график» она подходит, а как только возникнет желание, например, входные данные валидировать — то, наверное, уже тип опишите. И в любом случае оно вряд ли покинет границы функции.
Можно ли обобщить до такого?

динамический язык — достаточно экзотическая штука, неудобная и опасная. Ну вот для «накидать график» она подходит, а как только возникнет желание, например, входные данные валидировать — то, наверное, уже тип опишите.
Ну как, динамический язык может и прилетевший {'Age': '42' }, вместо {'Age': 42 } простить, и stuff.Address.City как stuff.?Address.?City обработать.

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

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


Но в 21 веке можно получит и то и то бесплатно. Например


type Simple = JsonProvider<""" { "name":"John", "age":94 } """>
let simple = Simple.Parse(""" { "name":"Tomas", "age":4 } """)
simple.Age
simple.Name2 // compile-time error: no field or property 'Name' found
НЛО прилетело и опубликовало эту надпись здесь
В редкой задаче есть больше двух циклов и нет плохокода одновременно.

Тогда пишется функция, где пишется return. Goto ни в одном проекте, слава богу, не встречал.

Тогда пишется функция, где пишется return. Goto ни в одном проекте, слава богу, не встречал.

ОЧЕНЬ быстрые корутины на дотнете написаны с помощью goto


https://github.com/Hopac/Hopac/blob/6b4fe8e89e95aa9f2119e96f19d37cfe541e7715/Libs/Hopac.Core/Engine/Worker.cs#L129

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

А еще yield return/ async-await разворачиваются в стейт-машины на goto, но это не повод так писать в проде.


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

Я больше одного return пишу с осторожностью. Приятно с таким кодом работать?
Лучше уж два return в функции, чем goto или вспомогательный флаг…
Я же не говорил не использовать, я говорил — с осторожностью. Некоторый примеры я бы отрефакторил, к примеру, updatePostAction можно разбить на две. $failResponse переменная вообще не нужна, в success pass она даже не используется, вынести это в третий метод.
Да, приятно. Особенно с теми языками, где нет switch-case.
Если нужно писать на порезанном ассемблере, то и goto может оказать верхом дизайна. Только ничего приятного я в этом не вижу.
Ранние return в общем случае — это избавление от вложенных блоков. Проверили что-то — если false, то до свидания. Как это может быть неприятно?
return бывает 2 типов:
1) проверка инвариантов — тут да, проверили, false, досвидания
2) из логики функции. И вот тут такое себе. А успели ли мы подготовить данные? А корректно ли всё закрыли? И записали ли про выход в лог? И тут ради раннего возврата начинают какой-нибудь try finally накручивать на ровном месте (к вопросу об избавлении от вложенных блоков)…

Вот нет ощущения, что это всегда прям хорошо.
2) Все эти вопросы отпадают при нормальной декомпозиции функции.
Конечно отпадают. Только она почему-то не всегда нормально декомпозируется.
Ну в шарпе и unsafe есть, и Span со stackalloc, но то не значит что ими надо всю программу обмазывать. Каждому инструменту свое место.

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

Читаем документацию, вставляем параметры как там описано.

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

Сэкономив на написании интерфейсов, вы рискуете потерять гораздо больше времени на всяких дурацких моментах, когда нужно будет подсмотреть имя аргумента где-то в коде, или когда вы запустите приложение и внезапно оно упадет из-за опечатки в имени свойства. По сравнению с тем, когда после написания интерфейса IDE понимает вас с полуслова — это куда более существенный «стопор творческого процесса», но если человек все время пользуется динамически типизированным языком, он может этого просто не осознавать.

Впрочем, по поводу хакатонов соглашусь. Если скорость важнее качества — то ДТ возможно будет лучше.
Типизация не влияет на скорость разработки, даже на хакатонах (мы использовали Шарп). Все дело привычки. И в повседневной разработке тоже. Тем более, что строгая типизация позволяет легко генерировать и рефакторить код. Мой рекорд 1300 строк идиоматического ООП в день.
вы рискуете потерять гораздо больше времени на всяких дурацких моментах

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


А сколько времени займет построение графика по данным из JSON на том же C#? Без сарказма, правда интересно, как бы вы это сделали и какие библиотеки использовали.

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

Касательно инструментов для построения графиков и анализа данных — конечно, у Python инфраструктура получше, однако и дотнет кое-что может. Например, в Azure Notebooks поддерживаются как Python/R, так и статически типизированный F#.

Лично я для исследовательских целей использую LINQPad — это REPL-среда для C#/VB/F#. Там можно использовать все, что есть в самом языке\фреймворке (LINQ, любые Nuget-библиотеки), и плюс встроенные утилиты (отображение таблиц, графиков, подключение к БД). Отрисовка графика по JSON выглядит примерно так:
Картинка


К слову сказать, советую почитать про провайдеры типов. Оно пока есть только в F#, но концепция поразительно мощная. Например, если ваше приложение скачивает JSON по ссылке — IDE подскажет вам те свойства, которые в нем действительно есть (отправив запрос к серверу), если отправляете SQL-запрос — увидите список полей в таблице (или ошибку, если в запросе опечатка). Вот гифки с примерами работы.
Спасибо за объяснения! Может быть, пригодится мне в будущем.

Про использование и не использование типов я уже свою мысль донес. Есть ситуации работы с произвольными данными, и на таких динамическая типизация и оправдывает свое существование.
НЛО прилетело и опубликовало эту надпись здесь
Вы правы. Я имел в виду среди тех языков, которые перечислил выше.
НЛО прилетело и опубликовало эту надпись здесь
Я конечно не зверь, не фашист и не садюга, но когда я слышу подобные рассуждения, я испытываю огромное желание, чтобы рассуждающий забыл о своём коде годика так на три, занявшись другими делами, а потом по серьёзной необходимости к нему вернулся. Думаю подобный квест многих опустит с небес на землю.
Был такой опыт. Большая часть проблем была не с сигнатурами вызовов или структурами объектов, а с банальным пониманием что эта функция делает. Читать код и вникать, что он делает придется в любом случае.

А правила написания читаемого и понятного кода примерно одинаковы для любых языков, статических и динамических. Им лучше, конечно же, следовать.
А еще Find Usages. Вот есть у меня в C# тип MySuperType, и надо мне найти где он юзается: где конструируется — в одном из case, в другом — вообще все использования, объявнения его как параметра в метод и как тип возврата.
Что я буду делать в JS? Т.е всеравно TypeScript вам тут нужен будет если проект не прототип, и даже не факт что все эти IDE современные по TS помогут вам сделать то же самое что с системой типов элеиментарно уже десятки лет IDE делает.

В современном Javascript активно рекомендуется к использованию синтаксис import something from './something'. Он отлично статически анализируется и найти все места использования something не составляет труда. Работает в том числе и автоматическое удаление/добавление импортов.


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


Здесь мем

вам аналогичный ответ — что я ответил ниже — про добавление свойства в объект. И да = объекта у вас не может не быть, он есть. Вы же в своей функции чтото делаете с каким-то переданным JS объектом, пусть он и без класса.
Был X,Y, надо Z добавить. Удачи добавлять. Искать не надо вам. Ну да без поиска попробуйте.
Не очень понял, а в чем проблема добавить еще одно свойство? В источнике данных добавим его создание, в получателе – чтение. С удалением могут возникнуть проблемы, а добавление делается очень легко.
В JS вам это просто не понадобится, как вам уже ответили: нет типов — нет проблем. В JS вы будете искать, где это функция вызывается — обычный тестовый поиск по проекту. Поэтому давайте уникальные имена вашим функциям в JS! Умные разработчики еще псевдо неймспесы используют и ограничивают область видимости по возможности. Или просто используют TypeScript.
давайте уникальные имена вашим функциям в JS!

Пока все прогрессивное человечество стремится к простоте и наглядности наименований…
Текстовый поиск без самантики в 21 веке? Статические типы и семантика имени что это объект или класс или переменная — были придуманы как раз для облегчения работы.
И допустим я не знаю имя функции. Я знаю что у меня есть сущность у которой X,Y координаты, и мне надо Z добавить. Какую функцию мне искать? Все которые работают с объектом у которого есть X,Y, но пока нет Z. Без семантики — кучу головной боли.
И да такие задачи сплошь и рядом в реальной разработке. Пример с X,Y,Z для упрощения. Реально бывает кучу других задач по расширению кол-ва свойств у сущности. А если вы сущности не ввели явно, а держите их в голове — ну удачи пройтись поиском.
Я где-то сказал, что это хорошо и правильно? Вопрос был, как искать — я ответил.
И пример на самом деле не релевантный — вы же добавили Z с какой-то целью, вот там, где вам Z понадобилось — там и меняйте. Сразу видно, что на JS не пишите, новые свойства вообще не проблема (если не использовать ...), намного большая проблема, если вы удалили/изменили существующее свойство Z. Как узнать, что оно нигде не используется? Даже если вы нашли все места, что если в соседнем бранче новую функцию написали, в которой по прежнему Z используется? Это никак не обнаружится при мереже, компиляции нет… ну вы понимаете, только тесты. То, что вам может прийти любой мусор — вот это проблема.
давайте уникальные имена вашим функциям в JS

Ага. Ну, к примеру, pipe, it, connect, put, dispatch и другие очень емкие и уникальные словечки из современных хипстерских библиотек. Сколько у вас будет совпадений со словом «put»? Или «it». А еще у нас есть куча нод-модулей, где все функции повторяются по несколько раз, у этих функций разные версии и ты точно не можешь сказать, какая именно из этих используется. Знаете, иногда типизация TypeScript ломается. И тогда я беру тазик и могу наблевать в него до самых краев пока разберусь во всем при помощи вашего совета.
А еще у меня во всех проектах был jQuery c noConflict, никогда $. А еще можно явно указать какую функцию из какого модуля использовать. Еще можно попробовать все зависимости прописать, но пока не получалось. Но кто сказал, что будет легко? Зато работало, а не полная консоль ошибок.
То есть вы пользуетесь менее удобными инструментами, чтобы было тяжелее? Я правильно вам понял?
Вы можете предложить альтернативу?
Конечно. Статическая типизация и никакого «текстового поиска». Я 7 лет писал на JS, а потом попробовал писать на C#. И осознал, насколько неудобно мне было все семь лет и какие жуткие и неудобные костыли я использовал. Теперь возвращаюсь на JS или с использованием TS (хотя это и не настолько круто как C#) или на скрипты до 50 строк — весь код должен умещаться на экран.
А как вы исполняете C# в браузере? Я пару лет назад смотрел прикольную либу для трансплайна в JS, но это было далеко от production-ready. А в скриптах на 50 строк никаких библиотек никогда не используете? А из TS?

blazor есть, но тоже не production ready

А как вы исполняете C# в браузере?
В браузере, увы, никак. Под браузер я пишу под ТС.

А в скриптах на 50 строк никаких библиотек никогда не используете
Да. Если скрипту нужна библиотека — значит он уже достаточно большой, чтобы использовать TS.
Я в курсе, и я об этом говорил. Только работает(ло) это через пень-колоду, плюс есть фактически стандарт TypeScript.
Хотя бы такой момент. Со статически типизированным кодом помогает разобраться сама IDE (хорошая конечно). С динамическим такой фокус не проходит. Она просто не знает как вывести типы. В итоге времени Вы на такие разбиралки потратите куда больше, чем если бы три года назад продумали и явно прописали типы. Нет, согласен, для скрипта в сотню-другую строк, да который ещё и пишется на один раз, можно особо не заморачиваться. Но в более серьезных случаях…
Присоединяюсь. Именно по этой причине я, поработав с TS, с трудом (и только по особой необходимости) заставляю себя возвращаться на чистый JS.
Нет, согласен, для скрипта в сотню-другую строк, да который ещё и пишется на один раз, можно особо не заморачиваться. Но в более серьезных случаях…

В этом и суть. С типами у вас каждая задача – "серьезный случай". В JS типы можно принести позже, когда совсем прижмет. Или не приносить, если и без них нормально, как в d3 или jsplumb.


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


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

В d3 исходники разбиты на маленькие модули (например). Эта модульность работает почти как типизация

Вот за эту подсказку спасибо. Надо будет поизучать. Тогда может врублюсь, как народ на этом пишет.
Ваши аргументы про «когда приспичит внести типы… да как в d3» относятся к использованию готового API / интерфейса. А попробуйте сам свой API / интерфейс для лругих создать, тогда и скажете что не надо это все.

О да я глянул только что вашу ссылку на arc
github.com/d3/d3-shape/blob/master/src/arc.js
Это жесть, куча копипаста в духе
" return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
И всеравно не понятны типы возврата. Тут будет приведение к int или float будет или строка? И в куче аналогичных случаев тот же вопрос.
А когда возврат структуры — вам еще внутри функции покопаться

И только не говорите что неважно. Вы с таким же успехом тогда аргументируете к вопросу «вот вам массив байт — в нем что — целое число и вещественное и с какой точностью, и какая в нем endianness если он сериализован, и что там за переменные в ней и каков размер массива если это он». А вы еще на функции будете проверять (по духу d3). А в колбеках будете выяснять что у вас в аргументах — строки (которые легко уже превратяться в int или float по вашей надобностью) или JSON сериализованная форма (и какого объекта серилизованного) — что даже в этом случае — строка всеравно, а семантика разная. Ну не дураки же придумали семантику. Ну потратите лишнее время на выяснения.

**

function arc() {
var buffer,
r,
r0
r1

и
export default function() {
var innerRadius = arcInnerRadius,
outerRadius
Это и есть объект. И система типов вот она. Даже автор d3 вводит. Только костылями, без явного объявления class (как в подмножестве 2016 или 2015 года не помню уже)
Вы еще не поняли всю глубину пропасти, в JS нет int.
Класс, единственное с чем вы не согласны это «нет int».
В компьютерной памяти, даже в интерпретаторе, не может «не быть int», можно либо представлять все байтами — что низкий уровень. Но мы же с вами говорим о Самантике. Оно либо целое число, уже приведенное, Из строки. Или это Функция принимающая int. Или любое другое число и не факт что уже округленное. Это семантика.

Но зато копипаст и дичь в духе
" return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
отличная. Тут автор d3 видимо с вами не согласен. Потому что он за каким то фигом проверяет function а в другом месте он может проверять на строку.

Давайте везде понатыкаем проверок на function. На всякий случай округлим числа, ведь у нас математика бывает целочисленная «по задаче надо округлить». А еще из строки будем переводить в int (parseInt и в таком духе) (вы наверное и не знали о ней? нафига вот ее придумали? строки везде? или объекты? да черт ногу сломит, но int у нас нету). И на всякий это в каждой функции сделаем. Лишь бы чтоб все можно было везде передать. Только не говорите что это не нужно. Тогда у вас все сломается, если «у нас нет int». А что у вас есть?
В компьютерной памяти, даже в интерпретаторе, не может «не быть int»
В интерпретаторе — может быть. А в JS — вот так. Там просто нет типа int. Не предусмотрен. И float не предусмотрен. Единственный «числовой», «базовый» тип — double. Который возвращается унарным плюсом! То есть в JS +"123.45" — это 123.45 и даже +null — это нуль!

Там что всё в порядке там с типами — сплошные double везде.
Если бы там были «double везде» то что это за дичь
" return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
т.е у нас появляются внезапно и функции аргументы, а еще это может быть объект с заданными полями или массив.
Так вот попробуйте сначала почитать исходники любого норм написанного проекта, да хоть d3, и увидите что там стремятся как раз явно описать типы. И да меня похоже многие не понимают, я в комментах и о пользовательских типах пишу, и их тоже прописывать лучше явно, что это Arc с такими-то полями, а не что это хз что.
Справедливости ради хорошие IDE нормально работают с (некоторыми) динамическими языками, если не использовать всякий манкипатчинг и смену типа (а частично и их поддерживают). Они точно так же статически выводят типы как и в случае статической типизации. Код на языке с динамической типизацией никто не запрещает анализировать статически, выводить типы, предупреждать об их несовпадении и т. п.
Вот только работает это только тогда, когда код написан более-менее как на языке со статической типизацией… а в этом случае непонятно — почему сразу не взять язык со статической типизацией…

Только не берите C/C++ — неплохие языки, но количество ногострелов, прилагаемых «в комплекте», что называется «зашкаливает»…
Именно. Более-менее так и написан, а все использования «киллер-фич» динамики как таковой строго задокументированы и частично статически и(или) динамически типизированы (например с помощью *doc аннотаций), чтобы помочь и IDE, и разработчику понять, что происходит.

А мне непонятно зачем его брать. Думаю, за месяц-два я легко перейду с PHP на Java/C#, как когда-то перешёл на PHP и JS с C/C++, пускай и не все их возможности знать буду. Но зачем? Что это даст мне лично и что бизнесу, на который я работаю? Ну вот есть у нас сейчас TS — за почти полгода работы ни разу он мне не помог обнаружить какой-то баг.
Ну вот есть у нас сейчас TS — за почти полгода работы ни разу он мне не помог обнаружить какой-то баг.

Ух. Первый негативный отзыв о TS который мне попался на глаза. Реквестирую статью про это :)

О чём об этом?

А вы не думали, что он, вместе с автодополнением в IDE, мог вам помочь этот баг не написать?

Не заметил существенной разницы между JS с ключом only type based competition и TS в одной и той же IDE

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


Мне вот достаточно написать $.extend(Foo.prototype, { ... }) — и IDE уже не может понять что все методы внутри фигурных скобок могут обращаться к свойствам Foo в контексте this.


Или viewModel.BeginPeriod.typed — и IDE перестает понимать что происходит, потому что свойства typed у ko.observable нету, а тайпинг через jsdoc чужому классу не добавить.

Стараюсь не писать сложного кода — KISS и всё такое :) И использовать библиотеки, о которых знает моя IDE (пускай она и использует для своего знания объявления на TS)

Но если ваша IDE использует тайпинги от TS даже для JS — вы уже не можете использован аргумент "Не заметил существенной разницы между JS с ключом only type based competition и TS в одной и той же IDE" в качестве доказательства ненужности TS.

Я не утверждаю, что TS не нужен в принципе никому и никогда. Решили разработчики IDE использовать существующие TS тайпинги для улучшения работы их IDE со стандартными или популярными JS-библиотеками — видимо нашли это полезным, я даже могу предположить почему.


Но вот при написании своего кода я особой разницы не замечаю.

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

Чем качественно хуже статические анализаторы динамических языков, которые точно так же не дадут, например, сделать коммит?

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

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

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


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

НЛО прилетело и опубликовало эту надпись здесь

Вы не путаете статическую типизацию со строгой? Гарантии даёт строгая прежде всего.

НЛО прилетело и опубликовало эту надпись здесь

Лучше поздно чем никогда. А иногда лучше даже поздно чем рано :)

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

Они широко используют автоматический вывод типов, а также информацию о типах, имеющуюся в синтаксисе типа function name(string $param) или в *doc-аннотациях.

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

Какой тип можно выводить из чего, когда типов нет? Как только речь не идет о строчках и числах, то пиши пропало. Повсюду объекты с хрен его знает каким полями и функциями. В любом случае, даже все это это костыли по одной простой причине — динамическая типизация мешает и всяческими способами это пытаются решить. Неудивительно в этой ситуации, что typescript имеет такой взрывной рост в одного из лидеров сразу — он пришел в нужное место в нужное время, когда JS приложения переросли объем, когда с ними можно адекватно работать со всей этой динамикой.

Если придерживаться определенных правил при написании, то анализатору будут известны поля и функции объектов. Грубо, нужно писать на JS в стиле на который не заругается компилятор TS, полагаясь только на автовывод.

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

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


У TS есть плюсы, но есть и минусы.

TS — это же надмножество JS, выучил его — второй уже знаешь.

Что интересно, я не нашёл полноценных учебников TS в своё время, в которых JS вообще не упоминался бы (ну пускай "под капотом компилируется в JS, но вам это не нужно", как может быть в учебнике по C про машинные коды или в учебнике по Java про байт-код). В подавляющем большинстве литературы речь о различиях, и подразумевается что JS уже знаешь. Разве что классы описаны "с нуля", но это как я понимаю, потому что по факту они пришли в JS из TS.

Если честно, я полноценных учебников по TS не встречал в принципе (не умоляет годности официальной документации, но, как вы уже заметили — требует старта со знания JS).
Что, кстати, огорчает, ибо в учебниках по JS я встречал достаточно мало информации насчет грамотного построения архитектуры ПО в контексте фронтенда, в стиле «Think in Java» и других хороших книжек.
Так эта… может он вам помог не допустить баги?

Помог незаметно как-то?

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

Вот что значит "не дали написать"?

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

А компилятор — тот же стат. анализ, только не отключаемый.

Я не пойму про какой сценарий речь идёт. Мой:


  • пишу код на TS
  • запускаю компилятор
  • не вижу ошибок

В какой момент мне не дали создать баг?

Тогда вы не совершаете ошибок типов, что ведет нас к двум возможным вариантам:


  1. Вы просто не пользуетесь типами. В стиле "напихаем везде any и компилятор доволен".
  2. Вы не совершаете ошибок. В таком случае мои поздравления, всегда мечтал познакомиться с таким человеком.
Вы идеальный программист, пишете сразу без ошибок.
Мои ошибки на другом уровне :)
Ну тогда это пункт 1: вы не смогли логику вынести на уровень типов. Вот пример, как это делается.

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


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

Собственно этот код выглядит на первый взгляд как смесь «ФП головного мозга» и «статической типизации головного мозга» — я лучше со своей смесью «ООП головного мозга» и «динамической типизации головного мозга» останусь — она гораздо ближе к натуральному мышлению человека, по крайней мере к моему мышлению.

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

Я уже не раз говорил, что игры с F# перестроили мое мышление, и позволили писать на моем обычном C# намного более структурированный и красивый код.

P.S. к этому
В логике типов ошибиться гораздо проще чем в простом императивном коде,

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

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

Ну вот как какой-то тип с ограничением типа value > 0 "поймёт", что я решил почему-то что слово "неотрицательный" эквивалентно слову "положительный"?

Перефразируйте вопрос.
«неотрицательный» — это value >= 0
«положительный» — это value > 0
От понимания двух этих слов человеком (равно как от отсутствия понимания) в коде программы ничего не зависит.

Вообще говоря, если определить функцию
doTimes(положительный times),
а потом Вася попытается её вызывать как
doTimes(0);
doTimes(-1);

и его код не скомпилируется, то виноват Вася, а не компилятор.

В задаче говорится про неотрицательные, я поставил условие >0. Чем тут поможет типизация?

Тем более типы тут ни при чём, такие вещи должны отлавливаться на приёмочном тестировании. И всего-то достаточно однострочного теста
myFunction(0);
чтобы проект перестал компилироваться, и кто нужно разобрался как следует и наказал кого попало.
Ну так вам говорят, что это полезно, вы говорите «нет смысла». Смысл в том, что можно написать работающую программу с первого раза, вообще ни разу не запуская ни под отладчиком, ни вообще в принципе.

Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции? Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS.


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

Мое мышление сформировано Pascal, С и C++ в то время когда JS-ом или PHP ещё не пахло.


Я уже не раз говорил, что игры с F# перестроили мое мышление, и позволили писать на моем обычном C# намного более структурированный и красивый код.

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


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

Вот в ограничениях ошибиться гораздо проще, а понять где ошибся гораздо сложнее. Опять же я равниваю исключительно JS и TS — не знаю что там с F#, но всегда был уверен, что он про ФП, а не про типизацию, типизация у него такая же как у С# — строгая статическая. И, кажется, мы по разному трактуем слово "ошибка" — мне почти без разницы вылезла ошибка в компайлтайме или в рантайме. Если вылезла — значит допущена 100% и её нужно исправлять.

Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции?

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


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


Рефакторить более-менее большой проект на питоне или на java/scala — тоже огромная разница. В статических языках та же IDEA хорошо справляется и можно совершенно свободно менять что хочется. Если не справляется — я узнаю об этом во время компиляции и тут же поправлю. С питоном среда разработки от тех же разработчиков справляет плохо и банальное переименование переменной/поля оставляет в различных местах "сюрпризы" на будущее.

Если разница огромна, то может быть имеет смысл. Мне такое почти не встречалось, если не считать SQL, но это вообще свой особый мир.

Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS

Какое тестовое покрытие у вашего кода?

Близкое к 100%. Отдельных тестов на типы нет, если что.

А в фоне тесты гоняются (типа https://wallabyjs.com/ )

На сохранение исходников. Автоматическое отключено.

И сколько они проходят? Просто контроль типов обычно делается фоном в процессе набора

Сравнимо со временем проверки TS на одном и том железе на схожих по объёму проектах.

А ведь все тесты можно было бы поменять на типы, и только поднять качество кода… )

Как вы типами проверите реакцию системы на невалидные данные, пришедшие с базы или от пользователя? Или как вы типами проверите сложный численный метод, принимающий число и возвращающий число? Или система типов позволяет что-то вроде type A = tan(sqrt(number)) писать?

Или система типов позволяет что-то вроде type A = tan(sqrt(number)) писать?

Позволяет, почему нет?


Как вы типами проверите реакцию системы на невалидные данные, пришедшие с базы или от пользователя?

Валидируете один раз IO, дальше все работает с точными данными.

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


Валидировать-то я могу и без типов. Как без хаков типа явного приведения или тайпгвардов прийти от почти нетипизированного json к какому-нибудь Dictionary<EntityId,<Model<Entity>>>?

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

При желании можно выразить. Я уже говорил, те же завтипы позволяют компилятору выводить утверждения в стиле "положительное число, делящееся на 3, от 14 до 25".


Валидировать-то я могу и без типов. Как без хаков типа явного приведения или тайпгвардов прийти от почти нетипизированного json к какому-нибудь Dictionary<EntityId,<Model>>?

В том, что в IO (точнее в I, O то нас не интересует, т.к. переход к более слабой форме представления всегда успешен) это 1% программы, а остальной код работает с конкретной структурой, где гарантированно есть поле User, где есть поле first_name, которое является валидной UTF8 строкой длиной до 30. А если это ВДРУГ нарушается, то оно не валится хрен пойми где, а отваливается на самой границе [не наш код / наш код], и сразу понятно, в чем проблема, потом что контекст еще не потерян.

даёт ли гарантии, что для пи в квадрате значение будет 0?

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

Пускай не пи, а 180 градусов (функции соответственно в градусах принимают)

Если градусы у нас — целочисленная переменная, то это в крайнем случае можно и таблицей значений выдать (и, да, если совсем упороться — вписать её в систему типов). Если вещественная — опять упираемся в проблему сравнения на равенство чисел с плавающей точкой.
Ок. Вас понял, статическая типизация гарантирует правильность сложных выражений путём написания типа для каждого значения аргумента, так? Проблемы с равенством в данном случае не вижу.
Я всего лишь пытался сказать, что независимо от типизации гарантировать равенство нулю вещественного числа (а тангенс даже целочисленного аргумента, измеренного в градусах, всё-таки не целочисленный) можно только при условии, что этот случай был закоден явно. В любом другом случае нас ждут ошибки округления и прочие весёлости. А если он закоден явно, то у нас де-факто и так есть система типов, только реализованная через прямые проверки. И возвращаясь к изначальному вопросу — как Вы предлагаете писать тест, который проверит, что tan(sqrt(pi^2)) = 0?
Может ждут, а может и не ждут. А в целом сначала написать строгое сравнение с вещественным нулём по модулю, а дальше если тест не пройдёт из-за ограниченной точности, то погружаться в исходники и тесты базовых функций для выяснения того какую точность они гарантируют.

То есть Вас удовлетворит, к примеру, tan(sqrt(pi^2)) = 1.45291e-16 (число от балды, лишь бы примерно порядок соблюсти)? Тогда что Вы имели в виду под "гарантиями" в самом начале? А то, что тест запросто может не пройти, — факт из практики: в Ruby Math::tan(Math::sqrt(Math::PI * Math::PI)) == -1.2246467991473532e-16 (в версии 1.92, по крайней мере, с которой я работал).

Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции? Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS.

Вы всегда способны продебажить все возможные состояния всей программы? Тогда я даже не знаю, если не совершать ошибки еще можно (я сам пишу на солидити, где нет дебаггера, со средней скоростью 1 небольшая ошибка на неделю работы), то подобном уровню аналогов просто не вижу.

Нет, конечно, как и не могу с помощью типов их все описать.

Ну не знаю, люди как раз с помощью типов и описывают. Напрмер bar<T: Foo>() означает "вызвать bar для любых типов, которые имеют метод foo". И проверит, что во всевозможных вызовах метод действительно есть.

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

Ну например можно эффектами например описать. Тогда проверите, что он в БД лезет (или не лезет, смотря какие требования).

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

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

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

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

Так я о том, что случайные опечатки мне компилятор не ловит.

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

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

Не опечатываюсь настолько, что компилятор это замечает.
Автодополнение
Автодополнение без типов обожает подсказывать любую дич, но не то, что нужно. Потому что ему приходится буквально гадать на кофейной гуще. Особенно когда подключено много библиотек.
А кто сказал, что без типов? IDE нынче хорошо выводят типы даже без явного их указания. А если им немного помочь парой аннотаций типа ` // var BaseClass|string $clasname` то и выражение типа `return new $clasname поймёт`.
Меня смущает такое выборочное цитирование. Cмысл сообщения в том, что IDE и так хорошо справляются (без указания типов), но если им немного помочь (явно указать типы), то будут справляться лучше. У меня ощущение что вы просто не прочитали или не поняли большую часть того, на что отвечаете.

Компилятор тоже хорошо справляется с выводом типов, при этом ещё и услужливо скажет, где конкретно ему нужна помощь.

Нет.

Нет на этапе компиляции у меня таких ошибок. В основном инфраструктурные типа забыл добавить новый файл в tsconfig.json или мерж неудачно прошёл нарушив синтаксис.

Прекращайте уже писать С и С++ через /. Вы современный Си и современный Си++-то видели?

По части ногострелов они близнецы-братья

Понятно. Не видели.
Справедливости ради хорошие IDE нормально работают с (некоторыми) динамическими языками

Какие например? Я использовал поделия от JetBrains для js и python. Говно-говном. Хотя JetBrains реальный монстр, у них пожалуй самые сильные алгоритмы анализа кода. Просто они не волшебники. И если типы не выводятся в принципе, абсолютно логично что они их вывести и не могут. Тут не JetBrains нужен, а Гендальф!
А вообще со своим нынешним хобби-проектиком (надеюсь правда это продавать !) сижу сейчас на vs code и доволен как слон. Там у меня ts + php. Всё взаимно дружит и отлаживается в рамках одного проекта, тока в путь! :)) Хочу сейчас в добавок научиться писать для vs code свои расширения, как я это делаю для эклипса.

JetBrains прежде всего, да. Особенно если им всякие *doc аннотации о типах скармливать там где они вывести не могут.


А для PHP тоже говно-говном? Концептуально он мало чем от JS отличается же с точки зрения статанализа: интерпретируемый язык со слабой динамической типизацией. И даже typehints по сути сахар для if (is_string($param)) throw new TypeError();

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

interface IData {
  title?: string,
  content?: string, 
  footer?: string, 
  onSubmit?: Function, 
  onDismiss?: Function
}

function createDialog(data: IData) {
  console.log(`data: ${data.title} ,  ${data.content}, ${data.footer}`)
}

createDialog({title:"Hello"});
createDialog({title:"Hello Again", content:" Content Text"});

/*
  в консоли будет
  data: Hello ,  undefined, undefined
  data: Hello Again ,   Content Text, undefined
 */
Кроме того, можно сократить писанину, используя интерфейсы, расширяющие интерфейсы

interface IArticleData {
  title?: string,
  content?: string,
}

interface IArticleFullData extends IArticleData{
  footer?: string,
  onSubmit?: Function,
  onDismiss?: Function
}

function createDialog(data: IArticleFullData) {
  console.log(`data: ${data.title} ,  ${data.content}, ${data.footer}`)
}


IArticleFullData — это полный аналог IData из предыдущего примера
НЛО прилетело и опубликовало эту надпись здесь
Отвечу сразу и вам, и Zoolander

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

Поэтому лучше держать эти типы отдельно и мириться с многословностью. Особенно, если учесть, что синтаксис в С# и Typescript более компактный, чем в Java. Может быть и там когда-нибудь сахар из Lombok включат в основу языка.
НЛО прилетело и опубликовало эту надпись здесь

Утверждение "как только тайпчекер доволен, всё сразу работает" сильно переоценено.


Допустим, у вас есть некоторый счетчик в коде


var numberOfProcessedItems = 0;
for(const item of items) {
  if(matchesCondition(item)) {
     // тут много кода
     numberOfProcessedItems++;
  }
}

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


var numberOfProcessedItems = 0;
for(const item of items) {
  if(matchesCondition(item)) {
     numberOfProcessedItems++; // раз
     // тут много кода
     numberOfProcessedItems++; // два!
  }
}

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

НЛО прилетело и опубликовало эту надпись здесь
В принципе, согласен с предыдущим комментарием, но…

// тут много кода < — проблема тут

Когда в IDE код легко поправить в
var numberOfProcessedItems = 0;
for(const item of items) {
  if(matchesCondition(item)) {
     ProcessItem(item);
     numberOfProcessedItems++;
  }
}

то и проблем, внезапно, не проявляется. У вас просто не будет правок кода тут — ни рефакторинга, ни проблемного мерджа.

И проблема именно в IDE: когда рефакторинг делается в один клик и без ошибок — руки сами тянутся мусор спрятать. А когда мне приходится работать в блокноте — у меня тоже «много кода» появляется.

> А с поиском использования типов все еще лучше: вам не придется искать, где именно используется этот тип, если у вас не используются типы вообще.

А когда IDE не помогает — приходится себя убеждать даже от типов отказываться )
Это был только один пример. Есть и другие возможности наделать багов, от которых типизация не спасет: написать неправильное условие в if-блоке, забыть позвать метод или позвать но неправильный.

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

Не будет. Типы декларируются, а не исполняются, то есть не имеют временнОго параметра. Выставив время за скобки, вы убираете целое измерение из проблемы. То есть вы создаете правила, которые работают всегда, а не только в конкретный момент исполнения.


Вот наприме метод:


ApiResult<PostCommentResult> PostComment(PostCommentRequest request);

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

Это часто проявляется в работе с новыми библиотеками или незнакомыми исходниками. Не нужно лезть в доки для каждой новой вещи, нужно только найти тип который вам нужен и найти как его получить. Правильно сконструированный АПИ позволит это сделать только правильным образом.


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

Зато этот метод может запросто вернуть константу, которая никак не зависит от входного аргумента — и ваша система типов никак это не отследит.

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

Но вот это ваше утверждение остается неверным:


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

В идеале. Я не просто так это слово использовал. На практике конечно нужно найти золотую середину, как и во всем.

Середину-то найти нетрудно. Но надо понимать, что эта самая «идеальная» ситуация возможна только в том случае, когда система типов программы по сложности превосходит саму программу.

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


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

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

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


Так можно дойти до вопроса, почему типы не проверяют код на соответсвие с ТЗ.

Ваша… Никакая система типов не отследит константу возвращает метод или нет. Это уже не зависит от системы типов в принципе.


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

Константы отслеживать очень просто, для этого например в C++ есть модификатор constexpr.
НЛО прилетело и опубликовало эту надпись здесь
> На определенном количестве и сложности кода эти затраты окупаются, а иногда нет.

А я хотел донести мысль, что сложность и бажность зависит не только от типов, а от инструментов и практик. Если инструменты под типизированный язык есть, а под нетипизированный нету, то баланс сильно смещается в сторону «я лучше var name = (string)json[»name"] напишу, чем с динамикой свяжусь".
А корреляция между типизацией языка и доступностью инструментов есть.
В типизированном подходе вам понадобится описать интерфейс DialogOptions и перечислить эти поля еще раз.

Нет, можно написать вот прямо так как вы написали:


function createDialog({title, content, footer, onSubmit, onDismiss, ...andMore}) {
}

и выведет автоматом тип: createDialog: { [x: string]: any, title: any, content: any, footer: any, onSubmit: any, onDismiss: any } -> void
и потом можно написать createDialog({title: 1, content: 2, footer: 3, onSibmit: 4, foo: 6, bar: 7}) — ошибка, отсутствует onDismiss, createDialog({title: 1, content: 2, footer: 3, onSibmit: 4, onDismiss: 5, foo: 6, bar: 7}) — все хорошо.

Это будет не выведение типов, а implicit any, который отключается в strict-режиме Typescript. Именно возможность такого any где попало автор статьи и большинство комментаторов и называют происками Javascript в системе типов.


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

Это будет не выведение типов, а implicit any

implicit any будет для конкретных полей. Но у аргумента ф-и тип — не any, его тип — объект с наличием конкретных полей

habr.com/post/431250/#comment_19422410 вот камент самый правильный, там же и другая сторона, можете ознакомиться, дядька умный рассказывает с многолетним опытом в разработке
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.

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

А можно ссылочку там, или выходные данные на эти «научные исследования»? А тож оно сильно «в зависимости от» должно быть. В смысле, это же столько от типизации должно зависить, сколько от системы типов.
Не увидел там сравнения статических и динамических языков от слова «совсем».

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

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

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

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

зачем это здесь сказано? ситуацию отсутствия тестов здесь никто не упоминал до сих пор

ситуацию отсутствия тестов здесь никто не упоминал до сих пор
То есть основную, ключевую и самую важную часть мы вынесем за скобки? Чего тогда обсуждать?
Снова уход от темы. Посмотри, пожалуйста, слова автора, которые я процитировал в самом начале этой ветки, и которые я опровергаю. Про остальные темы я не подписывался спорить.
Смотрю:
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.
Перевожу с русского на русский: «защищенность через покрытие — иллюзия» == «никто не гарантирует вам, что вы покроете все случаи тестами». И ешё: «тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов» — если таки прочитать всю статью целиком, а не видирать из неё пару слов, то можно будет заметить там такой пассаж: «Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.»

Откуда видно, что автор категорически против приёмов, которые позволяют «обмануть» тайпчекер — что указывает на то, что он видит преимущество TypeScript именно в том, что проверки типов покрывают весь код, в то время как тесты могут этого не делать. А не будут они этого делать только в одном случае — когда они не написаны.

Про остальные темы я не подписывался спорить.
Рекомендую всё-таки спорить со статьёй, а не воевать с ветряными мельницами…
Про остальные темы я не подписывался спорить.

Рекомендую всё-таки спорить со статьёй, а не воевать с ветряными мельницами…

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


Переводить с русского на русский не нужно, каждый может перевести по-своему. Что написано, то и обсуждаем.

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

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

Для разнообразия сторонники сильной типизации могут поделиться научными исследованиями вместо возмущённых постов.
Вот, пожалуйста. Да, строгая типизация, сама по себе, панацеей не является (С/C++ несмотря на типизацию остаются самыми «опасными» языками… откуда и все попытки найти им замену), но если сравнить JavaScript и TypeScript — то разница очевидна.
Вот, совсем другое дело!

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

Я же сослался на исследование, которое один и тот же проект без тестов и с тестами делал, и это гораздо более честное сравнение, на мой взгляд.
Я же сослался на исследование, которое один и тот же проект без тестов и с тестами делал, и это гораздо более честное сравнение, на мой взгляд.
Это довольно-таки бессмысленное сравнение: я не видел ни одного человека, который бы утверждал, что тесты не нужны и их можно заменить на статическую типизацию. Собственно если вы бы вы не выдрали из контеста два предложения, а захватили ещё одно — прямо перед ними, то вопросов вообще бы не возникло:
Нельзя строить свою бизнес логику, основываясь на типах, но при этом ничего о них не знать в процессе разработки. Именно поэтому есть фронтендеры, которые покрывают свой код невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors.
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.
Как видите в том абзаце, который вызвал у вас отторжение речь идёт не о «тестах вообще», а о «невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors».

И да — мне, как и автору статьи, кажется это бредом. Type errors должен проверять компилятор! Именно чтобы освободить время и силы для проверок ошибок другого рода.

Ага, здесь как раз тот случай, когда два человека по-разному поняли написанное. Моё понимание отличается, потому что я ни разу не видел юнит тестов, которые только type errors и проверяют. Я видел во множестве тесты, которые проверяют логику, ну и заодно типы тоже. По-моему, так и нужно делать. И в такой ситуации проверки компилятором добавляют мало пользы.


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

Моё понимание отличается, потому что я ни разу не видел юнит тестов, которые только type errors и проверяют.
Ну дык это ещё хуже! Если вы проверили, что у вас всё нормально работает при задании какой-нибудь ставки в 3.0, 10.0 и 150.0 (проценты), но не проверили, что будет, если передать туда null или undefined (которые запросто могут вылезти из-за того, что свойство в базе или формочке пропало из-за рефакторинга), то вы оставляете в программе потенциальную уязвимость.

А назвать проверку на null «проверкой на логику»… мне кажется, что это перебор… это в чистом виде проверка на тип…
Си — язык со слабой типизацией, некоторые его даже считают языком с практически отсутствующей типизацией.
НЛО прилетело и опубликовало эту надпись здесь
Вы оспариваете утверждение, что тесты могут проверить больше, чем система типов?
НЛО прилетело и опубликовало эту надпись здесь
Как вы проверите, что < вообще порядок?
Ни в JavaScript, ни в PHP оператор сравнения ни разу не порядок, и оператор равенства — нифига не эквивалентность.
Вы же понимаете что чтобы написать эти тесты в JS (например) нужно приложить больше усилий и времени, на величину «проверки типов», по сравнению с вариантом «написать только тесты бизнес-логики» в варианте статической проверки типов.

Во 2х, я бы сказал что защищают они даже не в момент рантайма программы, а в момент думанья человеком. Вот я допустим читаю вашу JS прогграмму, я должен перечитать тысячи (потенциально) тестовых вариантов чтобы понять какой тип аргумента у вас в функцию передается и может ли там быть null и должен ли там быть int от 1 до 10 вместо enum общепринятого. Верно?
А еще мне документацию надо где-то отдельно от основного кода искать? Т.е не в коде я буду видеть
Move[] GetMoves (TypeOfShape) — для нормального ООП
а вот такое >>
function GetMoves (TypeOfShape)
причем тип возврата вообще не виден, и даже если задокументировать что вертает Move — вы уверены что это быстрей чем написать тип возврата? И тип аргумента?

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


Вы упускаете суть строгой статической типизации: она защищает от ошибок проектирования. Если система типов имеет еще и алгебраические типы данных, то при правильном проектировании она защищает от ошибок бизнес-логики. Это очень хорошо описано в книге Скотта Влашина "Domain Modeling Made Functional", настоятельно рекомендую.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Ого. Дико интересно было бы взглянуть на реализацию.
НЛО прилетело и опубликовало эту надпись здесь
Я боюсь, что это эквивалентно например проблеме останова.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Если это некая библиотека всех стандартных алгоритмов вроде поиска или сортировки, то в принципе ничто не мешает выделить разные их виды в некие типы вроде binary_searcher<T> или bubble_sorter<T> и определить для них статические type traits вроде amortized_time_complexity<AlgoT>, amortized_space_complexity<AlgoT>, worst_case_time_complextiy<AlgoT> и т.п., а в месте использования статически проверять, что переданный объект-алгоритм удовлетворяет требованиям. Просто никто не заморачивался пока.
И где в это работе нашли, что
защищённость через покрытие выше, чем ...
?!

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

Кстати, замечу, что в отсутствие строгой типизации упомянутых в статье 2930 юнит-тестов, 100 тестов производительности и >400 интеграционных тестов было бы явно недостаточно, так как пришлось бы писать ещё пару тысяч тестов на то, что DeviceFactory::createLightBulbDevice действительно возвращает нечто, что ведёт себя в соответствии контракту LightBulbDevice, а DeviceEvent::getType никогда не возвращает строку.
Можно ссылочку на исследование?
Сильная типизация и покрытие тестами не исключают друг друга, а наоборот — вместе они дают лучший результат.
я отношусь к тем людям, которые считают, что порой потраченные на удовлетворение компилятора ресурсы мозга можно было бы применить лучшим образом, особенно если отсутствие ошибок уже проверено тестами, но вот это своё мнение я доказывать не буду
А ведь ровно ссылочку на оправдание именно такого мнения от вас и просили…
ссылочку просили на оправдание не этого мнения, а слов «защищённость через покрытие выше, чем через сильную типизацию», не нужно смешивать сущности
Это вы, кажется, их смешиваете. Потому что если «защищённость через покрытие выше, чем через сильную типизацию», то это значит, что мы можем взять любой язык без сильной типизации (JavaScript или там PHP), написать на нём тестов — и получить меньше ошибок, чем на C++, Java или, скажем, Rust'е.

Тот же пример, что вы привели — относится скорее к доказательству того, что «если ходить в куртке и в шапке, то вероятность заболеть ниже, чем если ходить в одной куртке». Что, в общем, достаточно очевидно.
а своё мнение про продолжение этой ветки я уже высказал
я отношусь к тем людям, которые считают, что порой потраченные на удовлетворение компилятора ресурсы мозга

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

А Вам не кажется что не стоит тратить ресурсы мозга на написание тех тестов, которые заменяются проверками компилятора?
А не стоит эти ресурсы рассматривать как «потраченные на удовлетворение компилятора», и тогда всё сойдётся. Описание типа, например, некой, уже будет включать в себя как минимум два теста:
  1. тест на то, что при каждом вызове функции в неё будут переданы параметры именно того типа, который она ожидает.
  2. тест на то, что независимо от реализации функции, результатом её вызова будет нечто определённое.

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

Приведу пример (я буду предполагать что система типов моего языка достаточно развита, чтобы всё это поддерживать):
  1. fun getLength — всё, что мы можем сказать о этой функции — это её имя. Исходя из нашего опыта, мы могли бы предположить, что она возвращает(get) некую длину(Length), но чего — мы не знаем. Также мы не знаем, что такое этот Length на самом деле. Нам нужны несколько групп тестов для каждого из типов, которые мы потенциально собираемся передать в эту функцию в качестве аргумента. Исходя, опять же, из опыта, мы предполагаем, что длина — это неотрицательное целое число. Нам нужны тесты, проверяющие, что функция действительно возвращает число, что оно действительно неотрицательное, и что оно действительно целое (минимум три типа тестов, для которых мы не гарантируем полного покрытия, ведь всегда останется шанс, что getLength("aaa") === 3, но getLength("™") === 3.14). Кроме того, нам нужны отдельные тесты на то, что функция не изменяет состояния своих параметров.
  2. fun getLength: List<Any> => Number — теперь нам уже не нужны вручную написанные проверки на принимаемый тип, ведь мы точно знаем, что если переданный параметр не является списком, то вызывающий код не пройдёт этап компиляции. Если также мы знаем, что базовый тип List не поддерживает мутации, то мы знаем, что вызов этой функции не меняет сам список. Нам всё ещё нужны проверки на то, что результат функции возвращает неотрицательное, и целое число.
  3. Пусть целое число у нас описывается типом Int. Тогда, если мы имеем
    fun getLength: List<Any> => Int — мы точно знаем, что длина всегда является целым числом, и каким бы ни было тело функции, она не сможет вернуть нецелый результат, или результат больше максимального или меньше минимального значения для Int. Но стоп, мы не знаем, что будет если передать в функцию Null или Undefined. Какова их длина? Нужен тест!
  4. Не совсем. Пусть в нашей системе типов, и Null, и Undefined являются отдельными типами. Так как наша функция определена как
    fun getLength: List<Any> => Int, она не содержит ни того, ни другого типа (Что выражалось бы, к примеру, как List<Any>|Null). Следовательно, никакой код не сможет передать внутрь Null, и никакая реализация не сможет вернуть Undefined. Потому этот класс тестов нам тоже не нужен. Нам по-прежнему нужны тесты на то, что результат неотрицательный.
  5. Если мы определили fun getLength: List<Any> => Length, а тип Length у нас:
    typedef Length as Int where $value >= 0 — то мы перекладываем ещё один из наших вручную написанных тестов на плечи компилятора, ведь теперь это его работа — проверять что функция не возвращает значений меньше нуля.

Итого, из первоначальных классов тестов нам останется только проверить, что для списков предопределённой длины вызов getLength(list) вернёт строго ожидаемое значение. Так система типизации позволила нам писать тесты только одного типа вместо шести или семи, которые я обрисовал вначале, все остальные группы тестов «написаны» и выполнены компилятором.

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

Я не думаю, что вам удастся найти живого JS\PHP\Ruby\Python программиста, кто будет писать все эти ваши тесты. Будет 1 тест вида:


it('length', () =>
{
  expect(getLength([1,2,3])).eq(3);
  expect(getLength([])).eq(0);
});

И всё.

В целом, да. По крайней мере если нет явного требования типа "при передаче не массива выбрасывать TypeError" — тогда добавится ещё один.

ведь теперь это его работа — проверять что функция не возвращает значений меньше нуля.

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

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

Вот как компилятор проверит, что программист проверил? Я навскидку могу с десяток способ проверки сделать. Ну, например, проверить старший бит хоть c XOR, хоть с AND маской. Или сравнить n === abs(n). Компилятор всё это учтёт?

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

Ну делаете какой-то тип вида Either<Nat, BelowZero>, теперь чтобы со значением работать вам надо при помощи проверки выделить Nat.


Ну, например, проверить старший бит хоть c XOR, хоть с AND маской. Или сравнить n === abs(n). Компилятор всё это учтёт?

Компилятор освободит ваших коллег от необходимости писать вам замечание на ревью :)

Может моё мнение непопулярное, но нечего делать операциям XOR и AND на типе, представляющем число, потому что это будет ограничивать возможную реализацию самого числа и требовать, чтобы число было расширением типа BitSet.
А если интерфейс BitSet из интерфейса числа исключить, то остаётся так уж много способов проверить на неотрицательность, и уж с ними компилятор как-нибудь справится.

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

А я вот исхожу. Потому что в логичной и развитой системе типов не должно быть произвольных исключений, вроде «это вот имеет интерфейс, а вот это не имеет». Число — это тип, не хуже любого другого типа. И у него тоже есть интерфейсы, обычно даже больше одного, в зависимости от самого числа.
Я руководствуюсь более прагматичным подходом, принятым во многих языках, в том числе с сильной статической типизацией. Иногда с хаками типа неявного преобразования к объектному типу при вызове метода на скаляре.

Вообще, сложилось впечатление, что логичная и развитая система это не про мэйнстрим большей частью императивные ООП языки. То есть логичная и развитая система типов это больше про академические задачи, а не про практические.
То есть логичная и развитая система типов это больше про академические задачи, а не про практическиe.

"Это не про практические задачи" — это такой тонкий способ сказать что конкретно? Вот язык Kotlin — по вашей логике, он решает больше академические задачи? Ведь там нет исключений, и число является объектным типом первого класса. Только заточен ли Kotlin под академические задачи, или всё же он ближе "к станку", раз уж его в Гугле полюбили настолько, что пытаются заменить им Java, в котором исключения из системы типов присутствуют? Достаточно ли Kotlin императивен и мейнстримен?

Недостаточно мейнстримен, как минимум — уровня Хасель и F# если верить www.tiobe.com/tiobe-index. Про императивность не скажу — не уверен, что вообще хоть раз код на Kotlin видел.
НЛО прилетело и опубликовало эту надпись здесь
К сожалению, самостоятельно исторические данные найти не смог, но согласно вот этой статье, в том же индексе Kotlin за один год вырос с ~65 места до 36-го, а это более чем двукратный рост. А учитывая что пока что Гугл старается как можно больше разработчиком переместить с Java на Kotlin, укрепление позиций среди задаваемых вопросов на StackOverflow может будет продолжаться как минимум до поры когда в Гугле найдут новую игрушку.
Тут многое зависит, имхо, будет ли Гугл активно продвигать Котлин как язык общего назначения, а не как нишевый, для разработки под Андроид. Вот Swift вроде такой нишевый язык, поскольку Эппл его не продвигает вроде как как язык общего назначения, прежде всего как серверный и CLI на всех популярных платформах.
А где его Гугл продвигает? Мне казалось это JetBrains. А Google просто не пытается им помешать…
Это просто моё видение происходящего:
Вследствие того, что в официальной документации теперь куски кода не только на Java, но и на Kotlin, рука Гугла тут всё же в наличии. Никаких официальных заявлений я не видел, разумеется (может просто не там смотрел), но вывод о продвижении выглядит уместным.
Гугл «продвигает» Kotlin только и иcключительно как язык разработки под Android. И внутри Гугла он только в таком качестве используется.
Андроид — одна из крупнейших платформ для программирования на планете. Я думаю, даже статус «язык для Андроид» — очень и очень немало.
На всё это, разумеется, нужно смотреть через призму любви Гугла к созданию и забрасыванию языков и технологий.

С теми же const generics мы можете Описать сигнатуру


fn add_item<const N: usize>(a: [i32; N], b: i32) -> [i32; N + 1];

И например такой код во время компиляции скажет unreachable code:


let array = [1,2];
let array2 = add_item(array, 3);
if array2.len() == 2 {
   ...
}

Очень полезно во многих сценариях. Например, когда мы хотим работать только с квадратной матрицей. С этой штукой мы можем выразить это в типах и получить ошибку компиляции, при попытке посчитать например определитель для матрицы 2х3. А так остается только эксепшн в рантайме кинуть.


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

бтв не очень понял вопрос. Ну берется число из базы, что с ним не так? Делаете:


let connection = establish_connection();
let results = arrayLengthSettings
    .limit(1)
    .load::<ArrayLengthSetting>(&connection)
    .expect("Error loading settings")?;
let index: i32 = results[0].index;
let index: usize = index.try_into()?;

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

Стоп, стоп. С каких это пор тесты гарантируют отсутствие ошибок?
До тех пор, пока программисты не научатся в обязательном порядке писать документацию, я буду убеждённым противником слабой динамической типизации.
На самом деле это не так уж сложно. В конце концов, пользоваться репами вместо папочек привыкли, и тесты писать тоже как-то научились. Но вот мысль о том, что недокументированный код == несуществующий код для большинства программистов всё ещё является глубоко авангардистской.
В итоге подавляющее большинство проектов не содержит вообще никакой документации кроме традиционной «шапки» с лицензией. Зачастую даже о зависимостях для успешной сборки приходится догадываться.
И вот разбираться в таком коде, если он написан на чём-то динамически типизируемом — это просто адский ад. Вот например, прямо сейчас я пытаюсь разобраться в коде PDF.js, для которой разработчики конечно же не завезли вменяемых доков, а примеры покрывают от силы 2% возможностей библиотеки.
И вот мне блин приходится гонять её под отладчиком, чтобы иметь возможность в рантайме проверить: что же, чёрт возьми, попадает в коллбэк при разрешении очередного промиса. Потому что из кода это как-то неочевидно.
Как то я пытался разобраться с модулем lirc для Linux. Он был написан со статический типизацией и комментариями. Но мне это не помогло! В итоге всё всегда упирается в программиста. Мир не стоит на месте и разработка по это всегда компромисс между скоростью и качеством. И динамика, по моему опыту, даёт результаты быстрее. А если проект полетел то можно его потом причёсовать хоть до посинения. Один из первых применил это правило некто по фамилии Гейтс, и причёсывает своё По до сих пор.
Один из первых применил это правило некто по фамилии Гейтс, и причёсывает своё По до сих пор.
А в каком месте он это применил, извините? Basic писался на ассемблере, более поздние вещи на Pascal, C и тому подобных языках, ни о какой динамике ни в какой момент там речь не шла, насколько мне известно.
Гейтс пожертвовал качеством кода в угоду скорости. Я об этом. И динамика в моем понимании — это жертва ради скорости разработки.

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

Как то я пытался разобраться с модулем lirc для Linux. Он был написан со статический типизацией и комментариями. Но мне это не помогло!
А будь этот модуль написан с динамической типизацией всё сразу стало бы лучше?

Вы всё же о другом говорите. Если вы с нуля пишете свою программу, то динамика может помочь, но если вдруг начнёте использовать стороннюю недокументированную библиотеку, то тут и появляются проблемы, которые описывает Alexey2005.
Я о том, что если моя подготовка позволяет разобраться с задачей, то мне без разницы какая там типизация. А если нет — то ничего не поможет. Хотя, конечно, я немного лукавлю красивый код всегда читать легче, но к системе типизации это мало имеет отношения.

Я вас сразу так и понял.


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

Тут дело даже не в документации. А в том что приходится держать в голове массу деталей, которые при статике берёт на себя компилятор, а хорошая IDE с автокомплитом ещё и подсказывает. Я пытался писать на js. В итоге запутался на третьей тысяче строк кода. Хорошо добрые люди подсказали, что существует такая штука как ts (тогда он был совсем-совсем новым).
Спасибо за статью. Что очень бы хотелось, это почитать мнение Вашего друга. Сам начинал с ассемблера. Казалось бы штука с типами и ООП вообще перпендикулярная. Однако всегда думал отталкиваясь от данных. Есть задача. Она работает с такими-то структурами данных. Их обрабатывают такие-то процедуры. И т.п. Фактически то же ООП, к которому перешел вполне безболезненно и естественно. Динамическую типизацию считаю Абсолютным Злом. Тем не менее вижу, какие сложные и нетривиальные библиотеки пишутся на том же js или на питоне. Из чего делаю вывод, что чего-то сильно не понимаю. Поэтому очень хотелось бы почитать статью подобную Вашей, но от сторонника динамической типизации. Пользоваться ей скорее всего никогда не буду (в конце концов мне уже под 60). Но просто интересно чисто с научной точки зрения. Может уговорите своего друга тут высказаться?
Интересно что вы хотите услышать в ответ? Что способ типизации сильно волнует разработчика при выборе технологии, или что динамическая типизация это хорошо, а статическая плохо?

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

> Динамическую типизацию считаю Абсолютным Злом

И раз уж Вы допустили такое высказывание, то я допущу немного софистики. TypeScript стал популярен, во многом исходя из того, что был запрос на статическую типизацию для JS, и те кто пишет/писал на JS, в этой популяризации сыграли основную роль. Дальше совсем прикольно получается: выходит, что в целом, разработчики, которые принимают такие гибкие технологии как JavaScript, со всеми плюсами и минусами, готовы принять и нечто более строгое, такое как TypeScript, да впрочем и любые другие технологии и принципы: например функциональщина также очень распространена. А в обратную сторону, в случае разработчиков, вот таких как автор, сталкиваешься с какой-то древневековой косностью мышления и иррациональной верой в то, что всякому необходим код, который будет работать веками, а данные должны быть пропитаны десятками абстракций, якобы для какой-то еще более абстрактной «правильности» кода.
Интересно что вы хотите услышать в ответ? Что способ типизации сильно волнует разработчика при выборе технологии, или что динамическая типизация это хорошо, а статическая плохо?

Второе.


Потому что все примеры, что я видел, легко разбиваются современными системами типов. "А вот надо много писать" — не надо, всё автоматически выводится. "А вот надо 2 разных типа вернуть" — не вопрос, вот вам ADT… Ну и так далее.


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


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

Прямо совсем все типы автоматически выводятся? Скорее всего нет, какие-то все равно придется описать. Вот так мы снова и приходим к аргументу "не хочу, не буду писать никаких типов", за который топят некоторые сторонники JS (не я).


С другой стороны, если можно вывести типы без явного их указания, то может быть, можно то же самое сделать с Javascript, анализируя функции и как их вызывают? Бинго! Есть flow – запускаем этот инструмент поверх ванильного Javascript и получаем предупреждения о несовместимости типов. При желании можно и аннотации типов добавить, но это уже отступление от канона "не хочу писать никаких типов".

НЛО прилетело и опубликовало эту надпись здесь
Прямо совсем все типы автоматически выводятся? Скорее всего нет, какие-то все равно придется описать. Вот так мы снова и приходим к аргументу «не хочу, не буду писать никаких типов», за который топят некоторые сторонники JS (не я).

По месту использования — не нужно вообще. Ну вот пример на расте (Который типизирован выше крыши):


fn send_internal(&self, method: Method, url: &str, body: Body) -> ResponseFuture {
    let uri = format!("https://api.telegram.org/{}", url);
    let request = Request::builder()
        .method(method)
        .uri(uri)
        .header("Content-Type", "application/json")
        .body(body)
        .unwrap();
    self.client.request(request)
}

который на JS будет выглядеть как-то


function send_internal(method, url, body)  {
    let uri = format("https://api.telegram.org/{}", url);
    let request = Request.builder()
        .method(method)
        .uri(uri)
        .header("Content-Type", "application/json")
        .body(body)
    return this.client.request(request);
}

Разницы почти нет.


В местах объявления типов и методов — придется писать, но jsDoc и прочее люди и так пишут. Типы это и есть такая документация, только всегда актуальная.


С другой стороны, если можно вывести типы без явного их указания, то может быть, можно то же самое сделать с Javascript, анализируя функции и как их вызывают? Бинго! Есть flow – запускаем этот инструмент поверх ванильного Javascript и получаем предупреждения о несовместимости типов. При желании можно и аннотации типов добавить, но это уже отступление от канона "не хочу писать никаких типов".

Типы, они как прививки — если 100% кода ими покрыто, они очень действенны, если 95 — то уже поменьше, а если 90 и меньше — то уже ничего не спасет этот проект.


image

Ваш «очень узкий кейс» с «минорными плюсами», это и есть ключевая причина успешности интерпретируемых языков с динамической типизацией. Вспомните изначально для чего создавались такие языки как PHP, JavaScript, Bash и другие. То что они получили развитие, это мне кажется логичное явление после успеха. То что способ их применения мог значительно усложниться, это всего лишь некоторый этап развития, в следствие успеха. И например TypeScript, я считаю, достаточно естественным развитием некоторой технологии, которая внешне сильно ограничена (теми же браузерами), и в которой не хватает средств для построения больших абстрактных систем. Причем, я считаю, что TS появился слишком поздно, но если бы не он, то многие писали бы на каком-нибудь Haxe или Flow. У меня в голове и мысли нет противопоставлять различные способы типизации в категории: хорошо или плохо — этим я пытался показать абсурдность подобных обсуждений.

Давайте так подытожим:

Для того, чтобы загрузить с сервера некоторый текст и посчитать сколько раз в этом тексте встречается выражение «Hello, world!»: строгая статическое типизация — плохо, слабая динамическая — хорошо.

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

А вот если задача находится между этими двумя случаями, то ответ не может быть таким однозначным. И например если бы передо мной стояла задача реализовать нечто не очень объемное и не очень маленькое, а самое главное быстро, то для меня та технология, которой я пользовался в последнее время, имела бы значимый вес при выборе, независимо от способа типизации в ней.
Ваш «очень узкий кейс» с «минорными плюсами», это и есть ключевая причина успешности интерпретируемых языков с динамической типизацией. Вспомните изначально для чего создавались такие языки как PHP, JavaScript, Bash и другие. То что они получили развитие, это мне кажется логичное явление после успеха. То что способ их применения мог значительно усложниться, это всего лишь некоторый этап развития, в следствие успеха. И например TypeScript, я считаю, достаточно естественным развитием некоторой технологии, которая внешне сильно ограничена (теми же браузерами), и в которой не хватает средств для построения больших абстрактных систем. Причем, я считаю, что TS появился слишком поздно, но если бы не он, то многие писали бы на каком-нибудь Haxe или Flow. У меня в голове и мысли нет противопоставлять различные способы типизации в категории: хорошо или плохо — этим я пытался показать абсурдность подобных обсуждений.

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


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

Я бы согласился, но буквально полгода назад был эксперимент, который мне было интересно провести: мы взяли небольшое JS-файл на 150 строк, который рисовал какую-то фигню, снежинки там и все такое. И переписали на TS. Переписывание заняло минут 30 (у человека, который TS в глаза не видел, я ему его рекламировал), и мы нашли 3 бага, в основном связанных с тем, что где-то записывались свойства, которых у объекта нет, а где-то они, наоборот, читались. На 50 строк кода была одна ошибка. Который в проде использовался больше года, и постоянно просматривался.




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


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


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

А если уже оба инструмента знаешь (скажем, на момент изучения инструмента 2 инструмента 1 не было в принципе), то почему не выбирать инструмент по сценарию?

Потому что я не вижу особого применения динамическим языкам. Как сказал товарищ в этом топике или соседнем, его рекорд: 1300 строк идеоматичного ООП кода в день. А больше 1000 строк это уже объем, где статика предпочтительнее.


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

Ну-ну, на этот «идиоматический ООП» ещё посмотреть надо. Там, ЕМНИП, речь была в контексте генерации кода. Может он там 1000 строк дата-классов нагенерил и радуется.
По-моему, довольно наивно выбирать инструмент по количеству строк кода.
Никто не выбирает. Но мое персональное мнение, что писать больше тыщи строк кода без типов — себя не любить.
Динамические слабые языки хороши обычно во всяких сценариях, когда ошибочный результат не так уж сильно заметен. Типа нарисовали врага на полпикселя левее — ну и хрен бы с ним. Поэтому они почти всегда используются как скриптовые языки в игровых движках и редакторах карт.

Да, а потом происходит вот так: https://habr.com/post/417435/

Вы немного не поняли. Я признаю что разработчики на чистом js вполне продуктивны и пишут очень неплохие вещи. И мне дико любопытно понять их стиль мышления, который явно отличается от моего. Вот и всё что я хотел сказать.
НЛО прилетело и опубликовало эту надпись здесь
Прихожу на проект. Там 10 индусов писали полгода. Оно разваливается. Прод через неделю. КАКИЕ ТУТ НАФИГ ТИПЫ?!?!))))
Прихожу на стройку. Там 10 индусов строили полгода. Оно разваливается. Сдавать через неделю. КАКОЙ ТУТ НАФИГ ФУНДАМЕНТ?
в динамике тоже есть преимущество для некоторых оптимизаций
А можно пример? Я предполагаю, что в языках с гибкой типизацией и макросами можно сделать то же самое, только как следует, а не monkey patching-ом.

js js'у рознь. Я когда пишу на чистом js взял в привычку натравливать flow или ts на него, проверяя достаточно ли информации в коде для автовывода.

Я когда пишу на чистом js взял в привычку натравливать flow или ts на него, проверяя достаточно ли информации в коде для автовывода.

А в чем проблема просто писать на flow или ts? Какие-то, честное слово, извращения у вас

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

Проблема в том что у JS слабая динамическая типизация, а Python сильная (строгая) динамическая типизация. Это убирает в питоне много проблем, так как он не дает совершать действия которые не свойственные определенному типу.
Приведу пример: JS — «Hello» + 123 = «Hello123»; Python — «Hello» + 123 = TypeError: must be str, not int.
Мне кажется что(как многие писали выше) большинство вопросов у сторонников того же C# к JS связаны с слабой типизацией, а не динамической.
P.S.: C++ имеет статичную но слабую типизацию.
А вообще-то угораю конечно. Очень умные люди, разработчики движков, сушат мозги над тем, как из кода на js вытащить типы и использовать их для всяких хитровывернутых оптимизаций в виртуальной машине. Потом другие, тоже очень неглупые люди, создают такие штуки как typescript, scala.js и т.п, чтобы как-то подружить ту же скажем браузерную разработку с типами. Очень напоминает тот самый анекдот, где два ковбоя задаром говна поели :)))
Я начинал изучать программирование с StarCraft1 GUI, потом Warcraft3 (тоже с GUI) — там даже не код, а парадигма натаскивания / выбора из dropdown подходящего (ограниченного множества) — события и условия и действия.

В техникуме на программера изучал JS — это был 1й язык именно написания кода (а не GUI).
Потом был assembler :) круто показало внутрянку.
И всегда на протяжении кодинга хоть на JS хоть на asm, я думал как с точки зрения сущностей / объектов… у нас был преподаватель на объектах чуть помешанный, ну там «очередь», «слот очереди», «животное», хотя и делали то на массивах параллельных, ну типа x[], y[] вместо объекта Obj {x, y}
Т.е тут дело как это в голове уместить. И абстракции в духе «очередь» и «слот» и т.п. ложатся на мозг человека очень хорошо… уж не помню по каким результатам исследований, но чел не может в голове держать несвязанные разрозненные концепции — далеко не уедешь, надо пытаться свести к формальной модели с определенными входами и выходами, и разными уровнями абстракции… в том числе несколько абстракций нельзя держать, вроде как 7 шт и дале уже забываешь о других объектах…
Если же исходить не из ООП объектов (которые по опыту можно и в JS и а ASM даже, главное концепция в голове), а строить какие-то сотни функций (function DoX(a, b, c) в JS) то в них очень просто запутаться… помню писал и шашки, и крестики-нолики, шахматы начинал — все на JS, по опыту знаю что путался… в итоге всеравно к объектам приходил, и к разным уровням абстракции, вроде это «поле» а это «фигура» а функция построения возможных ходов принимает «тип фигуры» в шахматах например… на Си без плюсов а потом и на C++ переписывал шахматы и явно было видно что статические типы облегчают концепции, в плане возврата в этому. Да это не Write-only, а когда недели/месяцы пилишь прогу (пусть и учебную в начале) то облегчают.
А потом пришел к нормальному ООП в С++ и C# — это было круто и никогда не уйду от них в пользу динамических (уж не силен в терминологии, строгая там или нет и т.п свойства) как в JS.
Да даже на статических и ООП заточенных типа C# бывает код абы-как написан, не используя нормальные общепринятые концепции… это как описывать кучу функций, кучу get-set или public свойства что по сути одно и тоже, но без адекватного поведения внутри объекта, и инкапсуляции… т.е добавить в объект свойство — это прошлый век (начало программирования), надо в него еще поведение заложить и инкапсулировать (чтоб все кому не лень не пытались извне копипастить, а если этот функционал в подчинении класса он и должен быть в нем описан — так понятней где искать — потом, при чтении кода и доработках)… имхо от недостатка опыта, они не допоняли концепции объектов со свойствами и поведением.

Что до паттернов, которые типа унивесральные (даже к JS применимо есть паттерны), суть их в том чтобы другой чел (или ты через годы) вспомнил/понял как оно тут работает в соответствии с общепринятыми джентельменскими нормами… и не 100% программеров придерживаются, но они формально есть, все эти «State в динамических языках не нужны» от непонимания что есть самый State, т.е может у вас вся программа из иммутабельных объектов, а тут у вас есть класс который имеет внутренный вложенный private (или нет — в JS) класс и вот он мутабельный State т.е вы его там гоняете и собираете по колбекам (парсер какойто иерархии, да хоть XML/JSON/SVG для наглядности) и в итоге вот у вас StateOfParsingProcess — вот он формально есть. Даже если вы его не напишите в виде статически class StateOfParsingProcess и не обозначите private — он в вашей голове есть. Но вот напишете — потом вспомнить и вам же будет проще.

Какие-то люди могут не понять как запрограммить процесс парсинга какогото формата файла на JS (не говорю про какие-то спец инструменты вроде разбора грамматик и построяния деревьев и т.п), вот они придумаю свою реализацию на любом языке, и она будет не понятна другому кто будет читать, потому что не то что не очевидна (это тоже), в ней может и логики не быть, потому что ее и не рефакторили, и сделали «чтоб работало» / write-only. А комуто это читать и допиливать вероятно.

У вас в голове дикая путаница.
а) «Мой друг хотел делать наоборот — сразу писать код и не описывать никакие типы. Он не был готов определять проблему в виде семейства типов».
«Это не значит, что динамически типизированные языки не нужны (на самом деле я и правда думаю, что не нужны). Это значит, что при их использовании, тебе нужно отказаться от ООП, как главенствующей парадигмы».
«Канонические реализации большинства ООП паттернов пестрят кейвордом Interface, который совершенно бессмысленнен в динамически типизированном языке.»
Вы очень узко смотрите на ООП. ООП — это не какая-то одна цельная парадигма, а очень широкий спектр различных вариантов ввести в язык объекты.
Ну вот, например, вы сетуете на то, что в динамически типизированных языках интерфейсы не декларируются явным образом (хотя используются постоянно). А вы в курсе, что статически типизированный ООП-язык C++ не имеет интерфейсов вообще, и предлагает совершенно другие способы решения проблем?
Динамические языки вполне себе описывают типы — возьмите Smalltalk и всех его последователей. Мне как человеку из ruby-мира с его тщательно продуманной системой типов просто смешно читать про то, что динамическая типизация — это отказ от продумывания пользовательских типов.
JS — не лучший и далеко не самый типичный пример ООП-языка с динамической типизацией.
б) «Паттерны — штука кросс-языковая». Эээ? Нет, паттерн — это всего-лишь типичное решение типичной проблемы. И в разных языках наборы задач и решений разные. Множество паттернов упростилось (вплоть до полного вырождения) с появлением в языках лямбда-функций, это же не повод поливать лямбды грязью и требовать возвращения всех тех сложностей, которые приходилось без них решать заведением специальных классов.
в) «Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.»
Часть модулей вашего кода получила гарантии согласованности. Если код настолько немодульный, что вы не получили никаких гарантий, то это просто плохой код. Статическая типизация его не исправит (скорее уж наоборот, законсервирует лишние взаимосвязи).
в') Ага, а потом ваши гарантии разбиваются о какую-нибудь ковариантность коллекций, хе-хе.
>Множество паттернов упростилось (вплоть до полного вырождения) с появлением в языках лямбда-функций

Взамен появилось множество новых.

Это ли не аргумент против того, что "паттерны — штука кросс-языковая"?

Ну… не уверен.

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

С другой стороны, map/reduce/filter/etc — это вполне себе паттерн для многих языков, претендующих на функциональность, и выглядит практически одинаково в Scala и в JS, с точностью до типизации и ленивости (т.е. внешне одинаково, а работать конечно будет по-разному).
Не могли бы вы раскрыть тему фабрики? Паттерн состоит в том, чтобы порождать различные (по типу) объекты одного семейства, основываясь на некоторых на некоторых параметрах. Как это теряет актуальность, если появляются функции как объекты первого класса?

Потому что функия с паттерн матчингом — совершенно типичная ситуация в ФП, и не требует выделения целого паттерна.

Условие было " функция не является примитивом 1 класса". если функции есть, а паттерн-матчинга нет?

ФП без паттерн матчинга это что-то из разряда фантастики.


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

Напомню, что мы не про ФП, а про языки где функция — примитив 1 класса.

По большому счету любая фабрика — это вообще функция. С параметрами. Разве нет?

>теряет актуальность
Я немного не так сказал. Речь о том, что с появлением скажем лямбд, большинство паттернов, описанных в той самой книге GoF, становятся настолько тривиальными и очевидными, что по большей части уже не нужны. В частности, в виде отдельных классов. На примере Java 8 это очень ярко проявилось.
Паттерн называется не «фабрика», а «абстрактная фабрика», и состоит из как минимум двух классов: абстрактного класса фабрики и конкретной реализации.

Он теряет актуальность как только появляется возможность передавать вместо абстрактного класса обычную функцию.
Вообще-то у GoF в книге две фабрики. И одна из них совсем не абстрактная.
Да, есть еще и Factory method. Но у него сильно сократилась область применимости, когда стали заменять наследование на композицию.

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

Типы всегда находятся во главе. Динамическая типизация — это не отсутствие типизации, иначе бы вместо "динамическая" мы бы говорили "никакая".
Просто в статическом случае работать с типами проще и удобнее. В динамике — с-но, менее удобно и сложнее, требуется большая квалификация, большая дисциплина. Человек, который умеет писать на динамике — прекрасно понимает, как использовать статику. Если не понимает — ему в динамике не место.
Вы вот пишите, что у вас проблемы с js-подходом — они не от того, что вы начали со статики, а от того, что вы просто недостаточно квалифицированы, чтобы писать качественный динамический код. При этом достаточно квалифицированы, чтобы осознавать этот факт (что, кстати, для огромного числа людей, пишущий на js, не выполняется).

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

А вы точно на мой комментарий отвечали? Я не говорил, что динамика дает преимущества, я говорил, что на ней писать труднее, едва ли это можно назвать преимуществом :)


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

А вы точно на мой комментарий отвечали? Я не говорил, что динамика дает преимущества, я говорил, что на ней писать труднее, едва ли это можно назвать преимуществом :)

Прошу прощения, наверно не так выразился. Хотелось бы немного проникнуться методами, которые используются при таком подходе. Понять как это делается. Советовать мне что-либо написать самому, дело достаточно бесполезное. Ибо пишу уже очень давно и только на статически типизированных языках. Сами понимаете, что я напишу. С тем что Вы сказали, согласен лишь отчасти. Макросы например это те же шаблоны С++. Достаточно уродские. Но скажем в D эта штука уже вполне удобоваримая. Нормальный репл прекрасно живет в Scala или Haskell. И даже дружит с jupyter. Рефлексия хоть и не без бубнов, неплохо работает в java. Впрочем возможно опять Вас не понял. Честно говоря просто не могу себе представить концепций, невозможных в статике, но при этом практически полезных. Напоминаю однако, что это лишь моё мнение. Не претендующее на истину, а судя по популярности того же js от истины весьма далёкое. Вот и пытаюсь к истине как-то подобраться.
НЛО прилетело и опубликовало эту надпись здесь
Макросы например это те же шаблоны С++.

Просто нет.


Нормальный репл прекрасно живет в Scala или Haskell.

Что происходит в этом прекрасно живущем репле, когда вы сперва написали ф-ю f с типом Х, где-то ее поюзали в другой ф-и, а потом переопределили f так, что у нее тип стал Y?


Рефлексия хоть и не без бубнов, неплохо работает в java.

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


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

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

Что происходит в этом прекрасно живущем репле, когда вы сперва написали ф-ю f с типом Х, где-то ее поюзали в другой ф-и, а потом переопределили f так, что у нее тип стал Y?

Ошибка происходит: функция уже определена.

Ошибка происходит: функция уже определена.

То есть, ваш репл невозможно использовать для image-based разработки. О чем и шла речь.

НЛО прилетело и опубликовало эту надпись здесь

Надо иметь возможность переопределения старых ф-й (то есть в вашем примере в g должна быть вызвана вторая f). С-но это (переопределение ф-и в репле) основная операция при image-based разработке.

НЛО прилетело и опубликовало эту надпись здесь
макросы с типами плохо совместимы

Скажите это разработчикам на Rust — авторам Helix, к примеру :)

И макросы в расте чекаются после раскрытия. Вопрос — что происходит, когда потом в этом раскрытии ошибка типов?

Рефлексия с типами плохо совместима? Это вообще как?
Полагаю, имелась в виду не рефлексия, а более широко — метапрограммирование.
Рефлексия с типами плохо совместима? Это вообще как?

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


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


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

Почему-то я всегда считал, что рефлексия — это анализ структуры программы в рантайме, а не конструирование. Но даже если принять ваше определение…

Вот вы пишите: «добавить в рантайме метод классу — что значение к списку». Класс — уже не тип? Тип. Так почему это рефлексия с типами плохо совместима-то?
Почему-то я всегда считал, что рефлексия — это анализ структуры программы в рантайме, а не конструирование.

Здесь есть некая доля путаницы, ага. Я подразумеваю под метапрограммированием работу с АСТ программы (макросы и другие варианты кодогенерации), а, с-но, рефлексия — это работа с программными объектами, которые этот АСТ может представлять. Если вы генерируете АСТ, который описывает класс — это метапрограммирование, если вы создаете программно некий класс как сущность, добавляете ему методы (как сущности) — то это рефлексия. Метапрограммирование может происходить при компиляции (к слову, тайпчек — формально одна из разновидностей метапрограммирования), рефлексия — в рантайме.


Класс — уже не тип? Тип. Так почему это рефлексия с типами плохо совместима-то?

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

Попробуйте описать результат парсинга произвольного json в статически типизированном языке. Работая с такими объектами вы теряете все плюсы статически типизированного языка, ибо только в рантайме станет ясно, что вы ошиблись с типами входящих данных. В compile time вы не знаете, какого типа json[«name»], и даже не знаете, корректное ли это выражение.
Чем динамический язык тут лучше? Вам не придется заводить обертки над значениями примитивных типов и руками распаковать их значения (а вам придется, если только у вас в языке нет union-type). Библиотека для работы с json в статически типизированном языке по сути будет реализовывать свою «динамическую типизацию».
Кроме того, в динамическом языке вы спокойно можете писать литералы, соответствующие нужному вам объекту. Я не знаю, есть ли хоть один статически типизированный язык, который вам позволит такое.
А задача работы с пришедшим извне json — типичнейшая.

С действительно произвольным json можно сделать не очень много полезных вещей, обычно все-таки программист знает схему пришедших данных. Эту самую схему можно или представить в виде типа данных (статического!), или держать в голове, но приводить типы по месту:


var name = (string)json["name"];

В любом случае система типов сработает как дополнительный слой защиты от некорректных данных и снизит вероятность появления ошибок подобных вот этой: https://habr.com/company/poiskvps/blog/422077/

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

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

Не очень понимаю, как проверка типов помогла бы в том примере (не работал с монгой и не знаю, что за тип у этих операторов и чем такой словарик будет отличаться от обычного {string=>string})?
Вы предлагаете (небезопасно) приводить типы по месту. Но где же тут хоть одно преимущество статической типизации реализуется?

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


Не очень понимаю, как проверка типов помогла бы в том примере

Очень просто: если привести userInput.paramA к строке, то для {$empty: false} будет выброшено исключение, и никакой запрос к СУБД не уйдет.

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

Лень нажать кнопку и запустить автоматическую генерацию типов? Ну знаете ли...


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

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

Вы предлагаете кодогенерацию в качестве решения проблемы? Спасибо, но нет.

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


image


Для примера на F#


let [<Literal>] questionsUrl = """https://api.stackexchange.com/2.2/questions?site=stackoverflow"""

type Question = JsonProvider<questionsUrl>
let questions = Question.Load questionsUrl
questions.Items |> Array.iter (fun x -> printfn "%s" x.Title) 

Просто, понятно, читаемо, валидируется во время компиляции. Если вместо RetweetCount случайно написать RetwetCount, компилятор подчеркнет ошибку и ругнется. Удобно? Вроде, весьма. И не нужна никакая документация, потому что код + IDE сами по себе лучшая документация


image

А то получается, что сначала «типы обязательно нужно писать руками, а это долго»

Та даже если долго — лучше раз написать типы под API. Значительно дольше потом каждый раз стараться понять, что же в этой непонятной переменной `json`
Вы предлагаете кодогенерацию в качестве решения проблемы? Спасибо, но нет.

А в чем проблема? Легко, надежно.

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

Вы какие-то ужасы рассказываете. О каком-то отвратительном апи с непонятной схемой. Как с такой вообще работать то? Что в динамическом, что в статическом? Все хорошие апи, которые я знаю очень легко описываются в статике. Да API, когда подключаешься к серверу — описывается в первую очередь, даже на чистом JS через JSDOC по той простой причине, что значительно легче посмотреть, что приходит в ответ на какую-то функцию в коде, чем запускать, ставить дебагер и пытаться поймать, что же там пришло.
С пришедшим извне json неизвестного формата? Сказал бы, что, напротив, совершенно нетипичная и нежелательная, если бы сам с ней сейчас не возился (в попытках единообразно обрабатывать данные из нескольких таблиц БД). Но в общем случае, это же совершенно нормальная практика — требовать от JSON-а соответствия опеределённой схеме и выбрасывать ошибку (контролируемо, конечно, не по NPE), если пришла какая-нибудь ерунда.

P.S. Минус не мой :)
Ок, у нас есть схема. И все равно, в чем профит-то по сравнению с динамически-типизированным языком? Вот вы потратили кучу сил на написание библиотеки, работающей с json, а дальше она не даёт вам никаких преимуществ статической типизации. И динамическая реализация, и статическая примерно одинаково выбросят ошибку, но на динамическую реализацию потрачено на два порядка меньше сил (на разработку библиотеки).

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

Это — одно проблемное место во всей кодовой базе. Объект, вышедший из-под JSON.parse<someInterface>, в дальнейшем обрабатывается с учётом всех статических типов. В то время как в динамически типизированном языке он так и останется "чем-то, определённым в рантайме".

Вероятно, у нас просто различаются задачи. У меня основной рабочий инструмент — скрипты и не слишком большие числомолотилки. Половина кода там будет тем самым проблемным местом, где статическая типизация бессильна, но тратит много сил на реализацию. Другая половина кода не настолько велика, чтобы нельзя было за время работы с ней не забыть, какого же типа объект передается в переменную с именем matrix.

В случае достаточно компактного кода — не спорю. Сам писал небольшую строкодробилку на Ruby (извлекал нужные мне данные из LaTeX-овского документа), после того, как задолбался обходить различные краевые случаи в коде на C, и от динамической типизации не сильно страдал. И соглашусь, что в названном вами случае статика действительно может быть излишней. Если же код достаточно большой, а внешняя среда в него попадает через ограниченный интерфейс — вполне логично будет всё вне этого интерфейса типизировать статически, а его самого — валидировать для соответствия этим типам.
С неизвестным форматом пример не очень удачный. Более удачный — это когда по типу JSON нужно динамически создать определенный объект. Такое используется при обработке AST.
Да. Пожалуй это единственный известный мне пример, когда можно говорить о каких-то удобствах динамики. Хотя на той же скажем яве я прекрасно это дело парсил. Но меня пожалуй скорее интересует другое. Не посмотреть отдельные примеры, а понять способ мышления, которым пользуются сильные js-разработчики. Он у них явно не такой как у меня. И при этом вполне продуктивен.
enum {
null,
bool,
int,
str,
float,
list,
object
}

В том же rust'е спокойно делается match по типу. И оно не динамически типизировано ни в один момент времени.

см github.com/serde-rs/json

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

Просто десериализуйте в объект и все. У вас же есть хоть какая-то схема? Вряд ли вы лупитесь по случайным именам вложенных хэшмап в надежде получить что-нибудь, наверное вы все же предполагаете, что у вас есть объект юзер, у него есть поле мессадж, а у него есть поле мессадж_айди. Почему бы это не зафиксировать в виде типа? Кому от этого станет хуже?

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

Так в этом весь смысл. Если вы обращаетесь к элементу, которого не ожидаете? Например, вы умеете str, object, list, num, а прилетает null.

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

Обычно это "точно знаю" заканчивается "блин, какой ДУРАК послал мне на вход ЭТО"? и "Почему у меня тут вместо строки [object Object]"

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

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

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

Есть языки которые и эти вопросы снимают по максимуму, но в моей сфере они не рапостранены.

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

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


Документация местами тоже божественная, например, "loss_func is loss function" — а с какими аргументами она будет вызвана, я сам должен угадать. При том что в нормальном языке просто в типах уже была бы информация о типах аргументов коллбека.


И самое противное — мой изначально простой код типа load_image() обрастает такой же кучей проверок (каждая была добавлена после очередного падения в рантайме и поиска бага), потому что часть png картинок грузятся как трёхмерные массивы numpy с тремя или четырьмя каналами, иногда чёрно-белые картинки вместо этого грузятся как двухмерные массивы, а ещё иногда тип данных в массиве оказывается не uint8 с интервалом 0..255, а float с интервалом 0..1.

Да, на интерпретируемых языках можно писать сложные системы. Заметил, что корреляция между использованием всех возможностей динамической системы типов в языке и вероятностью превращения проекта в кашу стремится к единице.

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

Основная идея в том, чтобы не пользоваться языком как динамическим и делать основной упор в коде на читаемость. А если всё же пользоваться, как динамическим — уже через неделю будь готов потратить n от дневного запаса когнитивного ресурса на ковыряния. Впрочем, бывают кейсы в которых это вполне нормик.
Julia — оптимальный вариант динамической + статической типизации. Можно типизировать только там, где надо.
И Perl6
Но технология, которая стала для разрабов первой «боевой», определяет их настолько сильно, что два взрослых опытных человека просто не готовы друг друга слушать.
Значит они недостаточно опытны.
Это вопрос скорее архитектурного проектирования при разработке.
Изначально JS, PHP решали небольшие сиюминутные задачи, где система сама по себе не усложнялась.
Крупные задачи решались на языках посерьезней.

Сейчас задачи другие, и соответственно меняется подход.

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

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

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

А для чего придуманы знаки препинания и заглавные буквы?


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

Вас никто не заставляет использовать AutoMapper. Проблема ведь не в языке.

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

Так оно и есть. Но к динамической типизации это не относится, и языком JS она не ограничивается.
А PHP, который во многом похож на JS по отношению к типам, почему в современном мире используется?

Потому что бытует мнение (ошибочное), что PHP — это простой язык для начинающих.

Может я чего-то не понимаю, но я всегда думал, что динамическая типизация это когда 0 == '0' == false == null, а то о чем пишет автор это стили программирования.

То, о чём пишете вы, — не динамическая, а слабая типизация. Динамическая — это когда можно написать let x = 1; processNumber(x); x = 'string'; processString(x); и не выворачивать себе мозг из-за того, что переменная x должна иметь разный тип в разных местах кода.

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

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

После многих споров на тему статика vs динамика, у меня создалось устойчивое впечатление, что разработчики делятся на 2 лагеря:


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

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

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

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

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

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

PS Разумеется, это не относится к критически важным вещам, вроде системы управления полетом или там медицинского оборудования. Хотя даже в этих случаях есть этап исследования и быстрой проверки гипотез.
Если плохо понять природу проекта или плохо уметь проектировать, конечно же, получится все ровно так, как вы описываете. Сначала будет трата времени на бойлерплейт, потом на борьбу с ним и болезненное натягивание совы на глобус.
Кстати, именно так и происходит, когда за архитектуру берется мидл, даже сообразительный — просто опыта не хватает.

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

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

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

Отдельно добавлю, что во многих мейнстримных языках (C#, Java, Go etc.) система типов слабая, не хватает банальных типов-сумм, я уже молчу про HKT. Тем не менее, это уже лучше, чем ничего.
Кстати, именно так и происходит, когда за архитектуру берется мидл, даже сообразительный — просто опыта не хватает.

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

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

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

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


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


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

Ну у вас на одной стороне "там походу разберёмся", "не думать головой", "не выяснять заранее требования". А с другой те, кто набравшись опыта, и соответственно умея проектировать, пишут всё с первого раза на чистую. Ерунда какая-то. Чёрное и белое. Раздолбаи и гении.


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


Т.е. так бывает не когда middle берётся за проектирование, это в целом рядовое явление. Чем опытнее архитектор и co, тем меньше будет граблей и серьёзных изменений\корректировок. Но они будут.

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

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

Другое ее применение — там, где поток требований к программе быстро и непредсказуемо меняется, и расходы на статические описания элементов программы (никто не будет спорить, что это расходы?) себя не оправдывают. Пример — веб-разработка.

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

Поэтому весь спор напоминает дискуссии о том, что лучше — пиво или мороженое. Смотря кому и для чего.
С другой стороны, вроде во многих популярных языках со статической типизацией есть или вводятся типы а-ля variant, any и т. п., а для динамических языков есть и улучшаются инструменты статического анализа.

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

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


Другое ее применение — там, где поток требований к программе быстро и непредсказуемо меняется, и расходы на статические описания элементов программы (никто не будет спорить, что это расходы?) себя не оправдывают. Пример — веб-разработка.

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


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

Что такое универсальные приложения? Это когда и мессенджер, и календарь, и ядро ОС немношк?




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


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

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

Правильно — у нас есть много вариантов типов. И современные языки с алгебраическими типами данных не смотря на статическую типизацию отлично справляются. Более того, эти языки больно бьют по рукам того, кто забыл, что float — частично упорядочен, а в json'е может быть null.
расходы на статические описания элементов программы (никто не будет спорить, что это расходы?)

Это не расходы, а инвестиции.
Инвестиции — разновидность расходов.

Которые потом приносят доход. А обычные расходы — не обязательно.

Вспомнил про еще один нетривиальное достоинство динамической типизации — всякие квайны и другие эзотерические вещи на таких языках получаются более изящными и лаконичными.
Есть такое. Я не могу сказать про JS, но питон (который динамически типизированный) предлагает свои best practices как с этим жить. Де-факто статическая типизация питона происходит на этапе написания тестов, именно они проверяют, что у нас не будет «TypeError: cannot concatenate 'str' and 'NoneType' objects». И тесты на питоне из-за этого получаются очень избыточными, потому что приходится проверять буквально всё.

С другой стороны, я понимаю ценителей динамической типизации. До момента появления типажей прошло много времени, а до этого нас много раз заставляли думать про наследование и другие ужасы ООПа, что превращает статическую типизацию в большее зло, чем её отсутствие.
Даже программируя на динамическом языке люди используют неявно статическую типизацию, так как для того, чтобы написать x.y(z) надо знать что у объекта x есть метод y, который принимает параметр z, а это и есть тип.

Питон говорит, что это называется "утиная типизация". У объекта может быть вообще враппер над rpc, который все вызовы всех методов тупо передаёт дальше. Не разбираясь, "есть такое или нет". Это, кстати, яркий пример динамической типизации — код не знает, но делает.

Я совершенно с вами согласен. Утиная типизация фактически это структурная типизация. Она может быть статической и динамической. Даже если она динамическая чтобы ей пользоваться надо статически о ней рассуждать. Просто без поддержи компилятора.

> У объекта может быть вообще враппер над rpc, который все вызовы всех методов тупо передаёт дальше.

Внутри враппера он не зависит от конкретного типа. Чтобы пользоваться враппером, надо понимать, какие методы там могут быть, а какие нет. То есть вывести в мозгу тип.
На питоне не надо «знать». Просто получаешь getattr с именем, возвращаешь объект. Этот объект в себе уже содержит строку со своим названием, и все пришедшие аргументы вместе с этим названием пакуются в json — и в requests.

Получается класс-прослойка, который готов выполнить и foo.len(), и foo.p_equal_np(), и foo.transpose(), абсолютно не зная про эти имена в момент написания кода.

Я и говорю что прослойка не знает. Но в том месте где вы пишете foo.len() вам надо знать что при выполнении туда попадет что-то у чего есть метод len.

А, я понял вашу смысль. Мы ожидаем, что у «этого» есть длина. Это не типизация, это трейты, или, в контексте питона — «утиная типизация». Что угодно с ".len()" сойдёт в этом месте.

… Но есть код, который даже не знает что вызывать. Например, многие фреймворки тестирования просто вызывают все функции по маске, не думая о том, что это за функция.
… и у этой функции тоже есть тип.
Наличие __call__, да. Но в этом случае, какой тип у mock.MagicMock? Оно может всё. Вообще всё.

Указанный вами.

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

— Прочитать её как флоат.
— Прочитать её как инт.
— Перейти на неё как на код.

И всё это не делает происходящее сколь-либо типизируемым. Типы — это ограничения. Когда у нас нет ограничений, то нет и типов.
Память — и правда не типизирована. А mock.MagicMock — типизирован, но он типизирован тем типом, который вы подразумеваете. Моки же используются не потому что это круто, а в качестве замены для зависимостей. А у зависимостей есть типы.

Когда вы используете mock.MagicMock для замены foo.Bar — ваш mock.MagicMock как бы имеет тип foo.Bar. Код, которому вы передали свой мок, ожидает именно foo.Bar.

mock.MagicMock максимальное приближение к нетипизированному объекту, которое можно придумать. Он не "имеет тип", он не "вызывает ошибок". Ровно так же, как jmp $RANDOM не вызывает ошибок типизации в машинном коде (даже если это ахинея).


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

у него тип mock.MagicMock :) или mock.MagicMock — я не знаю питона глубоко

С точки зрения системы типов у него типа нет, потому что он ничего не ограничивает.

Насколько я понял, он гарантировано предоставляет. Нет?

Он их предоставляет в том смысле, что ничего не происходит. Ни хорошего, ни плохого.

Вот это наверное и есть гарантия — какой-нибудь int упадет с ошибкой

Тыг это не гарантия, это анти-гарантия. Мы ожидаем, что на попытку str+None нас пошлют нафиг, а mock всё жрёт и причмокивает. Падают только негативные тесты (которые ожидают ошибку).

Повторю, magicMock, это такой метод отключить типизацию в питоне.

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


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

Что такое «свойство»? Пропуская низкоуровневые ответы, высокоуровневый — обещание интерфейса, что такая-то операция имеет смысл. Ошибка уровня типизации — это защита языка от выполнения операции, для которой нет реализации (т.е. нет смысла).

А теперь у нас нечто, что не реализует смысл, а всего лишь выключает ошибки типизации. Внутри там ничего не происходит (я не про счётчики вызова, я про «смысл» — len и т.д.), и в этом отношении он эквивалентен просто коду.

Мы можем сделать jmp куда угодно — нас никто не остановит, и никакого соглашения об ограничениях нет.

Система типов — это соглашение об обозначении смыслов, плюс машинный энфорсинг этих соглашений. mock всё это отключает.
"Это не типизация, это… — «утиная типизация». Что угодно с ".len()" сойдёт в этом месте.

Утиная типизация это тоже типизация. Это просто не номинативная типизация а структурная.

не думая о том, что это за функция.

"функция" это уже тип.

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

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

Или проверить наличие этого метода в рантайме, Или проверить тип в рантайме, когда значение придёт. Разве можно назвать статической типизацию параметра функции если пишешь код типа if (param instanceof SomeClass) throw new TypeError(), пускай он даже спрятан под сахаром типаfunction someFunction(SomeClass param)`?

Я в последнее время пишу в основном на Javascript. Так что меня можно назвать сторонником слабой динамической типизации. Перечислю ее плюсы лично для себя, исходя из своего опыта…
1. К примеру, у меня есть функция, которая парсит массив объектов, например Employee, и что-то с ними делает. В JS я могу описать логику этой функции, и затем применять ее к любому массиву объектов, чья внутреняя структура одинакова. Мне не нужно заботится о типе объектов, которые содержатся в массиве. В Typescript, насколько я понимаю, мне пришлось бы внутри моей функции явно указать тип объектов, которые содержатся в массиве. Что-то вроде ...<Employee[]> (прошу прощения, если ошибся в синтаксисе, никогда промышленно не писал на TS). При попытке передать туда массив объектов другого типа компилятор будет ругаться. Мне надо идти в мою функцию и переписывать ее, добавляя новый тип, или делая переключение типов, или создавая свой тип. Точно не помню, но суть — в усложнении возможностей повторного использования. Можно вообще присвоить параметру значение по умолчанию и пускай приходит любой тип. Если он будет расценен, как false, функция возьмет мое значение по умолчанию.
Это же касается и возвращаемого функцией значения. В JS я могу вернуть в зависимости от логики функции, например, false, число или массив чисел. Мне не нужно прописывать 3 типа или выбирать только один их них. Причем возвращаемый результат я могу записывать с одну переменную. Например, присвоив ей при инициализации false, а затем число или массив чисел в зависимости, скажем, от количества возвращаемых данных. В других ЯП мне пришлось бы создавать 3 функции с одинаковыми именами, но с разной сигнатурой.
2. Возможность быстрого сравнения данных разных типов. В частности, приведение к логическому типу. Например, если в метод пришла пустая строка, число 0, значение null или undefined, то все они логически определяются как false. Я могу просто проверить if(!value) и не делать ничего с пришедшими данными. Это позволяет быстро определять значения, непригодные для работы: undefined (если значение не было установлено), null (если явно установлено пустое значение) и какое-то из пустых числовых и строковых значений, трактующихся как false, не прибегая к определению типа этого значение. Мне просто не нужен тип в данным случае. Это также облегчает использование примитивов в параметрах функции. Я ожидаю в пераметре функции массив или строку, а приходит null. Я могу сделать быстрый возврат при получении null. Мне не нужно описывать тип каждого аргумента.
3. Объединение значений разного типа. В JS я могу с легкостью сложить число со строкой. Это удобно при работе в WEB, например при получении данных их формы и произведения с ними каких-то вычислений. Например, я могу объединить имя (строка) и возраст (число) пользователя и вывести для него приветствие, например, «Здравствуйте, Петр. Сумма вашего заказа 30 руб.» Я как-то сталкивался с C# и искренне не мог понять, почему он не дает мне сложить число со строкой. Значения из полей форм в HTML приходят в string, и такая возможность бывает удобной. Это не камень в огород C#, просто я привык мыслить по-другому.
4. Отдельно отмечу возможность логического сравнения в операторах && и ||. Например, один из излюбленных приемов среди JS-программистов:
someFlagValue && anotherValue

Позволяет вернуть, например, в шаблон второе значение, только если первое истинно.
а
firstValue || secondValue

одно из истинных значений. Плюс в том, что мне не нужно думать, какой тип придет в них (undefined, null или пустая строка), для меня главное, как значение будет оцениваться логически.
5. Не уверен точно, но мне кажется, что для создания массива в ЯП со строгой статической типизацией нужно, чтобы данные в массиве были одного типа. Часто это неудобно, особенно если нужно потом эти данные в качестве параметров функции в виде массива. В JS можно запихнуть в массив данные разных типов.
6. Автоматическое приведение к числу. Это одна из моих любимых вещей. Посколько данные из HTML-форм приходят в программу в виде строк, даже числа, нет ничего странного, что в JS я могу проверить правильность
'2' > '3'
Например, когда у меня есть выпадающий список с перечнем годов и мне нужно сравнить 2 значения. В других ЯП часто нужно делать что-то вроде
String.Compare(str1,str2)
или
ParseInt

7. Устойчивость программы. Javascript позволяет гибко производить вычисления с данными разных типов. Да, эти вычисления могут быть ошибочными, но программа продолжит работу. Разумеется, это неприемлемо в ПО, предназначенном, скажем, для медицины и космоса, но вполне допустимо для показа на карте 10 ближайших баров в округе. Да, может быть, где-то будут ошибки с точными координатами или в названии одного из баров затесается "[object Object]", но приложение продолжит работу, что в данной ситуации, возможно, более важно.

Возможно, такое использование языка несколько усложняет жизнь и на нем бывает тяжелее писать, но мне нравится его гибкость. Кстати, я всегда считал и продолжаю считать, что программирование на JS не легче, как это принято считать, а наоборот тяжелее, чем на языках со строгой статической типизацией. Есть еще и другие вещи которые мне нравятся в JS, но я уже много написал, да и не все сразу вспоминается.
НЛО прилетело и опубликовало эту надпись здесь
Мне не нужно заботится о типе объектов, которые содержатся в массиве. В Typescript, насколько я понимаю, мне пришлось бы внутри моей функции явно указать тип объектов, которые содержатся в массиве.

Нет, не пришлось бы. Есть генерики, то есть пишите что-то вроде: parse(arr: T[]) {...} и теперь оно работает для любого Т.

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

Прекрасно работает:


function yoba(x: number) {
    if (x < 0) {
        return true;
    } else if (x === 0) {
        return 0;
    } else {
        return [''];
    }
}

let res = yoba(-10);
res = 0;
res = ['1', '2'];

тип res: number | boolean | string[]


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

function yoba(strs: string[] | null) {
    if (strs) {
        return strs.reduce((x, y) => x + y, '');
    } else {
        return '';
    }
}

В JS я могу с легкостью сложить число со строкой.

Да не проблема:


function yoba(x: string, y: number) {
    return x + y;
}

Плюс в том, что мне не нужно думать, какой тип придет в них (undefined, null или пустая строка), для меня главное, как значение будет оцениваться логически.

И не думайте!


function yoba(x: undefined | null | boolean | string) {
    return x || 'yoba';
}

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

const yoba = [1, true, 'yoba'];

yoba: (number | boolean | string)[]


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

const yoba = '1' > '2';

yoba: boolean


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

типы не меняют семантику рантайма

function yoba(x: undefined | null | boolean | string) {...

Не уверен, что приведенный вами пример обладает хорошей читаемостью
Все-таки
function yoba(x = 'Default') {...
воспринимается куда легче

const yoba = [1, true, 'yoba'];

Это также код и на JS. Не думаю, что C# или Java позволят такое напрямую без создания объектных оболочек.
Не думаю, что C# или Java позволят такое напрямую без создания объектных оболочек.

В вашем ecmascript любое число уже изначально обладает своей объектной оболочкой! Так что неявное их создание компиляторов никак не может быть записано в недостатки C#.

В вашем ecmascript любое число уже изначально обладает своей объектной оболочкой! Так что неявное их создание компиляторов никак не может быть записано в недостатки C#.
Да, но для программиста при разработке тип примитива не будет является объектом с точки зрения языка. Это позволяет писать более лаконичный код. Разумеется, в JS можно написать код вида
const myNum = new Number(2);
Но тогда «myNum » явно будет объектом, а можно использовать сразу тип «number».

… и что? Вопрос-то был про массив из разнородных данных.

Возможно, я немного запутался в ветках комментариев, поэтому, чтобы не разводить лишних споров, я просто попрошу вас ниже написать для кода на JS:
const yoba = [1, true, 'yoba'];

аналог кода на C# в виде массива из разнородных данных.
var yoba = new object[] { 1, true, "yoba" };
А «yoba» в вашем случае будет объектом или массивом?

В C# массивы являются объектами.

Не уверен, что приведенный вами пример обладает хорошей читаемостью

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


function yoba(x = 'Default') {
    return x;
}

yoba: string? -> string


Это также код и на JS.

Это ts, он статически типизирован, все ок. Да, в c# или java так нельзя, потому что система типов в этих языках проектировалась несколько с другим прицелом.

Да, в c# или java так нельзя

Ну да, с кастом до object можно, но это наверное все же несколько другой кейс. С-но, в тс можно типизировать и как const x: [number, boolean, string] = [1, true, '']; в c# будут таплы ближайшим аналогом (с тем исключением что таплы не являются подтипами массивов), есть ли что-то похожее в джаве — хз.

А «yoba» в вашем случае будет объектом или массивом?
В первую очередь массивом. Объектом тоже будет, ведь любой массив — объект.
Простите, но простое гугление выдало мне такой способ создания массива в C#:
int[] numbers = new int[] { 1, 2, 3, 4, 5 };

Ваш пример больше похож на создание именно объекта.

Простите, но вы несете чушь. Признак массива — [] (квадратные скобки), а вовсе не int. Вместо int можно написать любой тип данных. В том числе object. В моем коде написано object[], что определяет массив объектов.

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

Ну так в массиве объектов как раз и содержатся "несколько значений с примитивных типом данных". Что вам не нравится-то?

Тем, что внутри вашего массива лежит объект, который при итерации выдаст в качестве item именно объект. В моем примере на JS при итерации, скажем, с использованием «forEach» вы получаете доступ сразу к нужному значению массива. Также мне хотелось бы узнать, сможете ли вы передать созданный вами массив в функцию в качестве параметра?

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


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

А что, собственно, может этому помешать?

Тем, что внутри вашего массива лежит объект, который при итерации выдаст в качестве item именно объект. В моем примере на JS при итерации, скажем, с использованием «forEach» вы получаете доступ сразу к нужному значению массива.

В его примере точно так же будет доступ к нужному значению массива.

И этим значением будет объект, а не примитив. И уже из объекта нужно доставать значение, обращаясь к его свойству. Это усложняет структуру и понимание кода.

Ну и где вы тут видите обращение к свойству объекта?


var x = (int)y;
массива, в котором содержатся несколько значений с разными примитивными типами данных

Массив содержит данные одного типа (и его подтипов). То, о чем вы говорите, является кортежем.
Массив содержит данные одного типа (и его подтипов). То, о чем вы говорите, является кортежем.

Спасибо! Вот то, что я имел ввиду. Сказываются различия в терминологии в ЯП)) Значит мое утверждение под номером 5 выше, по крайней мере, для C# неактуально.
С-но из вашего описания было не совсем понятно, что требуется. Если речь просто о дефолтном значении, то можно точно так же и написать

Вам для того, что в TS это заработало, пришлось перечислить все типы для x, которые могут прийти в функцию:
function yoba(x: undefined | null | boolean | string) {...

Я лишь подчеркнул, что JS этого не требует.
Вам для того, что в TS это заработало, пришлось перечислить все типы для x, которые могут прийти в функцию:

Так это два разных кода. Если вы на js напишете function yoba(x = 'Default') { то обрабатывать дефолтным образом оно будет только undefined, в точности как вариант на TS, null, false и все остальное потребуется описать руками.
Ну а если вам просто лень писать | null | undefined… то можете просто сделать:


type Falsey<T> = T | null | undefined | false | '' | 0;

и использовать везде.

Ну а если вам просто лень писать | null | undefined… то можете просто сделать

А чем это тогда отличается от того, что есть в JS по умолчанию?

Неужели тем, что компилятор проверит корректность типов в вашем коде статически?

Но если там перечислены практически все типы данных, какой вообще тогда смысл в такой проверке?

Статика поможет тем, что вы для аргумента x типа Falsey гарантированно не забудете сделать проверку if (x) {} else {} и обработать граничный случай. А вот если типов нет — можете и забыть.

1) То есть функция принимает массив чего-то, делает что-то и возвращает то ли строку, то ли массив, то ли bool? Как вы вообще понимаете что у вас в коде происходит?
1) То есть функция принимает массив чего-то, делает что-то и возвращает то ли строку, то ли массив, то ли bool? Как вы вообще понимаете что у вас в коде происходит?

Да, например, если нужное значение не вычислено или данных не хватает, то можно вернуть «false». Если нужно вернуть одно число, то оно и возвращается, если чисел больше одного, что можно вернуть массив.
Очень сложно придумать кейс, когда действительно нужна такая вариативность и нет нарушения SRP.

«Because I can»

Вообще тут вопрос в том, как следует обрабатывать данные. Если все кейзы обрабатываются однородно, то имеет смысл возвращать пустой массив вместо null и массив из одного элемента вместо числа. С другой стороны, если эти случаи на call site требуется обработать специфически — лучше как раз сделать тип number[] | null | number — тогда мы гарантированно не забудем обработать кейзы с пустым и одинарным значением, в то время как в случае возвращаемого типа number[] их можно прощелкать.

если эти случаи на call site требуется обработать специфически

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

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

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

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

Очень сложно придумать кейс, когда действительно нужна такая вариативность и нет нарушения SRP.
Ну не знаю, может быть, вас это удивит, но в своей работе я практически ежедневно сталкиваюсь с подобным кодом. Именно поэтому эта особенность вспомнилась мне одной из первых.
К сожалению не удивит, это популярный подход в JS.
Почему «к сожалению»? Что в этом подходе не так? Он позволяет создавать исключительно гибкие функции, код в которых не будет падать с ошибкой при неправильных или недостаточных входных данных.

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

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

Так SRP это подход объектно-ориентированного, но не функционального программирования.
Функциональное программирование поощряет божественные сущности?)
Почему «божественные сущности»? Можно, например, в зависимости от переданных параметров вернуть из функции «а» созданную функцию «b», можно вернуть функцию «с», а можно вернуть «false», если что-то пошло не так.
Почему «божественные сущности»?

Потому что слишком много ответственности на себя берут. «Вокруг них все крутится».

Можно, например, в зависимости от переданных параметров

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

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

Это не рулетка, если вы хотите заменить функцию, значит вы уже в теме и знаете зачем и что на входе/выходе.
Да и вообще наличие типов не значит, что вы передадите на вход правильные данные.
Всегда есть соблазн добавить сделать стрёмный ход. Поддержание кодовой базы в надлежащем виде требует усилий (которые необходимо закладывать в оценку, иначе скупой оплатит еще и дополнительный таймбокс на QA при стабилизации плохокода).
Но есть в жизни и хорошие вещи:
  • тесты смогут подтвердить, что новые функции продолжают выполнять старые контракты (или укажут на то, как нужно изменить клиентский код для соответствия новым контрактам);
  • аффект будет меньше: при плохом сценарии рискуют пострадать только некоторые (непосредственно замененные) точки вызовов, а не все сразу.
НЛО прилетело и опубликовало эту надпись здесь
SRP это общий принцип программирования. Может(и должен) применяться в любой парадигме.

вы реально хотите с десяток вариаций console.log?

О каких вариациях речь?

для каждого примитивного типа данных. для number, для string и т. п.

Зачем? Алгоритм console.log остается одним и тем же для разных типов данных. Но если он захочет для number варить кофе и возвращать промис готовности — вот это мы оттуда должны убрать.

Не обязательно, можно будет просто сделать тип-сумму.


Хотя запашок у подобной потребности определнный есть.

SRP говорит, что не стоит так делать. Но чувствительность к запахам — штука индивидуальная)

Я верю, что так делать никогда не надо. Вопрос про "можем"/"не можем"))


Во-первых можем.
Во-вторых хотя можем, лучше никогда так не делать)

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

Не, алгоритм разный. Разные алгоритмы преобразования к строке у строк, чисел, объектов и т. п.

динамическая диспетчеризация в помощь.

То есть проверка типов в рантайме?

С чего бы это?

Ну а как реализуются разные ветви в зависимости от типа аргумента функции?

С помощью таблицы виртуальных методов. Метод по смещению 10 от начала vtable — нужная нам версия foo.

А как определить 10 или 0, если мы не знаем какого типа будет аргумент?

Потому что vtable всегда имеет одни и те же методы по одному и тому же смещению. Почитайте как ООП работает, пожалуйста.

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

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

И это не отменяет сказанного мной ни в какой мере.
НЛО прилетело и опубликовало эту надпись здесь
Если член не обнаружен в текущем объекте, рантайм пытается получить объект прототипа (по ссылке, которая есть почти в каждом JS-объекте) и искать на нем, проходя так всю цепочку, пока либо не найдет искомое, либо не встретит null на месте ссылки.
Преобразование сущностей к строке обеспечивает полиморфизм *.prototype.toString, а console.log лишь отправляет результат на консоль.
console.log не приводит значение к строке, а использует рефлексию, для формирования разного интерфейса для разных типов.
Ну если уж начинаем глубоко копать, то это зависит от реализации) В дохромовую эпоху console.log выводил просто строку.

А потом сидишь с коллстеком в 50 "супер-гибких функций", и пытаешься понять, кто данные попортил, а возможно и не один раз. Каждая ведь решила "гибко" адаптироваться. И дебажишь 4 часа, чтобы понять, что в от в этом параметре число нужно в строковом представлении передавать, а не в числовом.

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

Аналог стандартного console.log

«Можно вернуть» — это замечательно. Я даже не спрашиваю «зачем?» — пусть будет.

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

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


При статической типизации вам это поможет сделать анализ типов и рефакторинги.

То есть преимущество JS в том, что он позволяет говнокод многослойно писать? Ну, такое себе…

> и не уронить всё приложение.

Вы просто не заметите, ибо п.7
Допустим, вам надо отправить сообщение юзеру, для этого нужно получить канал отправки по id юзера, а потом отправить это сообщение по каналу. У вас есть функция, которая принимает на вход user_id, на выходе отдает один из нескольких типов данных: или экземпляр класса SMS, или экземпляр класса Email, или экземпляр класса Websocket, или вообще null. Вы вызываете эту функцию, получаете объект, а потом через него отправляете сообщение object.send(«hello»).

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

Если вы получает из «нереляционной базы какие-то данные» — причём то фотографию, то вообще песенку, то какой у всего этого действия физический смысл? Что пользователь-то должен увидеть?
Допустим, есть опросник. На некоторые пункты юзер отвечает текстом, на другие — числом, еще один — выбор нескольких вариантов из перечисленных, и вдогонку еще и фотографии может прикрепить.

Вам нужно сделать фронт-админку для этого. Вы отправляете на бек запрос с айдишником опроса, в ответ от присылает вам всё что заполнил юзер — массив из данных разных типов. Вы каждый раз получаете следующий элемент этого массива — цикл по сути, в js это можно сделать функцией forEach() — и оно возвращает каждый раз данные разного типа. В зависимости от типа данных вы рендерите по-разному: где-то добавляете тег img, где-то checkbox, где-то просто вставляете строку.

Вот и получается что-то такое странное:
let arr = returnedDataFromBackend;
arr.forEach((ele) => {
 // функция каждый раз возвращает переменную ele разных типов
 if (typeof ele === 'string') return renderString(ele);
 if (ele.type == 'image') return renderImage(ele);
 ...
});

Разумеется, этот код можно улучшить. К примеру, подойти к беку и сказать, чтобы он отдавал мне все данные в виде массива хешей с двумя полями: type и value. Либо самому написать прослойку которая нормализует полученные данные, а потом передает их дальше. Но это уже совсем другая история :)
Если у меня фиксированное количество вариантов, то я либо должен сделать либо std::variant, либо банальный union с селектором, как в Pascal 1970го года. Либо можно сделать интерфейс, который будет с этим типом-содержащим-невесть-что работать (и потом его реализовать).

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

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

А вот если мы имеем заранее неизвестный набор типов и понятия ни имеем — что с ними делать… зачем это? Куда это? Как это?
НЛО прилетело и опубликовало эту надпись здесь

В Rust для этого есть impl Trait и Box<dyn Trait>. Как раз для примера из коммента от 0xd34df00d, когда мы не знаем, что за сущность нам пришлют, но знаем, что она будет уметь некоторую нужную нам функциональность.

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

Да, только на деле потом оказывается, что программист в качестве типа ответа прописал String, в базе оно лежит как VARCHAR(255), и для того, чтобы добавить в варианты картинку, придется переписать десять классов и три шаблона. А сделать это надо буквально вчера.
То есть лучше было бы начать переписывать не глядя на типы, и словить баги в 10 классах и 3х шаблонах? :-)

Так может эти баги никто и не заметит, чего вы волнуетесь сразу! :)
В итоге силы — сэкономлены, заказчик — доволен. Пока… :)

Угу. У нас вот однажды выяснилось, что какой-то сервис не работает уже 2 года. Причем как выяснилось? Сдох сервер, пришлось переезжать на новый. И в процессе проверки успешности переезда обнаружилось, что сервис не работает. Начали разбираться — а он и раньше не работал!

С тех пор прошло уже 2 года. До сих пор не работает, уже 4 года в сумме. Возможно, это и правда кому-то кажется нормальным…

Ну если у кого-то сервис 2 года не работает, и никто не заметил, то тут проблемы явно не в ЯП, и уж точно не в типизации.

Дело в подходе. Нормально ли оставлять недоделки на потом. Если нормально — то динамическая типизация и правда может помочь сделать что-то быстрее и уложиться в сроки (первые несколько раз).


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

А он вообще нужен? Что за сервис такой, который четыре года не работает, а никто не чешется?
А фиг его знает. Его другой подрядчик делал…
Если переписывать на динамическом языке, то вполне можно переписать два шаблона и три класса. А чем меньше переписываешь, тем меньше шанс наделать ошибок, тем проще тем, кто делает code review, ну и так далее.

А за счет какого волшебства у вас в динамическом ЯП количество классов уменьшится?

Ну а как иначе? Вот был у нас ответ с типом String, а теперь должен быть и String, и Image. Начинаем переписывать!
Для начала введем базовый класс, AbstractAnswer, и от него сделаем двух наследников: AnswerString и AnswerImage. Потом добавим к этому фабрику AnswerFactory — она будет брать сериализованный JSON, пришедший от API, и порождать нужный класс. Конечно же, у каждого из классов будет свой метод render, каждому нужен свой шаблон…
В динамическом языке все будет попроще, менять будем только рендер:
if (typeof(answer) == "object" && answer.type == "image") {
    return renderImage(answer.image);
} else {
    return renderText(answer);
}

Это только фронтэнд, про бэкэнд я молчу.

Речь на самом деле не про статическую/динамическую типизацию, а про номинальную/структурную. Просто с динамическим рантаймом структурная типизация дается проще.
Ну а как иначе? Вот был у нас ответ с типом String, а теперь должен быть и String, и Image. Начинаем переписывать!

Ничего не начинаем, просто пишем в типе возврата string | image, а потом тот самый ваш код выше.

Для того, чтобы у вас можно было вернуть или string, или image, вам понадобится рантайм с динамической типизацией, или рефлексией. Это к стат.анализу flow или транспилеру TypeScript можно приписать string | image.

Нет. Для того чтобы передавать string | image, совершенно необязательно иметь в рантайме полную информацию о типах. Достаточно чтобы разные типы были различимыми.


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

Рантайм динамического языка должен обеспечивать гарантии, даваемые системой типов языка. Если я написал function a(int b) на динамическом языке, то рантайм должен дать TypeError при попытке объект, например, передать.

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


В том же С++, например, рантайм в общем случае ничего не знает про типы передаваемых параметров. Но это ничуть не мешает существованию std::variant или даже std::any

А какое отношение эти типы имеют к, например, PHP? )

И правда, какое же? И при чем тут PHP?

Язык с динамической типизацией, дающий в рантайме гарантии своей системы типов. В последнее время всё более строгие гарантии.

И что? Как это подтверждает тезис что динамическая типизация нужна для типов-объединений?

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

Тогда зачем вы это пишите в этой ветке?
И, кстати, у рантайма не может быть динамической типизации.
Хорошо, но при чем тут PHP?

У него есть динамическая типизация в рантайме, именно рантайм PHP гарантирует утверждения системы типов языка PHP.

Нет такого понятия, как динамическая типизация рантайма. Есть только проверки типов в рантайме.

Во-первых, вы писали про "переписать 10 классов", а не про "написать 10 классов".


Во-вторых, не выдавайте свои фантазии на тему ООП за недостатки статической типизации. Нахрена тут фабрика, да еще и абстрактная?


В-третьих, исходно предлагалось, вообще-то, использование готовых языковых либо библиотечных механизмов вместо наследования. Вот я пишу: std::variant<std::string, Image> — где тут наследование и лишние классы?

Так у вас на входе юзер, на выходе наличие .send() — вопросов нет.
НЛО прилетело и опубликовало эту надпись здесь
Допустим, вам надо отправить сообщение юзеру, для этого нужно получить канал отправки по id юзера, а потом отправить это сообщение по каналу. У вас есть функция, которая принимает на вход user_id, на выходе отдает один из нескольких типов данных: или экземпляр класса SMS, или экземпляр класса Email, или экземпляр класса Websocket, или вообще null.

То есть вы думаете что результат является каналом отправки, и SMS, Email и Websocket являются каналами. Это похоже на описание типов

Ну как бы это разные каналы. Если мы объединяем в один тип SMS, Email и Websocket, то с тем же успехом можно объединить в один тип строку, число и булево значение — всё это данные. Только зачем тогда вообще типы нужны, если мы их все приводим к одному супертипу?

UPDATE Мне в этом смысле golang нравится — есть структуры (типы), а есть интерфейсы (поведение). В итоге разные структуры (SMS, Email и Websocket) реализовывают одно и то же поведение — отправку сообщения.
Вы не объединяете в один тип, вы описываете интерфейс канала и получаете разные имплементации. Но у вас не падает все внезапно, когда channel.send({ duck: «Я маленькая уточка и знать не знаю про никакие контракты.» });
> один тип строку, число и булево значение — всё это данные.

Все это объекты. Да, есть типы есть подтипы.
> олько зачем тогда вообще типы нужны, если мы их все приводим к одному супертипу?

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

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

При попытке передать туда массив объектов другого типа компилятор будет ругаться.

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


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

Не является особенностью динамической типизации.


Я как-то сталкивался с C# и искренне не мог понять, почему он не дает мне сложить число со строкой.

Вранье, C# это разрешает делать.


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

В том же C# любое значение может быть приведено к object и положено в object[].


Автоматическое приведение к числу. Это одна из моих любимых вещей.

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


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


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

Чтобы программа продолжала работу при ошибках — нужно не забывать ставить try/catch. А продолжать вычисления после ошибки типизации — бессмысленно.

Typescript использует структурную типизацию. Это, в частности, означает что компилятор не будет ругаться если у объектов другого типа есть все нужные поля.
А нужных полей может и не быть. Объекты могут незначительно различаться, но при этом все равно быть успешно распарсены одной JS функцией — утилитой.

В том же C# любое значение может быть приведено к object и положено в object[].
В JS для этого не нужно создавать объект. Можно положить в массив как примитивы, так и объекты:
const customArr = [1, 'two', {three: 'three'}];


Чтобы программа продолжала работу при ошибках — нужно не забывать ставить try/catch
Для этого придется обертывать в try/catch практически весь код, а не предположительно наиболее уязвимые участки кода, как обычно делается.

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

Это одна из ужаснейших вещей. Простота перевода между строками и числами заставляет программистов забывать об отличиях между ними, равно как и о региональных особенностях
Эта особенность наоборот заставляет не забывать, а помнить об этих отличиях. Насчет «ужаснейшей», как говорится, это дело вкуса.
А нужных полей может и не быть. Объекты могут незначительно различаться, но при этом все равно быть успешно распарсены одной JS функцией — утилитой.

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


В JS для этого не нужно создавать объект.

Почему приведение типа к object вы называете "созданием" объекта?

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

Почему приведение типа к object вы называете «созданием» объекта?
Насколько, я знаю, в C# массивы не могут иметь разные типы данных. Это принцип проектирования массивов в этом языке. Поэтому я предполагаю, что вы имели ввиду создание объектной оболочки над примитивом. В отличие от моего примера, где используется сам примитив.
Ну к JS понятие «требуется функцией» не вполне применимо. Переданных параметров может быть больше или меньше, чем описано аргументов в сигнатуре функции.

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


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

Вас что, вручную заставляют создавать эту самую объектную оболочку? Так нет же, компилятор автоматически приводит любое значение к object! Что же до эффективности такой операции — не js-нику жаловаться на производительность упакованных объектов в c#...

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

… и все равно у этого подстраивания есть какие-то пределы.

Объекты могут незначительно различаться, но при этом все равно быть успешно распарсены одной JS функцией

придется обертывать в try/catch практически весь код, а не предположительно наиболее уязвимые участки кода

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

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


"Наши самолеты в целом более менее скорее летают, чем падают. Предположительно большинство из них долетит до места назначения, наиболее похожие на критически уязвимые места в коде мы с большой вероятностью обернули в try/catch".

Точность, достойная тру-разработчика. Просто восхитительно. Надеюсь, вас в жизни не допустят до написания кода систем, имеющих хоть какое-то отношение к человеческому здоровью/жизни.
Спасибо за характеристику меня, как разработчика)) Но я в своем развернутом мнении выше как раз и описал, что это
… неприемлемо в ПО, предназначенном, скажем, для медицины и космоса, но вполне допустимо для показа на карте 10 ближайших баров в округе
Особенно, если приложение для показа баров должно было быть готово «вчера», но узнали об этом, как обычно, сегодня.
Софт для самолетов никто на JS не пишет. Для разных типов задач существуют разные подходы и инструменты, это понятно. Писать софт для марсохода — это одно дело, нафигачить MVP за месяц для заказчика (а иначе он уйдет) — это совсем другое.
В современных языках динамическая типизация или статическая определяется исключительно разработчиком.

TypeScript:
// Динамика это круто
function f(a, b) {
	// Будь что будет
	return a * b;
}

f(1, "abc") // Ну и что, что получаем NaN, зато не надо писать типы. Красота ! 


// Укажу динамический тип. Динамика это круто
function g(a: any, b: any): any {
	// Будь что будет
	return a * b;
}

// Статика это тоже круто
function h(a: number, b: number): number {
	// Умножаем числа
	return a * b;
}

h(1, "abc") // Плохой компилятор, не хочу ошибку компиляции


C#:
public class Program
{
	// Динамика это круто
	public static dynamic g(dynamic a, dynamic b) {
		// Будь что будет
		return a * b;
	}
	
	// Статика это тоже круто
	public static int h(int a, int b) {
		// Умножаем числа
		return a * b;
	}
	
    public static void Main()
    {
		g(1, "abc"); // Кидает исключение, ерунда. Зато принимает любые типы
		h(1, "abc") // Плохой компилятор, не хочу ошибку компиляции  
    }
}

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

Небольшое лирическое отступление. Прочитал комменты и в очередной раз убедился что прогеры народ всё-таки (как бы это помягче сказать...) совершенно особый. Как то с трудом могу себе представить подобный спор об инструментах (а ведь говорим мы именно о них !) в среде например сантехников. Ещё большое спасибо любимому хабру, что тут всё-таки атмосфера научного диспута, а не бардака при пивном ларьке! А то в начале 2000-х на соответствующих конфах помню такое…
И да. Очень хотелось бы чтобы подобную статью написал кто-то из сильных js разработчиков со своей колокольни. Надеюсь эта дискуссия кого-нибудь из них сподвигнет. Буду каждый день просматривать хаб по джаваскрипту :))
>среде например сантехников
Да ладно.
Метапол против полипропилена.
А если наберёте Festool, то откроете ворота в ад.

Именно. Совсем недавно на youtube наблюдал, как «спорили» два ремонтника (ну, не совсем спорили, и не совсем сантехники, а скорее сборщики мебели).

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

Да проще. Лён против фума =)
Актуально уже скорее фум против унилока)
Мне две недели назад со льном делали, и я так думаю, про унилок они даже не слышали.
Кошмар сантехника-хипстера: пластиковые трубы, лён и сверху красочкой…

Давайте я попробую. У меня есть интересный опыт вида Scala -> JavaScript (и сервер и фронт) -> Typescript -> Scala.


  1. всем кто пишет на строготипизированных языках нужно обязательно попробовать языки со слабой типизацией и наоборот. Например, на домашних поделках внезапно оказывается, что можно не писать тонны бойлерплейта (привет Java), и не воевать с компилятором. Такое вот чистое творчество.
  2. как правильно заметили выше — в слаботипизированных языках типы никуда не исчезают. Они просто становятся сильно менее строгими и позволяют больше фривольностей.
  3. судя по всему существует определённый предел для проекта с со слабой типизацией. Это и количество задействованных одновременно людей (интерфейсов нет), и объем проекта (типы — документация). Выше некоторого объема начинается настоящая боль.
  4. насколько я вижу сейчас, в мейнстримовых языках типы — не панацея, но здорово помогает. Системы типов либо не достаточно мощные (Java, например), чтобы выражать всё задуманное. Либо чересчур сложные (Haskell, Agda, Idris), чтобы на этом можно было писать бизнес-код средними бизнес-программистами. Плюс частенько порождает некоторые теоретические тупики, которые императивно решаются в десяток строк кода, а функционально заставляют весь мир намотать на себя. Есть Scala, которая пытается быть и тем и другим, но у неё свои проблемы есть.
  5. как минимум в мейнстримовых языках со строгой типизацией от написания тестов никуда не сбежать — система типов не достаточно мощная, чтобы выразить все операции с данными, плюс в игру вступают сайд-эффекты, внешний мир, базы данных, итд. Со стороны приёмочного тестирования, едва ли тестов меньше. В юнит-тестах — возможно.

Вообще, безотносительно самого JS мне очень нравится связка JS + TS из-за gradual typing. Хочется писать быстро — пишешь быстро на JS и надеешься на лучшее. Хочется писать надежнее — развешиваешь типы и спишь по ночам крепче.


Если кому-то интересно, я с радостью могу поотвечать на волнующие вопросы :)

Немного не в тему, но как вообще со скалой, с вакансиями и т.п.? Мне многие знакомые советуют с C# свичнутся, для большей продуктивности, но я не вижу ни вакансий, ни потребностей рынка… С удовольствием почитал бы статью, но сойдет и комментарий.

Они есть, но если сравнивать с C#, Java и тем более JS — значительно меньше.
Вообще на мой взгляд Scala сейчас переживает не лучшие времена. Волна хайпа прошла, scala-only фичи потихоньку подвозят в мейнстримовые языке (те же лямбды в Java заставили задуматься очень многих о том действительно ли им всё это надо или и джавы хватит). Kotlin, опять же появился.


Вообще по своему Scala-опыту могу сказать, что использовать Scala как Better Java не очень хорошо выйдет. Оно хоть и достаточно интероперабельно, но не так всё гладко. Плюс многие Scala-библиотеки делают вещи scala-way (чем и хороши). В этом плане Kotlin выглядит сильно лучше.


Но и нельзя сказать что рынок совсем глух. Даже на неактивный в данный момент профиль на upwork раз в пару недель уж точно инвайт на Scala-интервью прилетает.


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

Просто хочется нормальный паттерн-матчинг, нормальные атд (и гадт при должной фантазии), типы высших порядков, literal типы, path-dependent типы и т.п., человеческие трейты, импликиты, макросы, развитая и популярная ФП экосистема… Беттер джава не сильно инетерсует, скорее интересует вариация "раста без борроу чекера, на гц, но с хкт". Просто у меня выбор как раз между ними. С одной стороны не очень популярная скала. С другой — раст, которым не очень легко заниматься в мейнстрим разработке, даже коллеги-растовчане на нетривиальные вопросы предпочитают отвечать в духе "а нафига ты пишешь это на расте?". Хаскель сильно академичный + мне не нравятся многие вещи (вроде специального синтаксиса для filter, странные операторы вроде ++ и т.п.). А больше и нет альтернатив, где можно писать типичные-задачи более удобно и красиво.

A что плохого в функции filter в Haskell?
filter (>5) [1,2,3,4,5,6,7,8]

IMHO, это само совершенство!
это не filter а list comprehension

Ну вот часть x 'mod' 7 == 3 это чисто filter, зачем её выносить в синтаксис если можно было бы просто вызвать как функцию как в вашем примере не сильно понятно.

В самом описании list comprehension в Haskell написано, что это «syntax sugar».

filter (>5) [x | x <- [1..]]
тоже самое что и
[x | x <- [1..], x > 5]
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Я ж надеюсь, Вы не про этот Zombie?..

НЛО прилетело и опубликовало эту надпись здесь

Idris это вообще уже удел оторванных чистых математиков, которые еще и ХМ без ограничений юзают :)


Хотя я, конечно же, могу ошибаться. Для меня это 'Known fact', без какого-то конкретного достоверного источника.

НЛО прилетело и опубликовало эту надпись здесь
Гы, а математики считают

Кек. Ну математики пусть считают, они умеют иногда :)

Со скалой вас без вопросов возьмут в мир BigData (Spark и т.п.). Это та еще потребность рынка.
Ну вот я смотрю на тот же хх и грустно становится. Не, с текущей ситуацией с рублем, скорее всего, нужно уже смотреть на линкедин и аутсорс на запад, но это пока только перспектива. И если на шарпы я могу в течение недели найти пару хороших оферов, то на скалу всего 12 вакансий в принципе было, когда я последний раз смотрел, из которых с котами только половина.
Для начала, включите в поиск Hadoop и Spark, и оцените разницу в числе вакансий. У нас вот де факто Scala == Java, на чем хочешь, на том и пишешь.

Хороший оффер это сколько?

Хороший я больше считаю по вакансии, нежели по деньгам. А то последний год я проработал в подвале, вот там было не очень :D


Если по деньгам, мне кажеся от 3к белыми на сениор позицию это хорошие деньги.

Это в рублях в пределах 180к? Так это совсем не фокус для сеньорских вакансий. Но только реально сеньорских, конечно…
Это 198 600,30 по текущему курсу, как подсказывает гугл.

Это моё личное мнение. Знаю людей, которые больше 250 получают и им мало, но я пока не дорос. Но стремлюсь достаточно активно.
Только скала это наверное наименьшая из экспертиз, которая нужная для работы со спарком. Я на нем успешно писал, не зная ничего ни о скала, ни о JVM языках вообще.
Тут зависимость обратная — нужны spark-разработчики. Человека с экспертизой в скале взяли бы с удовольствием — и не потому, что скала нужна сама по себе, а потому что спарк ему будет достаточно близок.

А то что можно писать скажем на питоне или на R — это понятно.
Спасибо, действительно интересный коммент. Извините, немного не в тему, а с scala.js Вы работали? По-моему вообще самое лучшее что сейчас существует для фронтэнда. К сожалению вынужден был от этого отказаться из-за совершенно безобразной поддержки в IDE. Сейчас работаю на ts + php в рамках единого проекта под vs code. Но если бы была нормальная поддержка, с огромным удовольствием ушел бы с ts на scala.
P.S. Да, на всякий случай ко всем. Если хотите изучить функциональщину, играться со scala будет далеко не лучшей идеей. Слишком многое scala позволяет и Вы ничему не научитесь. Начните с Haskell. Он просто не позволит Вам писать не идеоматично. Сам сейчас именно его потихоньку изучаю.

Со scala.js не работал сам, но на одном из проектов работал с фронтендерами, которые как раз выкидывали всё написанное ими же на scala.js в пользу более джаваскриптовых фреймворков.
Тогда меня фронтенд не интересовал совсем, но эти ребята говорили, что:
а) реальной гетерогенности нет
б) тулинг ужасный
в) готовые фронтендовые либы очень грустно интегрируются
г) и если всё правильно помню, то на выходе у них получался огромный бандл


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

У нас в отделе был такой опыт. Очень любили scala.js, на мой взгляд — незаслуженно. Результаты того не стоят.

>вообще самое лучшее что сейчас существует для фронтэнда.
Не соглашусь. Безобразной поддержки IDE уже было бы достаточно. Впрочем, расскажите, почему вы так думаете?

Мне кажется, что автор неоправданно сравнивает C# с JS, противопоставляя статическую типизацию динамической. JS слабо типизирован, но не все языки с динамической типизацией слабо типизированы. Далеко не во всех языках с динамической типизацией можно к строке прибавить число.


Кроме того, не с проста во многих языках со статической типизацией есть тип any. Даже в C# есть утиная типизация.


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


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

Это я к тому, что вероятно, неверно относить язык к тому или иному виду типизации

Язык либо динамически типизирован, либо статически. Он не может "плавать" где-то посередине. В языке могут быть различные средства для того, чтобы что-то почерпнуть из другого мира, например dynamic в C#. Но от того, что в сишарпе есть dynamic он не стал динамически типизированным.
Еще, например, в С# есть неявное приведение типов. Но оно работает, только если заранее описать, как оно должно работать, оно не работает для всего и всегда. Да и делается обычно это для каких-то совсем очевидных случаев, вроде int -> long.


Между тем, утиная статическая типизация это все равно жесткая типизация, абсолютно не важно, что она не номинальная. Она все равно жестко следит за соблюдением объявленного контракта, и если этот контракт нарушить, то ошибка проявит себя еще до запуска приложения: код не скомпилируется.


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

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

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

Язык: Си, зачем: fast inverse square root =)
НЛО прилетело и опубликовало эту надпись здесь
Да и делается обычно это для каких-то совсем очевидных случаев, вроде int -> long.

Корректней сказать для примитивных типов и при отсутствии потери информации int (4 байта) -> long (8 байт). Обратное преобразование породит ошибку компиляции.

… от того, что в сишарпе есть dynamic он не стал динамически типизированным

Возможно, я что-то упускаю, т.к. не знаком с C#, но разве dynamic не использует DLR? Это же не библиотека, а часть языка.


Язык либо динамически типизирован, либо статически.

Здесь соглашусь. Я имел в виду, что те вещи, которые ассоциируют в основном со статической типизацией, характерны и для многих языков с динамической типизацией. И наоборот. Те же object/any, например.


По поводу статьи:


Статическая типизация не панацея. Например, статическая типизация в Java не помогает отловить NullPointerException.
Вот, чуваки утверждают:


A statically typed language can catch some errors in your program before it even runs, by analyzing the types of your variables and the ways they're being used. A dynamically language can't necessarily do this, but generally you'll be writing unit tests for your code either way (since type errors are a small fraction of all the things that might go wrong in a program); as a result, programmers in dynamic languages rely on their test suites to catch these and all other errors, rather than using a dedicated type-checking compiler.

И действительно, как часто, по вашему, у программистов на языках с сильной динамической типизацией возникает несогласование типов (none/null и т.п. не в счёт)? А в статически типизированных?
И каким образом компилятор статически типизированного языка поможет мне защититься от внешних данных с неверным типом?


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


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

Благодаря гибкости кода в рантайме (см. тот же duck typing) и интроспекции (анализ свойств объектов и кода) получается на порядок проще и быстрее писать универсальные алгоритмы и конструкции вроде декораторов, всяческих ORM и подобных вещей. Это сильно упрощает интерфейсы библиотек, что в совокупности ведёт к более простому коду и к плавной кривой обучения новичков.

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

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

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

И ещё от туда же:


Тип переменных (примитивов) можно и указать, это не сложно. Гораздо сложнее указывать тип объектов. Не всегда известно, с каким объектом придётся работать. В питоне мне достаточно знать, что у объекта имеется некоторый метод или поле (а можно и того не знать). В статическом языке от меня сам компилятор бы не отстал до тех пор, пока я бы не указал тип объекта. Для этого мне пришлось бы следить, чтобы все объекты наследовались от одного базового класса (что не всегда возможно), или мне пришлось бы выделять интерфейс, имплементируя у всех возможных объектов данный интерфейс. Появлялись бы ситуации, где мне пришлось бы выдумывать, как бы сделать так, чтобы явно не указывать тип, но и чтобы компилятор пропустил.

Ну и где мои коллеги на С# пишут всякие dependency injection, visitor, перегружают методы, наворачивают какие-то абстракции, вводят интерфейсы, я со своим питоном просто проверяю, есть ли свойство через hasattr(), или узнаю тип через type(). Так что, сила питона в интроспекции и рефлексии.
Статическая типизация не панацея. Например, статическая типизация в Java не помогает отловить NullPointerException.

Потому что в жабе достаточно слабая типизация. В любом современном языке у вас есть Maybe T/Option<T>/..., где вы явно говорите, что тут может быть нулл, и вы не забудете его проверить, и не будете проверять там, где не надо. Так что компилятор именно что помогает отловить нулл. Более того, в C# 8.0 это ключевая фича новой версии языка.


since type errors are a small fraction of all the things that might go wrong in a program

Практически любую ошибку можно выразить на уровне типов. Это не всегда полезно, и можно упороться тем, чтобы сделать классы IntOne, IntTwo и т.п., описывая все их свойства (такие завтипы для бедных), но в моей практике скорее все наоборот: типы позволяют найти почти все ошибки, кроме совсем дурацких, вроде написали 1 вместо 10. Которые легко чинятся, потому что совершенно попиюще кричат из кода "Я НЕВЕРНОЕ ЗНАЧЕНИЕ" :)


По-поводу первого комментария: человек пришел из С++ в питон и там все было зашибись. Окей, только типы никакого отношения к JIT и дополнительным оптимизациям не имеют отношения, типы это про статический анализ на предмет ошибок. Типы могут эффективно полность заменить все тесты в программе, при должном старании. Хотя выгоднее оставить 10% тестов, чтобы упростить типы, но все равно это позволяет в 10 раз сократить их количество, с одновременным увеличением качества и упрощением дальнейших доработок.


Посмотрите на ORM в каких-нибудь шарпах. Как по мне, во-первых сильно проще их писать (потому что ОРМ сама по себе сложная штука, а все сложные штуки, как я уже выше писал, проще писать с поддержкой от компилятора), во-вторых их проще использовать, потому что компилятор помогает понять, что можно делатЬ, что нет, и т.п.


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


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

Интерфейсы и экзистенциальные типы в помощь.
Зато в динамике легко забыть, какой именно набор полей может быть у сущности, и начать передавать еще один, но не во всех местах, а потом радоваться взлетающим undefined is not a function.


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

Надо имплементировать только тот интерфейс, которым пользуется вызывающий код. То есть тот, которым вы и так неявно пользуетесь, когда надеетесь, что у объекта есть метод .foo(). Только в другом случае вы не надеетесь, а точно знаете, что он будет.


Ну и где мои коллеги на С# пишут всякие dependency injection, visitor, перегружают методы, наворачивают какие-то абстракции, вводят интерфейсы, я со своим питоном просто проверяю, есть ли свойство через hasattr(), или узнаю тип через type(). Так что, сила питона в интроспекции и рефлексии.

В C# тоже так можно, только так не делают. И не потому, что медленно, а потому что ненадежно. Не говоря про то, что визитор ортогонален hasattr'у.

пороться тем, чтобы сделать классы IntOne, IntTwo и т.п

Это, кстати, вполне себе type refinement. Кое где есть.

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

Никто не говорит, что это панацея. Но целый пласт ошибок убирает.

как часто, по вашему, у программистов на языках с сильной динамической типизацией возникает несогласование типов (none/null и т.п. не в счёт)?

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

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

Да люди не знают. что в нормальном проекте даже 500 файлов перелопатить (например, у меня такое было с миграции с монго драйвера версии 1.0 на 2.0) не вызывает проблем в статическом языке, а в динамике обычно дернуться боишьсЯ, чтобы где-то не взорвалось, файлики меняются аккуратно, по одному, и желательно с фоллбеком. Поменять треть проекта так, чтобы сохранить работоспособность, это для людей совершенно непонятно :)

Когда-нибудь в браузерах у JS отрастет родная типизация и евангелисты расскажут нам про очередную революцию, которая изменила мир веб-программирования. Конечно, если раньше сюда не заползет PHP-рантайм via WASM.
НЛО прилетело и опубликовало эту надпись здесь
В этом случае статическая типизация вам только ошибки компиляции покажет

Это и есть баги типизации, которые без этого этапа останутся незамеченными и начнут всплывать гораздо позже (+ к временным затратам на стабилизацию), а то еще и «дрейфовать» в случайные стороны, если типизация будет не только динамической, но и слабой.

Это и погрепать можно

Нельзя. Греп не видит контекста. Да и вы это в 2к18 руками будете делать?!

Но, хорошее тестовое покрытие считаю более важным, чем наличие/отсутствие типизации.

Более/не более — это софизм. Зачем теплое с мягким сравнивать? Если будет и то, и другое — это только лучше.
НЛО прилетело и опубликовало эту надпись здесь
а одних тестов — достаточно.

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

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

Статическая типизация — это тесты, которые уже написаны за вас и запускаются при каждом изменении. Это взаимодополняющая штука с юнит-тестами.
НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

С JIT в теории скриптовые языки должны показывать то же быстродействие, что и компилируемые
К сожалению на практике все далеко не так. Даже лучшие представители JIT компилируемых языков вроде Java и C# существенно проигрывают плюсам в сколько нибудь серьезной вычислительной задаче. Всякие питоны и яваскрипты отстают вообще катастрофически. Гонять HTTP запросики по сети — тут уже может быть пофиг будет. И то, главное, чтобы еще время ответа было приемлемым, что в языках со сборкой мусора часто проблема.
Java и C# конкурируют с С++ в сегменте компилируемых языков. А скриптовые конкурируют прежде всего с Java и C#.
Только конкуренции особо нет ни у тех, ни у других. Отставание в скорости вычислительных задач очень большое. Go не случайно начал перетягивать на себя людей с питона, руби, ноды и прочего. Он дает преимущества обоих лагерей.
При этом по скорости он очень сильно отстаёт от C/C++. Потому, удивительным образом, люди для которых он был изначально сделан, остаются на C/C++.

А вот люди, неготовые работать с языками, в которых для каждого случая припасён новый, свежий, крупнокалиберный ногострел — на Go переходят с удовольствием.
> Даже лучшие представители JIT компилируемых языков вроде Java и C# существенно проигрывают плюсам в сколько нибудь серьезной вычислительной задаче.

На каких именно задачах и за счёт чего конкретно?

JIT дает задержку при, собственно, компиляции (и этого никто не скрывает). А в скомпилированном виде за счет чего проигрыш? Нет поддержки каких-то SSE или что?

Обычно в тестах любят включить оптимизации для плюсов и написать «в лоб» что-нибудь на C#. Причем, взяв задачку, где GC должен сдохнуть. И закончить сравнение «вот так пила сказала хр-р-р, как и предполагали суровые сибирские мужики».

А вот с тестами без издевательств над JIT/GC как-то сложнее (мне таких не попадалось).

Но наличие GC компилирумости языков то ортоганально.
Честно, просто зашел на Computer Language Benchmarks Game. Да и как-то это уже настолько очевидная вещь, подкрепленная годами практики, что нигде даже не пытаются опровергать, что плюсы все равно быстрее.

Почему оно так получается, я думаю это задачка не из простых. На ум приходят всякие мысли о райнтайм проверках, GC, который в фоне постоянно работает параллельно, всяческие другие вставки JIT компилятора, которые делают пользовательский код более контролируемым. Ведь тому же GC надо как-то останавливать все потоки безопасно, нужные safe point'ы. При любом изменении ссылки нужно вставить write barrier, чтобы GC мог свои структуры обновить. И т.д. и т.п.

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

Мне показалось у вас спор «С JIT в теории скриптовые языки должны показывать то же быстродействие, что и компилируемые», а вы говорите «GC дает оверхед».

Да, дает, но это же разные тезисы. Можно, например, заняться ручным выделением памяти и выкинуть GC за скобки (что тот же Computer Language Benchmarks Game запрещает прямым текстом).

Да и вообще, GC — это про создание миллионов объектов, а не про вычислительные задачи. И относительный оверхед на компиляцию падает со сложностью задачи.

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

Еще можно взглянуть конкретно на n-body бенчмарк в рамках java и C++. Код практически идентичный, но разница в скорости огромная:
1. GC вроде мешать сильно не должен, выделений памяти нет.
2. Сразу бросается в глаза наличие методов. У С++ это будет обычный джамп, а для Java еще один indirection уровень из-за vtable. На 50000000 итерациях это может вполне сказаться.
3. Тоже самое с полями. У Java это ссылки на объекты. У С++ это std::array, что тоже исключается уровень indirection
4. Ну и самое явное это явное использование векторных инструкций. Догадалась ли Java их использовать большой вопрос.

В общем, наверняка можно попробовать сделать такой пример, который полностью исключит и GC, и специфику управляемых языков, где добавляются дополнительные уровни indirection, чтобы все зависело только от компиляторов. Но на сколько-нибудь реальной задаче это все равно представляет лишь академический интерес.
4) Ну да, магия то оказывается не в компиляторе. Для получения выделяющихся результатов нужна тупо готовая библиотека с векторами.
Ответ простой — время компиляции. Вопрос — сколько в среднем gcc компилирует один метод? Так вот у Ryujit красный предел — 50мс. А плюсы могут часами ворочить, пока не заоптимизируют в хлам, особенно с включенным LTO.
НЛО прилетело и опубликовало эту надпись здесь

Ну, если «канонической джавой» называть код школьника, который никто не пытался оптимизировать, а каноническими плюсами — «SSE-интринсики руками», то да.

А в питоне не компилятор заругается, насколько я знаю, а синтаксический анализатор.
Синтаксический анализатор, который читает текстовый файл и превращает его в бинарный байткод? Который потом даже можно сложить в файл?

Извините, но это компилятор. И очень многие скриптовые языки так устроены: emacs lisp, perl, python и т.д.

Но с тем же успехом можно научить Linux использовать Go как скриптовый язык. Тут нет никакой магии и никакой разницы: скриптовые языки отличаются от не скриптовых тем, что у них есть «однопроходный запускач», которым пользуются люди. Всё.

С JIT в теории скриптовые языки должны показывать то же быстродействие, что и компилируемые
Это дурацкая теория, которая не подтверждается практикой. И никакого отношения к скриптовым языкам не имеет.
Все таки отличий несколько больше. Скриптовые языки конечно компилируются обычно в байткод, но дальше идет интерпретатор или JIT. При этом это еще является частью рантайма самого языка, так что можно код генерировать и исполнять на лету. Сделать подобное на С++ теоретически возможно, но это будет целый подвиг. Это уже не говоря о самом опыте работы. Компилируемые языки обычно делают это медленно и четко делят фазы на компиляцию и запуск. В скриптовых это все склеено и скрыто в один этап — запуск интерпретатора, что в корне меняет опыт работы с ними. По той же причине в упомянутом Go с самого началась боролись за скорость компиляции, чтобы команда `go run` ощущалась как запуск скрипта какого-нить питона.
Синтаксический анализатор читает, грубо говоря, исходный код и создаёт абстрактное синтаксическое дерево или какой-то его аналог, которое потом компилируется или интерпретируется.
Чем «синтаксический анализатор» какого-нибудь питона, порождающий байткод в .pyc файле отличатеся от «компилятора Java», порождающего байткод в .class файлах?

Только тем, что питон вызывает свой компилятор (а это — полноценный компилятор, без каких-либо оговорок) неясно, а JavaC обычно (но не всегда) вызывается явно.

Но он может и неявно вызываться «в фоновом режиме». JSP, к примеру.
Отсутствием оптимизации, например.
Ну это уже пошло «расщепление волос». Типа Turbo Pascal 1.0 — это у нас синтаксический анализатор, а вот FreePascal — это уже да, это уже компилятор.
Но целый пласт ошибок убирает.
в проектах совсем мало ошибок, которые уберёт статическая типизация
отсюда.
От того, что вы повторите эту цитату еще несколько раз, правдой она не станет.
Судя по тому, что исходный коментарий чуть ли не самый заплюсованный — люди с ним согласны.
Статическая типизация ценна не из-за «ошибок», а совсем по другим причинам.
Она ценна по многим причинам, в том числе и из-за ошибок. А плюсы — да хоть их там 200 было бы, погоды это не делает и правее мысль не делает. Одна из основных причин почему typescript был вообще создан это возможность комфортно рефакторить большие проекты. Без статической типизации это можно сказать невозможно. Так что комментарий тут здесь мимо. Если комментатору статическая типизация не помогала избегать ошибок, то, видимо, он не сталкивался с тем, ради чего в javascript и пытаются засунуть статическую типизацию.
почему typescript был вообще создан это возможность комфортно рефакторить большие проекты
Вот, это и есть одна из основных причин.
Но рефакторить вам помогает «тулинг»/IDE и «тайп-хинтинг». Является ли «тайп-хинтинг» статической типизацией? Явяется ли TypeScript статический типизированныйм? На мой взгляд он примерно такой же «типизированный» как и Python.
Он такой же типизированный как питон, если повсюду использовать any. Типы есть, но как бы и нет. Ключевая идея это знание типа каждой переменной, что дает валидацию на этапе компиляции всей цепочки вызовов. Проблемы начинаются от того, что typescript внутри все тот же динамический типизированный и автоматический вывод типов на этапе компиляции делать не умеет. gradual typing, все тела. Но если у всех функций и переменных четко определять их тип, то получится таже самая статическая типизация. Конечно, можно извращаться всячески и наставить себе костылей, но тоже самое можно и в С++ каком-нить натворить, где вообще типы исчезают после компиляции, а указателями можно слона в стул превратить.
Комментарий с бестолковыми сравнениями теплого с мягким. Статическая типизация защищает от ошибок с типами, а NRE и вообще возможность писать плохокод к ним не относятся. Ошибка компиляции для эквивалента на динамическом языке означала бы скрытый баг, который всплывет только в процессе тестирования.

А вот другой комментарий с близким количеством плюсов.
Из последнего абзаца получается интересный вывод:

Если только начинаете изучать разработку, начинайте со статической типизации

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

Зависит от того, как эти типы использовать.


Если создать они огромный byte[] Memory и дальше с ним по указателям работать, то толку будет мало. А можно сделать тип IsOddNumberLargerThanTen и во время компиляции получить ошибку, при попытке проверить, является ли число простым.

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

Если послушать аргументы сторонников строгой типизации, в них звучит одна эмоция — страх. Страх допустить ошибку. Страх, что код станет неуправляемым — со временем или при использовании другими людьми. У автора:
«Компилятор меня проверил, я все сделал правильно», «IDE… проверяет на ошибки». «JS-ник, берет этот мой тип… и начинает использовать его неправильно»

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

Но страх — очень сильная эмоция. Кто-то с ним (и заодно со сложностью) справляется лучше, а у кого-то мозг просто устроен по-другому. И в дело вступает любимая психологическая защита людей с высоким интеллектом — рационализация.
Честно говоря про страх подумал только после Ваших слов. Да, и это имеет место быть. Хотя я бы называл это немного по-другому — защита от совсем уж глупых ошибок. Но для меня лично главное пожалуй это хорошая поддержка IDE. Автокомплит, показ структуры и т.п. Возможно крутые перцы тут меня засмеют, мол православный и духоскрепный прогер обязан уметь писать код хоть в notepad-е, но моя практика показывает, что писать код для современных окружений (ОС, броузер и т.п.) можно только с хорошей поддержкой IDE. Иначе слишком много деталей приходится держать в голове и процесс превращается в мучение. Для динамических же языков поддержка IDE увы, но просто не может быть хорошей.
Мне кажется, необходимоcть IDE для работы с современным окружением говорит о том, что это окружение уже перешло порог сложности, доступный человеку.

Требуются абстракции следующего уровня, и в окружении, и в языках.

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

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


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

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

Нежелание — не эмоция. Это реакция на эмоцию.
Допустим вы правы (я не согласен, но в качестве гипотезы примем). Что дальше? Человек, в страхе совершить ошибку, ошибается еще больше? Отнюдь. Он стремиться ошибаться меньше (что у него неплохо получается), от чего он только в выигрыше. Нормально бояться ходить без страховки на 50 этаже просто по приколу. Нормально бояться залезать в пасть тигру, который неделю не ел. И так далее.
Человек, в страхе совершить ошибку, ошибается еще больше?

Строго говоря да. Человек в страхе более подвержен ошибкам мышления.

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

Этот демагогический прием называется "чтение в сердцах". Если вы откроете поисковик и напишете "why static typing" вы легко найдете другие доводы за. И почему это может быть не эмоция, а расчет?


«Компилятор меня проверил, я все сделал правильно»

А так же "IDE мне подсказывает" — уже не только про ошибки.


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

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


Когда вы говорите "не спасает от ошибок" вы имеете ввиду не спасает от всех ошибок или нет вообще никаких ошибок, от которых он спасает?


И в дело вступает любимая психологическая защита

И опять "чтение в сердцах"

Почитал ваш ответ, понял, что зря вообще начинал отвечать данному товарищу.
Если вы откроете поисковик и напишете «why static typing» вы легко найдете другие доводы за.
Именно так работает когнитивное искажение предвзятость подтверждения.

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

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

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

Привести примеры взаимодействия?
Отвалившееся соединение с базой данных. Ответ от API с некорректными данными в неожиданном формате. Пользователь сделал что-то не то.

Надеюсь, так понятней.

Если вопрос был в том, спасает ли компилятор вообще от каких-то ошибок, то… Риторические вопросы ж не требуют ответа?
Если вопрос был в том, спасает ли компилятор вообще от каких-то ошибок, то… Риторические вопросы ж не требуют ответа?

А вопрос был не риторическим и ответа требовал.

Именно так работает когнитивное искажение предвзятость подтверждения.

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


В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами.

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

Я могу ответить не на вопрос «как типы помогают находить ошибки в логике.»
а на вопрос «как типы помогают не допустить лишних ошибок в логике».
Если у меня UnityType { Pawn, King,… etc } это ТипФигуры в шахматах (или другой игре не суть) и я передаю его в нужный метод, как аргумент или возвращаю, то я уверен что тут ожидается оно, и есть смысл в этом. А если я обозначу int (или вобще без типа не суть) я буду лишнее время тратить чтобы найти где оно передается, что туда передается, и почему, и как обрабатывать, если я пишу новое, или ВАЖНЕЙ — РЕДАКТИРУЮ, я ЧИТАЮ код, и вижу ограничения. Они придуманы как раз чтобы в голове держать ТипыФигуры, а не «незнамо что». А когда enum таких становится десятки, то перепутать чтото компилятор уже поможет. И даже без компилятора, find usages.
Тоже самое касается классов, в которых есть семантика, я знаю что ожидается класс который будет делать тото, да хоть делегат / интерфейс с функцией, у которой строгое число и смысл аргументов, а не function (a, b) с неясным смыслом аргументов. Это даже к вопросу косвенному недавно вон написали «уже не нужны паттерны, если можно передать указатель на функцию» — да можно было еще с Си передать указатели, смысл в семантике. И чтении и поиске при разработке.
А НЕ write only Код потом write only Тесты.

**
«статическая типизация это 10%, а тесты это 90%.»
по этому — т.е вы предлагаете написать огромное кол-во тестов, которые надо будет тысячу раз перечитывать (а разработка требует такого, что функционал меняется часто, и во многих местах, я уже не помню как оно работает), вместо нормального использования типов? Вот мне тесты читать не надо чтоб понять что тут ожидается UnitType или MaterialType и т.п., и как оно себя поведет и какие в нем ограничения.
Может даже тестов еще и нет, их над написать. а функционал в разработке, и меняется, толку тесты писать еще не было, не дошло, бывает? Часто. Тут только типы и помогут. Вот потом вы тесты и напишете. Или вобще выкините функционал, т.к. над по-другому реализовать.
И да у меня даже страха нет, когда тестов нет, или вообще нет, или еще нет, не суть, а вот типы уже есть, и уже видны семантические ограничения.
Если речь про семантику и чтение, то без разницы динамическая типизация или статическая
НЛО прилетело и опубликовало эту надпись здесь
Это большое заблуждение, что если пишут на динамическом языке, то проверяют типы в тестах. У нас тысячи тестов и ни один не проверяет тип.

Так это ж еще хуже.

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

И как же с тестами вы проверяете? Сначала надо спроектировать «как оно должно работать» — это вопрос подумать головой, и в этом как раз статическая типизация поможет, потому что явно видно что куда передается, где find usages, где-то срефакторить где было не-красиво и багоопасно из-за запутанных конструкций и только что введенного нового функционала.
Ну и как вы сможете тесты написать на код которого нет — тот еще вопрос… особенно если это очень исследовательская правка, которую можно реализовать кучей способов, а вы пишете сразу тест, который фиксирует API и возможно реализацию связанную с ним, иначе вы не сможете определить даже типы параметров и что с чем взаимодействует, вы всеравно сначала спроектируете, хоть что-то типизированное, в духе «есть объект Factory у него параметр UnitType и он выдает Unit с заданным UnitType» это уже интерфейс и вон сколько в нем типов, и это прсотейший пример, как вы тесты напишете не спроектировав — не представляю. Тем более не представляю если у вас типов нет в синтаксисе, как вы будете писать тесты как доменную область, не зная что в этой доменной области (бизнес-логике) за объекты. И как вы будете рефакторить, если у вас доменные объекты не ищутся по find usages. А если ищутся — так это уже нормальная статическая типизация.

А да — если воппрос только «как проверить когда спроектировали» — я не говорил ни разу что проверять не надо. Я говорю только о том что в языке со статической типизацией, явно определнные типы — помогут вам спроектировать. И помогут написать тесты (которые — не обязаны быть написаны до пректирования).

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

И где же в моем утверждении — хоть намек на проверку типу в тестах?
Вот цитата от моего же сообщения
Вот мне тесты читать не надо чтоб понять что тут ожидается UnitType или MaterialType и т.п., и как оно себя поведет и какие в нем ограничения.

Я сообщаю о том что у меня есть UnitType и MaterialType — это типы, они определны явно, да пусть и enum в простом случае (вместо констант 1-2-3 в разных множествах что еще перепутать можно, но я не говорю однозначно что вы используете константы, мало ли способов). И если они явно опеределены как enum, то я могу посмотреть на код и без тестов увидеть что эти аргументы являются «типом юнита» а в случае int в кривой реализации без типов или «нетипизированного unitType аргумента в function getA(unitType) в JS» что тоже самое (! абсолютно) я не могу быть уверен что передается UnitType и что он может быть только UnitType.Pawn или UnitType.King но не 0 и не строка и не фиг-знает-что-еще. Ну а в случае JS с не явной типизацией которая в аргументе не читается, еще и тесты надо писать, но как вы выразились — вы их даже и не пишете (я про проверку типов), ну значит еще больше не можете быть уверены что ваш код работает, если только вы не провели тесты на абсолютно все комбинации (декартово множество — ререал)
У меня такого не бывает. Сначала тесты, потом код. Ну запроектировали вы типы, все у вас стыкуется, а как проверить, что оно работает? Я имею ввиду бизнес логику?

Если компилируется, то есть очень неплохой шанс, что в логике проблем нет. Точнее он прямо пропорционален тому, насколько полно вы используете типы. В пределе скомпилировалось == вся бизнес-логика корректна.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Нету такого в мэинстрим языках

Почему вы думаете, что в этой ветке обсуждают мейнстрим языки?

Нету такого в мэинстрим языках

Уже есть. Typescript, Kotlin, Rust, еще в C# 8.0 обещают...


Или для вас мейнстрим-языки — это только Си, С++, Java и Javascript?

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
1. Очень-очень спорно. Иной раз залезаешь в биндинги TS и видишь на поэкрана только описание тип аодного аргумента какой-нить стандартной функции.

2. TDD позволяет ровно то же

3. Ведёт к тому же

4. Аналогично в какой-то мере.
Иной раз залезаешь в биндинги TS и видишь на поэкрана только описание тип аодного аргумента какой-нить стандартной функции.

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


Ведёт к тому же

Наоборот. Часто требование простоты и удобства тестирования строго противоречит качеству архитектуры.

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

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

Так они не заменяют. Если проект без документации но с типами, то без типов он был бы и без документации и без типов.


Можно пример?

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

Заменяют. Даже по истории коммитов можно отследить момент замены.

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

А примеры можно?


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

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

Примерно так:


const two = new VeryTestableNumber( 2 , new VeryTestableNumberTextSerializer , new VeryTestableNumbeJSONSerializer )

const algebra = new VeryTestableAlgebra( VeryTestableNumber , new VeryTestableMultiplicator( VeryTestableNumber ) , new VeryTestableDivider( VeryTestableNumber ) , new VeryTestableSummator( VeryTestableNumber ) , new VeryTestableSubtractor( VeryTestableNumber ) )

const calc = new VeryTestableCalculation( ()=> algebra.multiply( four , four ) , new VeryTestableErrorStrategy( new VeryTestableLogger ) )

const four = calc.execute().result

И это я ещё упростил..

Не похоже это на легко тестируемую архитектуру.

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

1. Очень-очень спорно. Иной раз залезаешь в биндинги TS и видишь на поэкрана только описание тип аодного аргумента какой-нить стандартной функции.

Думаю, что это для документации, которая отображается при альт-ентер в IDE
Думаю, что это IDE использует в качестве документации биндинги TS, созданные сообществом, для контроля типов при компиляции TS.
«Компилируется — значит работает правильно» звучит в 2018 году смешно

Как по мне, все наоборот. Сейчас именно то время, когда я все больше замечаю — скомпилировалось и работает нормально с первого раза. Это раньше нужно было дебаггером десять раз пройтись, чтобы выловить какое-то хитрое непонятное поведение.
НЛО прилетело и опубликовало эту надпись здесь
Требования всегда не полные, в конце проекта всегда нужно внести изменения, 3rd party api за день до нашего релиза выпускают новые версии несовместимые с тем на чем мы разрабатывали, ни и вы сами все про это знаете.

Так вы как раз описали ситуацию, когда Type Driven Developmen раскрывается во всей красе. Когда требования меняются, надо исправлять, рефакторить, фиксить под новый апи и т.д.
Вот когда вы раз написали — и все, тогда, да, можно и динамикой обойтись.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Очень неплохой шанс отхватит NPE, ошибку ±1, неправильное направление сравнения, ошибку в логичеком условии и т.д.
Типы никак с этим не помогают.

Очень даже помогают
Но конечно от C# разработчика слышать, что «если билдится значит работает» очень странно.

Я не C# разработчик, я просто программист :) На текущем проекте у меня питон/C#/Solidity/Rust в пропорциях примерно 0.1/0.4/0.4/0.1, а в любом другом может быть любая другая комбинация. Учитывая, что язык хуже солидити придумать трудно, я без труда пишу на практически любом распространенном.

В C# никаких подпорок под это нету (я знаю что в 8ой версии появились not null типы, но это все-таки будущее, а не настоящее).

C# не вершина эволюции. Он примерно в середине множества типов. Не такой классный, как rust/haskell, но сильно мощнее динамики. Да блин, даже скриптовать в linqpad сильно удобнее, чем в webstorm под ноду.

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

Когда люди поймут, что тесты не заменяют типы? Они могут их в лучшем случае дополнить. Вы никак не можете проверить ВСЕ входные данные. Как-то с этим бороться пытаются тулзы вроде QuickCheck, но рандомные входные данные сильно хуже, чем математически доказанная корректность для любых ситуаций.

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

Если мои тесты прошли, я уверен только в том, что в ожидаемых случаях поведение совпадает со спекой :) И то если покрытие достаточно хорошее.

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

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

Типы помогают в фиксации тех инвариантов, которые вы хотите зафиксировать.

int sum(int a, int b) { return a-b; }
Скомпилировалось, но делает не то что нужно. Как вам типы тут помогут?
НЛО прилетело и опубликовало эту надпись здесь
Это не имеет значения в данном случае — это не поможет компиллятору.
Ну почему же. Если наши числа беззнаковые, то можно статически гарантировать, что в штатной ситуации (т.е. без переполнения) их сумма будет не меньше, чем каждое из них. Самый очевидный пример, специалист наверняка подберёт и больше.
Это очевидные вещи из математики, которые не относятся к примеру выше.
Так о том и речь, насколько я понял, что за счёт использования достаточно мощной системы типов эти самые «очевидные вещи» могут быть выражены в описании функции. Если действие противоречит описанию — функция не скомпилируется.
НЛО прилетело и опубликовало эту надпись здесь
Вы формализовали свое требование только названием функции. Компилятору невдомёк, что ожидалось что-то другое.

Благо, для выражения семантики суммирования есть специальное синтаксическое средство — оператор сложения ("+"). При его использовании компилятор обязательно проверит, что эта операция определена для типов аргументов.
Благо, для выражения семантики суммирования есть специальное синтаксическое средство — оператор сложения ("+")
Ну так и «минус» там тоже есть, в итоге ошибка не куда не исчезает.
Вы формализовали свое требование только названием функции. Компилятору невдомёк, что ожидалось что-то другое.
Приведите пример как должно быть, чтобы компиллятору было «вдомёк».
НЛО прилетело и опубликовало эту надпись здесь
и в типах, скажем, написано `∀x sub(x, x) = 0`
Это же просто тест, вы его прямо с кодом смешиваете? И при чем тут типы? точно так же можно тестировать и в динамической типизации.
НЛО прилетело и опубликовало эту надпись здесь
Это не просто тест
И все же тест.
Да. Более того, я его не запускаю.
В динамическом языке его тоже можно использовать только во время «сборки», а не в рантайме.
Нет, нельзя
Т.к. это тест, то это будет не проблема, просто «формат» будет другой.

∀x sub(x, x) = 0
А на чем этот кусок кода можно запустить?
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Это не страх, это Уверенность. Когда у вас статическая типизация вы просто уверены
Хорошо подметили. Похоже, сторонникам статической типизации действительно для уверенности требуется статическая типизация.

Плюс получаем поддержку IDE для рефакторинга.
Рефакторинг в IDE есть и для динамических языков. И даже без IDE есть
К слову, первый язык (и один из немногих), в котором код можно рефакторить самим же кодом, как раз динамический. Я про Лисп, конечно.
Лисп- это язык, где надо в экспрешнах все описывать.

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

БТВ рефакторингов в лиспе никаких не было, потому что первой IDE был борландовский паскаль.
Вероятно, ваше понимание рефакторинга расходится с общепринятым.
Википедия: «рефакторинг означает изменение исходного кода программы без изменения его внешнего поведения». При чем тут IDE?

Википедия: «Средства автоматизации рефакторинга. Технические критерии для инструментов рефакторинга: деревья синтаксического разбора».

Обьяснить, почему Лисп, или этого достаточно?
Объясните, пожалуйста.
Для программной обработки кода используются структуры абстрактное синтаксическое дерево (AST) или дерево синтаксического разбора.

Особенность Лиспа (и некоторых других языков) в том, что в нем структура AST полностью идентична структуре кода и строится из примитивных типов языка, что позволяет анализировать и модифицировать ее средствами самого языка. Звучит наверное сложновато, но тут есть наглядные примеры

а рефакторинг тут при чем?

первой IDE был борландовский паскаль
И опять вы заблуждаетесь
Что значит «опять»? Это первый случай, и я признаю, что ошибался.
> Рефакторинг в IDE есть и для динамических языков

Но он почему-то не вызывает желания поделиться этой радостью в комментариях.
То, что я видел, хотелось «обнять и плакать»
Да, я тоже в первый раз, когда попробовал написать что-то на QML малость прифигел.
До сих пор интересует вопрос, какому гению пришла в голову идея в качестве базы для QML выбрать JS? И это во фреймворке на C++, где типизацией пронизано буквально всё, а в QML её особо нет, даже если захочется. Порог вхождения во фреймворк по этому получился просто астрономическим. Нужно не только уметь мыслить «по ООП-шному», но еще и по JS-ному. При чем и то и другое нужно уметь делать одинаково хорошо…
Вообще, дело же в первую очередь в мышлении, в привычках. Совершенно не вижу причин для негодования или пренебрежительного отношения к тем, кто пишет на динамически типизированных языках.
Огромная часть frontend'а — это landing pages и небольшие сайты, например, портфолио или промо. Можно, конечно, спорить frontend-программист ли вообще человек, встраивающий slider-widget на сайт, но так называются вакансии и позиции, на которых они работают. Годами. Суть же в том, что для выполнения этой работы, которой много, типизация просто-напросто избыточна, читай не окупает себя.
Как только задача перестает быть тривиальной, она начинает требовать декомпозиции. И как следствие для ее решения выстраивается архитектура и подбираются паттерны. Да, трудно спорить о том, что ООП может поспособствовать развитию системного мышления, это то, что Вы описывали: системные объекты и их взаимодействия, но это вовсе не обязательно. Более того, не всегда опыт и владение языком со статической типизацией ведут к тому, что у человека появляются способности к системному мышлению и проектированию. Ровно как и то, что человек пишет, скажем, на JavaScript, не лишает его этих способностей.
Кстати, качественное тестирование помогает решить не проблему TypeError, а logical errors.
В любом случае, судя по Вами же описанному случаю, у Вас проблема заключалась не в разных языковых предпочтениях, а в том, что Ваш друг и коллега отказывался от этапа проектирования. Совсем ли он отказывался или настаивал на поэтапном проектировании, призывая выбрать agile-подход, не понятно, а может быть он вообще хотел сначала сделать прототип (как частенько бывает во frontend), но суть вашей проблемы именно в этом.
А к написанию этой статьи Вас подвигло то, что вы распространили отдельно взятый частный случай, когда, пишущий на динамически типизированном языке, отказался от проектирования, на всех программистов использующих такие языки.
Не все такие.
staticTypes !== projectDisign && dynamicTypes !== noProjectDisign

Вы знаете, кажется, что Вы раздули из мухи слона. К сожалению, проблемы проектирования и взаимопонимания (а может и лидерства, кто вас знает, может подоплекой разногласий было выяснение того, кто из вас у руля и принимает окончательное решение) решаются не через изучение статически типизированных языков, а только через улучшение владением естественными языками, те самые пресловутые soft skills.
<Мой код отвечает на вопрос «Как с этим работать», а как код разрабов, привыкших к динамической типизации — «Как это работает». Оба подхода имеют право на жизнь, и есть инструменты для обоих, но только один может стоять во главе угла.>

Но это не все типизация в языке может вообще отсутствовать.

<Статическая хороша для больших проектов, которые годами делают сотни разрабов, а динамическая — для маленьких команд, для задач, где часто нужен write-only код. С динамической ты экономишь время и силы в начале разработки, а со статической — в конце.>

Сомнительное утверждение. Связь не просматривается.
<Идея ставить во главу типы серьезно повлияла на мое мышление разработчика.
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю. Увидев любую задачу, я пытаюсь представить ее решение как набор типов и принципов их взаимодействия. Когда я разрабатываю модуль, первым делом определяю, какими типами он оперирует и какими взаимодействует со своим окружением. Я даже не помню, как я подходил к решению задач раньше. >

В этом вся соль. Проектирование приложения отталкиваясь от данных. Абсолютно с вами согласен в этом. Только типизация тут вообще не причем. Данные могут быть спроектированы любыми способами способами, хоть в комментариях. Типы только один из способов их спроектировать, и возможно не самый лучший. То что вы к этому пришли через типы, ну прекрасно. Утверждать что проектирование данных вытекает только из статической типизации я бы не стал. Это все таки разные категории.
Кстати плюсую. Проектированием в комментариях я занимаюст тоже, например обозначить что «если тут null — значит рендерить не нужно» семантика, которую иначе очень сложно выразить. Аналогично про ед измерения вроде это килограммы а не граммы, или миллисекунды а не секунды.

Но проблема бывает когда не только типы не используются, и комментариев и других ограничителей тоже нет, и попробуйте догадаться что там за единица измерения (называть всегда millis суффикс если это милисекунды — не факт что удобно, а еще бывает и сложней)
Можно часть проблем перенести на пользователя. Ввел килограммы вместо грамм. Увидел результат удивился и исправил.
<Поэтому я не хочу искать компромиссов, а говорю прямо. Если только начинаете изучать разработку, начинайте со статической типизации.>

А еще лучше вообще забудьте о типизации как о дурном сне.
Но с этим категорически не согласен. Текущий мой проект невозможно было бы без типов построить. Там и комментарии есть. И тесты частично. Но суть в семантике. Ну забудьте — и будете тратить в разы больше времени на отладку и написание тестов и потом всеравно будут баги и потом по новой тесты, на то, что было бы легче ограничить «ожидается MaterialType а не произвольный int»
Я не могу судить о вашем проекте. Но я не понимаю как типизация кардинально влияет на создание проекта. Нет типов нет и багов с ними связанными. Речь естественно идет только о базовых типах. Я типизацию только в таком контексте и рассматриваю. В безтиповом языке нет ошибок неправильного применения типа. Любая переменная триединна. Она и целое и действительное и строка одновременно. Ошибка типа заменяется преобразованием к соответствующему типу. Если надо то проверяешь значение всеми доступными средствами, но это касается в основном внешних данных.
Я не знаю кто рассматривает языки с динамической типизацией так что «Речь естественно идет только о базовых типах». Разумеется я подразумеваю все пользовательские типы, доменную модель приложения.
Но как раз в том же JS (без keyword class из нового стандарта и без TypeScript) там черт ногу сломит даже с пользовательскими типами
В общем случае все вычисления проводятся с базовыми типами, там и возникают проблемы. Против пользовательских типов я ничего не имею против, даже за. Сторонники статической типизации возлагают на нее надежды по борьбе с багами. Проблема багов значительно шире и эффект от применения статической типизации явно преувеличен. Хотя проблема багов одна из самых значительных в программировании, независимо от типизации. Для борьбы с ней наиболее эффективно лишить программиста инструментов управления памятью. Управлять памятью должен полностью компилятор. Динамические языки ближе к решению данной проблемы и поэтому более эффективны.
> эффект от применения статической типизации явно преувеличен.

Особенно если сравнивающие под «статической типизацией» имеют в виду «строгая статическая типизация с гибкой и мощной системой типов», а не «слабая статическая с примитивной системой типов». А под динамической «слабая динамическая типизация с примитивной системой типов», а не «строгая динамическая типизация с гибкой и мощной системой типов».

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

Я как сторонник возлагаю надежды, и даже сообщаю свой опыт, и я писал и на JS и на PHP (уж не говорю это «правильная» там динамика или нет), и вижу что проще не допускать баги в Java или C# и даже в C++, с его управлением памятью, и даже в Си с его старой древней библиотекой, я на всем этом попробовал писать проекты с использованием нормальных доменных моделей, пусть и не из миллиона строк, но опыт у меня есть.

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

Зависит от проекта. Для подавляющего большинства простых проектов, и для большинства средних/сложных — наверное да, управление памятью в духе сборки мусора — вне ведома программиста, это хорошее решение, в сложных проектах где к тому же нужна оптимизация — лучше позволять умные ссылки (как в С++) и какие-то новые разработанные языки в духе Rust/go и что-там-еще. Но управление памятью вводит баги толькоо пределенных классов вроде «утечка (не удаление)» или «двойное удаление (краш)» или «переполнение массива/буфера (уязвимость без проверки лимитов)», но они и направлены чтобы определенные вещи оптимизировать (те же аллокации не делать), так что не все баги они вводят, я бы сказал что доменные баги в духе «в эту функцию(метод класса) передал аргумент и забыл инвариант, а аргумент оказался null (простой случай) или невалидный (меньше 0)» и тут динамика никак не поможет а навредит т.к. инварианты видны хуже (и типы, и без комментариев особенно если, а полагаться на «да итак сойдет» как многие пишут на динамике, фиг разберешь что творится в коде, без тестов (всмысле без тестов не разбрешь, но они могут быть), которые еще и читать замучаешься тоже, когда надо в доменной области разобраться)
Есть подозрение, что вы не только из-за типизации не поняли друг друга. Есть два метода разработки — нисходящий и восходящий. Вы — приверженец восходящего подхода. Это когда сначала пишутся все функции, методы и прочие взаимодействия составляющих «кирпичиков», а потом из них собирается проект. А ваш друг — приверженец нисходящего. То есть ставится задача, которая постепенно дробится на все более мелкие части. То есть пишем функцию «MakeBest()», в ней пишем вызов «MakeGood()», и только потом создаем эту самую MakeGood(), прописывая что именно она должна сделать.

Ваш подход позволяет реализовать более продуманную в техническом плане задачу, но требует держать в голове весь проект сразу. Подход вашего друга позволяет десятку джуниоров работать над проектом, вообще не представляя, зачем это все пишется. Как результат — получается, что функция на верхнем уровне выполняется год с небольшим и теряет память, потому что один из джуниоров написал рекурсивный вызов своей функции, которая использует функция другого джуниора с временем выполнения O(n^3)/
То есть пишем функцию «MakeBest()», в ней пишем вызов «MakeGood()», и только потом создаем эту самую MakeGood(), прописывая что именно она должна сделать.

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

Так а я и говорю, что с типизацией это никак не связано. Я говорю, что у автора в статье рассказывается о двух проблемах во взаимопонимании, а не одной.
Судя по факту что количество коментов продолжает нарастать день ото дня автор 100% прав!
«Приверженцы» таки никогда не поймут друг друга и топик явно тянет на холиварный.

Понять оппонента не сложно. Сложно убедить его, что он не прав, или убедить себя, что ты не прав.

… или убедиться, что спор идёт о разных вещах и разных контекстах (самый частый и самый сложный случай, имхо).
Холивары — это достаточно полезные «ярмарки знаний», если уметь их готовить. Для обывателя это километровый срач без толка, а для адекватного участника — источник для расширения профессионального кругозора. Не даром эта дисциплина соревнований так любима на любых IT-ресурсах.
Строгая типизация — один из методов борьбы со сложностью. Для простых задачек она не нужна. Описанный спор — спор пластилина с железобетоном. Быстрое прототипирование против надежности крупных конструкций.
Замечу еще, что строгая типизация появилась задолго до ООП, а у автора эи понятия смешиваются. Строгая типизация появилась еще в Фортране.Язык для ООП появился на десять лет позже — Симула-67. И мейнстримом не стал т.к. считалось что объекты нужны только для имитационного моделирования. Еще 20 лет потребовалось, чтобы осознать, что ООП — универсальный инструмент борьбы со сложностью.
Да как-то особо против строгой типизации никто не возражает. Сомнения вызывает необходимость статической на уровне языка.
Никакой необходимости нет, есть полезность. Я думаю динамические и статические языки сходятся — в статических все больше вывода типов — уже не надо их декларировать, а в динамических появляется возможность их декларировать и статические анализаторы.

Уровень языка позволяет большому количеству производителей и потребителей инструментов договориться об описании типов, наверное.
Многие как раз говорят о необходимости, о нужности, а не просто о большей полезности в каких-то аспектах. Ну типа «если программа больше 1000 строк, но её нужно писать на статическом языке»
При некотором объеме производства металлических изделий штамповка становится выгоднее фрезеровки. Здесь аналогичная ситуация, бизнес стремится снижать накладные расходы.

в статических все больше вывода типов — уже не надо их декларировать

Вывод типов не освобождает от необходимости эти типы определять.
НЛО прилетело и опубликовало эту надпись здесь
Моя цитата была о статически типизируемых языках с выводом типов. Типы нужно явно или неявно (как в случае анонимных типов в C#) определять заранее и выводятся, проверяются они не позже этапа компиляции.
Откуда это требование про этап? Если в рантайме выпадет ошибка типов, то лучше бы она вообще не выпадала и рантайм вёл себя не предсказуемо?
Статическая проверка типов должна быть статичной) В идеале, в рантайме не должно быть возможности для ошибок типов вообще, тогда и контролировать корректность (в рантайме) не придется.

Рантайм — это слишком поздно, чтобы ловить ошибки, это не надежно, это оверхед. Чем больше гарантий можно вынести на этап компиляции, тем лучше.
Даже ценой введения этапа компиляции там, где его отродясь не было? )
Это в машинные коды-то? А то, строго говоря, даже перевод с языка ассемблера в машинные коды — это тоже своего рода компиляция. И перевод строки на скриптовом языке в машинные коды — это тоже своего рода компиляция, только оперирующая минимально возможной единицей кода. Или Вы о том, что в некоторых языках неуместным смотрится проведение компиляции раньше выполнения? Тогда хотелось бы понять, откуда эта «неуместность» берётся.
В машкоды — да, компиляция. А вот в скриптовых языках идеологически нет (Jit и прочие оптимизации опустим для ясности) — в процессе интерпретации лишь выбирается какой из заранее скомпилированных кодов интерпретатора выполнить.
Любой язык, которые претендует хоть на что-то, все равно будет содержать JIT или какой-то промежуточный байткод, поэтому не надо опускать то, что в любом случае будет.
НЛО прилетело и опубликовало эту надпись здесь
Я к тому, что интерпретация тупо текста скрипта это тупик. Любой язык, который хочет, чтобы его использовали за пределами «bash скриптов», придется что-то придумать. JIT, компиляция в байтокод и его интерпретация, компиляция сразу в машинный код — без разницы.
Если вся эта внутренняя компиляция скрыта от разработчика, то какая ему разница? Работает как интерпретируемый, просто быстрее.
Вы так говорите, будто это что-то плохое) В том же веб-фронтенде это практикуют вполне успешно.
А это хорошее?
Да, конечно. Мы как раз до этого обсуждали преимущества, которые это решение может дать.
А почему фазой статанализа не обойтись хотя бы?

Слушайте, ну хватит уже про вашу "фазу статанализа". Ну не работает статанализ на чисто динамических языках. Вот прямо сейчас у меня на поддержке проект на реакте, там ни флоу ни тс нету, и это адовый ад. Половина ф-й ИДЕ — просто не работают, протоколы обмена данными все время приходится уточнять в сторонних местах, банальный рефакторинг с добавлением параметра в ф-ю превращается в эпопею и грепы по проекту, вещи с внедрением зависимостей, HOC и прочее — убивают типы сразу и на входе идет гордое any, и чтобы с этим работать приходится держать по 5 вкладок открытых на экране и просто ВЕРИТЬ, что сам не зевнешь и не забудешь ничего. И не помогает ничерта встроенный тс-анализатор с типами стандартной библиотеки и зависимостей.

Да пускай язык будет статически типизированный типизированный семантически, но без классической компиляции с порождением каких-то объектных файлов технически. Вот тайп-чекер, вот интерпретатор. Забыли запустить тайп-чекер — неопределенное поведение в случае ошибок типов.
Можно, но есть риск потерять преимущества AOT-компиляции. При этом нет гарантии, что между валидацией и исполнением кода никто не залезет «одну мелкую вещь пофиксить».
Как это обычно бывает
image
Что значит потерять преимущества в языке, где их никогда не было типа джаваскрипта? )
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
> Типы и ООП связаны неразрывно.
Что? Нет, не так. ЧТООООО????!!! Автор хоть википедию не мог почитать перед выкаткой? Smalltalk, CLOS, Obkective-C? Это типа половина ОО-языков к концу 80х.
Именно поэтому есть фронтендеры, которые покрывают свой код невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors.


Ну они точно в меньшинстве. Большинство просто не пишет юнит тестов, потому что who cares if doesn't work sometimes.
Большинство не пишет тестов вообще, а большинство тех, кто пишет не пишет их с мыслями типа «а не закрепить ли что эта функция нормально обрабатывает ситуации когда вместо числа приходит строка без потерь преобразуемая в число, а когда приходит массив или булево, то бросает исключения»
еще любят тестировать фреймворк, на котором пишут, очень смешно бывает
«Если только начинаете изучать разработку, начинайте со статической типизации»
Нельзя не согласиться. Это способствует формализации задачи, пониманию аппаратных и алгоритмических основ вычислительного процесса. Это фундамент программиста.
На аппаратном уровне популярных архитектур типов нет, типизации нет. В алгоритмах хоть типа пресловутой сортировки, хоть каких-то бизнес-процессов тоже типов нет. Есть что-то вроде «если первый элемент больше второго, то поменять их местами» или «если сальдо дебетовго счёта будет красное, то не проводить операцию»
На аппаратном уровне популярных архитектур типов нет, типизации нет.

Операции для целых чисел и с плавающей точкой не отличаются? Откуда понятия word, dword и прочие?

В алгоритмах хоть типа пресловутой сортировки… тоже типов нет.

Это обобщенные высокоуровневые алгоритмы. Если копнуть чуть глубже в реализации: для сортировки нужно выбрать некоторый критерий сравнения, который наверняка будет отличаться для разных типов.
> Операции для целых чисел и с плавающей точкой не отличаются? Откуда понятия word, dword и прочие?

Операции отличаются, конечно, по содержанию — разные манипуляции с битами в памяти проводят. Но они не контролируют, что записанные в память аргументы имеют нужный тип. Вы вправе записать в память строку в кодировке koi-8 из пользовательского ввода и вызвать над ней операцию извлечения квадратного корня — ошибки быть не должно. Операции процессора ожидают, что вы сами проконтролировали какой тип операнда им передали, если ожидаете разумного результата. Контролировали вы в уме при написании ассемблерного кода или умный компилятор высокоуровневого языка не дал вам скомпилировать использование значения неожидаемого типа в операции — операции всё равно. Даже если вы вообще не контролировали. word, dword — это не про типы данных, а про их размер прежде всего.

> Если копнуть чуть глубже в реализации

Мы об алгоритмах или их реализации? Ну и реализации тоже может быть всё равно на тип. Критерий выбран будет по ожидаемомум типу без контроля. Как в математике (грубо) «Пусть N — множество чисел, с операцией сравнения определенной как...»

И обычно низкоуровневые реализации не проверяют типы, да и не имеют такой возможности, а исходят из «пусть адрес который пришёл к нам на вход в регистре указывает на начало последовательности 32-битных целых чисел в дополнительном коде с little endian порядком байт»
Но они не контролируют

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

word, dword — это не про типы данных, а про их размер прежде всего.

В сях short и long тоже могут отличаться только размером. Но это ведь разные типы.

Как в математике (грубо) «Пусть N — множество чисел, с операцией сравнения определенной как...»

Набор данных, связанный с некоторым множеством допустимых над ним операций — это и есть тип.
Не инструкции ожидают чего-то, а инструкции выполняются на аппаратном уровне так, что в случае если операнды имеют смысл в терминах некоторых типов, то результат тоже имеет смысл в терминах какого-то типа. Не более. Инструкциям всё равно подадите вы на вход знаковые целые, символы, смещения в словах или адреса памяти. Примет какой-то SUB номер страницы памяти и символ unicode, вычтет второе из первого и даже не «задумается» имеет результат какой-либо смысл или нет.

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

Вот только в алгоритмах такие типы в лучше случае определяются как условия для применения алгоритмов, а часто вообще не определяются, подразумевая что если над входными параметрами какая операция в алгоритме применяется, то ра для них определена.
Инструкции имеют не божественное происхождение. Их проектировали люди с особой целью и в определенном формате. Если программиста не бъют по рукам, то это не значит, что он все делает правильно. Умножение для float может дать удивительный результат для word. А проблема в чем? Алгоритм ожидает другой тип.

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

Эти условия — это и есть типы.
Например std::sort, который подразумевает, что в для типа должна быть определена операция меньше ("<"). Хотя это вполне себе обобщенный алгоритм.
Алгоритм не ожидает другой тип, он о типах вообще ничего не знает. Он описывает последовательность преобразований входных значений в выходные. Это человек наделяет алгоритм смыслом типа «если подать на вход подмножество элементов множества с определенной на нём операцией сравнения, то этот алгоритм отсортирует подмножество» А вот уже имплементация алгоритма типа std::sort уже может проверять вход алгоритма на принадлежность к какому-то множеству на уровне гарантий языка или путём расширения исходного алгоритма, а может и не проверять.
Но они не контролируют, что записанные в память аргументы имеют нужный тип.
На некоторых процессорах — контролируют. Эльбрус пресловутый. А на «обычных» процессорах… да, после громкого провала iAPX 432 про это направление на много лет забыли… но сейчас потихоньку начинают-таки двигаться в эту сторону
Я где-то в треде сказал, что я про мейнстримовые архитектуры. В каждом комментарии повторять излишним считаю.
ARM — это вполне себе мейнстрим. x86 с его CET — тоже.

Да, там пока не всё так хорошо развито, как «в архитектурах, которые мы потеряли» — но тут, как бы, проблема совместимости. И уже тот факт, что мейнстриймовые архитектуры начинают об этом задумываться — говорит о многом.
$var = 123;
$var = $var + "123";

Как представлена переменная var в памяти и по какому адресу она лежит?
Что выполняет процессор на второй строчке?
В одном из языков для которых этот код условно синтаксически верен, переменная будет представлена сишной структурой zval со значением long (один из типов объединения zval_value) в поле value. Память выделяется динамически, по какому адресу думаю никто не предскажет. На второй строчке в процессе выполнения вызовется сначала обработчик опкода ADD с ссылкой на переменную и строкой в качестве операндов, а затем обработчик ASSIGN с ссылками на переменную и результат первого вызова. думаю, этого достаточно.
Предположим, что это верно, хотя ясно, что описание сильно неполное.
Возвращаясь к прошлому тезису: стоит ли с подобного объяснения начинать обучение человека с условно нулевыми знаниями?
Именно, не стоит засорять голову всякими long'ами и тем более union'ами из лонгов, флоатов, ссылок, структур, указателей и т. п. Тут бы концепцию переменной объяснить хоть как-то, а не объяснять почему такой умный компьютер не может сложить 123 и «123» :)
Ну а как тогда объяснить почему компьютер не может сложить, например, число 123 и строку «миллион»? Не кажется ли что без понятия типов данных не обойтись в объяснении?
Почему не может? Может.
Статическая типизация это как регистры процессора или прямое обращение к памяти.
Все очень мега-круто и гвоздями к затылку, но кнопка только слева и только квадратная и так у всех и навсегда или все взорвется!

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

Да, я беру тайпскрипт фреймворк и вижу сигнатуры методов и возвращаемые типы:
String, Boolean или Number

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

JS за пять лет наплодил кучу ошизительного софта от системного до прикладного какую все ЯП до него не смогли даже на сотую часть.

Потому, что все дело «разработки» в статике начинается и заканчивается «объектным анализом» «разработкой классов» а стадия «использования классов» не наступает никогда!

Представить себе ситуацию океана статических либ типа js-ных на гитхабе — невозможно именно потому что у каждого свой царь в голове и своя «объектная модель» и врубаться каждый раз в новую ОМ просто чтобы только узнать чего и как там делает эта либа это нереальный мрак потому что каждый тут же начнет переколбашивать ОМ «как правильно» и до дела опять дело не дойдет!

Статика это смерть, ее нужно дозировать как яд, в минимально0достаточной дозе на максимальном удалении от переднего края где кипит жизнь а не тащить «по поределению» потому что «гвоздями к голове»

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

И вот теперь вышедшие было в тираж джава-лиды-чемпионы приползли в райский сад JS и стали кадить своей ересью направо и налево, коварством поистине адового уровня — type script

сажать на кол, и жечь под городской стеной упырей!
Java приказала долго жить, сожрав бюджеты планетарного масштаба и так ничего путного и не родив

Что-то ржу...

Публикации

Истории