Comments 1301
Типы и ООП связаны неразрывно.

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

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

Что касается типизации, то люблю оба типа. Динамическая очень полезна, когда надо что-то на коленке на несколько сотен строк написать.
Имеется ввиду type inference, Когда компилятор догадывается о типах самостоятельно.

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

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

Как минимум, результаты статанализа можно игнорировать

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

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

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

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

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

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

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

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

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

Лично мне в 1с именно динамическая мешает. Когда чтобы посмотреть что нужно в метод передать приходится лезть в код этого метода. Особенно весело когда метод может не вернуть ничего, один элемент, коллекцию, или строку с ошибкой и все это одновременно.
UFO landed and left these words here
Ну так оно и логично, есть выбор — мелкий проект, прототип — можно без типов, что-то серьёзное — юзай типы.
UFO landed and left these words here
На уровне языка может только и рантаймово, а вот статанализаторы очень рады. И да, в целом в 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вятой грааль», эталон.
Я ничего не путаю. Даже автор статьи написал: «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-кода.

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

Я понял вашу мысль и вероятно не совсем понятно выразил свою.

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


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

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


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

Хм, ну 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 и могут быть вообще не связаны друг с другом.

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

Но в данном случае вам никто не мешает не проверить этот флаг перед использованием other_param, а с завтипами — мешает.

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


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

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

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


Я чуть ниже напишу сейчас Druu подробнее.

Ну это не ЗТ.


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 — это, с формальной точки зрения, именно лифтанутые типы-синглтоны, а не значения.

Ну и да, тут автолифтинг, то есть значения вроде true, false, undefined, 125, "yoba" автоматически лифтятся в соответствующий тип-синглтон, когда мы используем эти литералы в типовом контексте (вроде же кстати в хаскеле чот такое тоже прикручено в каком-то расширении).

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


Тем более, что оно потом так же автоматически опускается обратно, когда вы в if проверяете, и с проверкой из if тайпчекер может вывести, какие операции над значением допустимы. Прям dependent pattern matching.


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


{-# LANGUAGE GADTs, DataKinds #-}

data Yoba otherFlag where
  FalseYoba :: { id :: Number, someFlag :: Bool } -> Yoba 'False
  TrueYoba :: { id :: Number, someFlag :: Bool, otherParam :: String } -> Yoba 'True

то всё, приехали в славную завтипизированную страну.


Кстати, конкретно в хаскеле это вы написать так не можете, ибо конфликты типов лейблов (хотя, теоретически, наверное, компилятор мог бы и унифицировать до, скажем, someFlag :: forall otherFlag. Yoba otherFlag -> Bool), так что тут х-ль соснул у ts.


А вот в случае типов-объединений — это именно типы самостоятельные, по-этому конструктор C1 возвращает один тип, конструктор C2 — другой тип, оба эти типы — подтипы типа YobaType.

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

Ну так этот паттерн с синглтонами — это как раз классический способ выражения ЗТ в языке без них нативных, но с 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[] :)

Не, забавный этот ваш TS, я был о нём худшего мнения.


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

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

Например?

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

И как там с этим в TS — сможем? В хаскеле вот сможем.

А просто взять и поднять функцию на термах в типы не сможем, кстати, хотя для многих функций сможем переписать её на аналогичную с семействами типов и прочей наркоманией, и сможем сделать это внутри языка. Но это уже такое, практическое.
В TS — разумеется, не сможем, потому что к тому моменту, когда нам известен размер массива, никакого TS уже нет.
И как там с этим в TS — сможем?

Нет.


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

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

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

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


Получается что-то такое:


{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TemplateHaskell #-}

module Main where

import Data.Kind
import Data.Singletons
import Data.Singletons.TH

data Nat = Z | S Nat

$(genSingletons [''Nat])

type family (m :: Nat) <@? (n :: Nat) :: Bool where
  'Z   <@? 'S _ = 'True
  'S m <@? 'S n = m <@? n
  _    <@? _    = 'False

type x <@ y = (x <@? y) :~: 'True

data Vector (t :: Type) (a :: Nat) where
  VNil :: Vector t 'Z
  (:::) :: t -> Vector t n -> Vector t ('S n)

infixr 9 :::

safeIndex :: k <@ n -> SNat k -> Vector t n -> t
safeIndex Refl SZ     (v ::: _)  = v
safeIndex Refl (SS n) (_ ::: vs) = safeIndex Refl n vs

data SomeVector t where
  SomeVector :: SNat n -> Vector t n -> SomeVector t

makeSomeVector :: SomeVector Char
makeSomeVector = SomeVector sing ('t' ::: 'o' ::: 'p' ::: 'k' ::: 'e' ::: 'k' ::: VNil)

consumeSomeVector :: SomeVector Char -> (Int, String)
consumeSomeVector (SomeVector natSing vec) = undefined

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


Сравнение синглтонов надо писать руками, но это делать мне уже стало лень, принцип там тот же. С таким сравнением уже можно написать consumeSomeVector.

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


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

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

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

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


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

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

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

Ну как, семейство <@? там именно эту арифметику (вернее, её маленький кусочек) и реализует.

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

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

Ну там как раз совсем всё просто, демоутите синглтон и получаете value:


vlength :: forall t n. SingI n => Vector t n -> Nat
vlength _ = fromSing (sing :: Sing n)

forall там нужен для правильного скоупинга n, чтобы в теле функции оно обращалось к n из сигнатуры (такое использование этого ключевого слова меня почему-то всегда пробивает на поржать, haskell is the new C++).


С очевидной natToInt всё работает как ожидается:


*Main> natToInt $ vlength ('t' ::: 'o' ::: 'p' ::: 'k' ::: 'e' ::: 'k' ::: VNil)
6

Правда, рефлы делать вам это всё равно не поможет, потому что для рефлов нужно доказательство на уровне типов (ну как вон те <@ и <@? у меня), а термы вам с этим не помогут, вам их придётся обратно заворачивать в синглтоны, и вы ничего не выиграли. Рассуждайте сразу в терминах синглтонов.


Либо, если вы хотите vlength на уровне синглтонов, то у вас будет аналогичный SomeVector экзистенциальный тип вроде SomeLength, на котором нужно паттерн-матчить, чтобы получить длину.




Кстати, совершенно неотносящееся, но, возможно, вам будет забавно. Я тут книжку по вычислимым функциям читаю, и там есть забавная задачка, над которой я периодически раздумываю последние пару дней и что-то никак не могу решить. Так вот, если взять функцию U(n, x), универсальную в классе вычислимых функций и главную, то довольно легко показать, что существует алгоритм, по двум U-номерам функций дающий некоторый U-номер их композиции. Однако оказывается, что верно и обратное: если U — универсальная, и существует алгоритм, по двум номерам дающий номер их композиции, то U — главная. Как это доказать?

Эта ветка — лучшая иллюстрация того что статическая типизация вредна для большинства проектов.
В этой ветке пытаются поонанировать вприсядку после тройного тулупа в перевороте совершенно непредназначенными для этого инструментами просто из любви к искусству. Делать какие-то далеко идущие выводы на базе этого несколько странно.

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

Так ведь можно сказать, что любой не-type-drived-development заканчивается стектрейсом :)
Я кстати так и не понял, как это возможно. Мы же не знаем длину. Соответственно, никаких статических проверок делать не можем, ну просто потому что статическая — по определению до рантайма, то есть не зависящая от конкретных данных, а только от множества их допустимых значений.

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


В каком-нибудь C++ или C# вы можете написать какой-нибудь там class vector, у которого в API торчат только безопасные функции, которые проверяют индексы, и это будет удовлетворять условию задачи. Но у этого есть недостатки:


  1. Если вы вдруг вырежете все эти проверки, то окружающий код этого не заметит вообще никак. Как некоторое следствие, вам не нужно доказывать, что эти проверки вообще имеют смысл. Вы можете ошибиться на единицу, можете проверять какую-то ерунду для старой версии кода, и этого никто не заметит.
  2. Как вы вырезаете эти проверки, если вы в другом месте все индексы уже проверили и всё доказали, или если всё это вообще известно статически? И вообще, у вас ровно столько же проверок, сколько обращений к массиву, и компилятору надо быть ну очень умным, чтобы их вырезать. Хотя можно было бы проверить, скажем, только максимальный индекс, или некую верхнюю границу, если алгоритм нетривиален (в соседнем треде писали пример с arr[arr[i] ^ 0xc] или что-то в таком духе).
  3. Как вы обрабатываете ошибки? Исключения можно и не ловить (и это поведение по умолчанию; чтобы исключение поймать, надо там какой-то try/catch писать, за отсутствие которых компилятор не бьёт по рукам). Исключения довольно недружественны к code review и статическому анализу. Возвращать Either<OutOfBounds, T> в коде на плюсах я бы не стал, компиляторы такое очень плохо оптимизируют.

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


А проблема всех этих пунктов в том, что в C++ или C# такая проверка — это действие, а не значение. Вы не можете сделать проверку и потом передавать её по цепочке вызовов. Вы не получаете нахаляву вместе с проверкой в том случае, если она не удалась, значение-факт того, что она, собственно, не удалась, которое вы можете использовать (или требовать) в окружающем коде. Вы не можете из проверки вывести другие (а это уже можно сделать статически, ага: если индекс n + k валиден, то индекс n валиден, и ничего проверять не надо в рантайме). Или, вариация на тему, вы не можете сделать один раз проверку, что в arr из примера с битами выше больше 15 элементов (или 16 элементов? блин, я ошибся или нет? а тайпчекер бы поймал!), каждый из которых не больше 31, и потом статически доказать тайпчекеру, что если эта проверка удачна, то за границы массива вы не вылезете. А проверку нужно делать по-прежнему ровно один раз! И если вдруг массив вам известен статически, то тайпчекер вообще может вывести её сам (и когда он её вывести не может, это ошибка компиляции, а не скрытая деградация производительности).


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

Я кстати так и не понял, как это возможно.

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

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

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

Мой изначальный вопрос ничего общего не имел с js.

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

Благодарю за 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/..."). И часто типизация написана неаккуратно или отстает от последней версии библиотеки.

Так напишите свой интерфейс в своем проекте и прикастите к нему. Получите и строгую типизацию без any и минимум заморочек. И чужой код править не надо.

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

UFO landed and left these words here
Типы рождаются не из ключевого слова 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, в реакте сделан тайпчекинг. Ну, и наконец, учтем тяготение императивных современных языков к функциональщине, где есть стремление выражать возможные состояния через типы и сама система типов еще более строгая.

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

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

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

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

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

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

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

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

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

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

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

  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. я это не с вами мы полгода назад вели очень странный спор про бизнес-логику и асинхронные "экшоны"?

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

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

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

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


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

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

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

Хороший пример это утилита jq.
Внутри у неё все функции имеют чистый вид: значения принимаются, а новые возвращаются — но при использовании проверяется, единственная ли это ссылка, если да, то изменения производятся по месту.

Вопрос в том, гарантирует ли мне язык, что вся эта императивщина не вылезет наружу (как ST тот же), или нет.

Толк — в интерфейсе. И в общем инварианте 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, которой нигде больше нет. А то, что фактически разный код будет работать по-разному — это достаточно очевидно на мой взгляд (=
UFO landed and left these words here

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

UFO landed and left these words here
А не надо этого хотеть

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

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

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

UFO landed and left these words here

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

UFO landed and left these words here
> Вы спросили что нельзя сделать в шарпах — вы получили ответ.

Мне кажется, это некорректный ответ. Ну как и, например, «чего нельзя сделать на 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. А проблемы сторонних продуктов — уже нет.
UFO landed and left these words here
Допустим, есть Алиса из отдела биллинга и Боб из отдела доставки. Алиса получает объект, патчит его, передает в функцию, написанную Бобом. А в этой функции Боб тоже патчит этот объект, но уже на свой лад. Поведение системы становится непредсказуемым.

Такое можно сделать и на статически типизируемых языках с 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. Ну и есть тонны расширений для всего на свете, что для веба очень полезно.
vscode вам недостаточно IDE? Рефакторинг, подсказки, отладка, тестирование, чего только нет. Чего конкретно не хватает? Ну кроме заветного IDE в описании на википедии.
Спросим еще раз. Что конкретно? Чего конкретно нет в 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?
К рефлекшену есть доступ из 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# кодом, что и у полноценной студии.

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

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

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

Согласен. Это отличный способ отстрелить себе ногу и должен применяться с осторожностью даже в строго типизированных языках. А в JS за такое надо как-то наказывать и желательно жестоко.
UFO landed and left these words here

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


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

UFO landed and left these words here

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Таварисчь наверно имел в виду ситуацию, когда задача сильно изменчива. Тогда возможно и да. Типонезависимые синтаксические формы высокого уровня возможно лучше будут выглядеть на чём-то динамическом. Хотя я правда для для подобной задачи (моделирование алгоритма БПФ Кули-Тюки с целью его конвейерезации и запихивания в 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 назад), я замечал, что стабильность и корректность мерджа изменений несколько раз заметно улучшалась. Что сейчас, тяжело сказать. Кроме «неопознанных капибар» ничего не заметил.