Комментарии 1304
Типы и ООП связаны неразрывно.
А чем, например, Ruby не ООПшный?
![image](https://habrastorage.org/getpro/habr/comment_images/90f/ea2/d6f/90fea2d6ffbce3cb43cb6bd91020c9c9.jpg)
К сожалению, очень многие считают, что статическая типизация и обязательные аннотации типов — это одно и то же. И наоборот, что возможность передать в функцию произвольный тип обязательно означает динамическую типизацию. Отсюда и весь срач. Комментарии к этой статье ничем в этом смысле не выделяются.
Если они используются для статического анализа, в чём смысл их проверять в рантайме? Если после статического анализа приходится проверять в рантайме, в чём смысл такого "статического" анализа?
Как минимум, результаты статанализа можно игнорировать, поэтому проверка в рантайме нужна. А статанализ нужен для выявления ошибок без запуска.
Вопрос как раз об этом, зачем нужен статанализ, результаты которого могут быть проигнорированы? Что этот статанализ вообще тогда даёт? :-)
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю.
Знакомое чувство.
ЗЫ
А начинал я вообще с изучения Си в самом начале 90х по книге Кернигана и Ритчи, которую я, будучи студентом, случайно купил в славном городе Краснодаре.
Представляете, как я страдаю от самой мысли, что существуют ЯП с другими подходами? :) (шутка).
Я начинал с C#, недавно влез во фронтенд. Не пониманию страданий автора. Динамика позволяет очень легко писать костыли в самом начале, когда структура сырая и ты сам не уверен, как лучше тут сделать. Затем после нахождения определенного пути, ты просто рефакторишь это в типы и все. В итоге в проекте есть фронтир, где на каждом шагу any. А есть устаканившаяся часть, где все по-полочкам. По-моему очень удобно получается.
Вы уверены, что мешает вам динамическая? Большинство проблем типизации с которыми я сталкивался за почти 20 лет PHP+JS были связаны со слабой типизацией, а не динамической.
— я пишу на C++/go/js/js+flow/typescript, увлекаюсь Haskell и ReasonML и тп. Я понимаю какие ошибки вы описываете, но не понимаю каким боком тут JS. Писать на C# можно ещё хуже, чем на JS и типы не спасут, можно глянуть www.govnokod.ru/csharp
— JS относится не только к фронту, но и к беку, а вся статья пронизана ФРОНТЕНДЩИКИ ПИШУТ ОТСТОЙ
— в проектах совсем мало ошибок, которые уберёт статическая типизация. Никто не пишет невообразимое количество тестов на type errors в js… так же как на C# никто не пишет проверки ссылок на null повсеместно или вы пишете?
— много чего ещё
У меня вывод один, нужно расслабиться и заниматься свои делом. Новичкам эта статья не поможет, а остальные и так все понимают.
{
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, имел дело только с типизацией в 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#?
Коллега, вы вероятно ошиблись. Я с С# никогда не работал и никогда этого не утверждал.
«один api endpoint, но с адекватным контрактом»
Поподробнее, пожалуйста.
Ну тогда приведите как это делается на том языке, с которым вы работали!
И пример с того объекта на 200 свойств и 30 зависимостей тоже приведите.
Поэтому я так активно принимаю участие в этой дискуссии и мне очень любопытно наблюдать за решениями, которые вы предлагаете! Разница только в том, что все эти решения мы уже пытались внедрить, но ни одно решение не дало хорошего результата (нагрузки на сервер, читаемость/поддержка/добавление кода и т.д.). В итоге мы остановились на решении, которое вы называете «говнокодом».
Очень хочется узнать о «один 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;
}
Спустя некоторое время, я все более склоняюсь к идее, что можно «вертеть типами» во все стороны, чтобы правильно реализовать задачу, но есть задачи, которые невозможно типизировать способом, который удобен программисту. DRY (do not repeat yourself), читаемый код, поддерживаемый код — это пожалуй самые уязвимые места этой проблемы.
А это в свою очередь ведет к «types hell» — миллиону вариаций (типов) одно и того же объекта
Вы можете написать типы с T | undefined (aka T?), тогда тип будет один.
Вы можете написать как вам выше указали — каждый кейз отдельно и потом объединить через & — будет всего тридцать типов, для каждого кейза. При этом данный набор типов будет прекрасной документацией вашего апи (которое весьма сложное учитывая наличие такого количества зависимостей) сам по себе, это уже не говоря о статических проверках.
Мне кажется, что все ваши упомянутые выше "грабли" были исключительно из-за того, что у вас не было человека, который бы нормально умел в используемый инструмент (flow в вашем случае). Еще вариант — дело было давно, когда во flow еще не было discriminated unions.
Только не надо писать: «Поставь other_param?: string
». Это не решение проблемы.
Почему не решение?
Каким бы ни был крут 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.
Я понял вашу мысль и вероятно не совсем понятно выразил свою.
«Вопрос, с чем мы работаем, и какие гарантии оно дает.»
Тогда, коллега, задам наводящий вопрос: «Почему TS компилирует в JS а не в машинный код?»
Вполне себе компилируется
Вы и C++ можете в JS транспилировать.
С каких пор такая зависимость должна решаться на уровне типов?
Описываете обычный тип состоящий из всех четырёх параметров. При десериализации параметр other_param, в случает отсутствия, останется равной null. А вот уже в коде вы же перед использованием параметра проверите, param_flag равен true или нет?
Почитайте про зависимые типы
Спасибо, прочёл. Но как это в коде реализуется?
Видимо тут пример неудачный, потому что, как уже говорил, я сходу вижу конкретный тип состоящий из 4 параметров. Просто в одном случае мы получим other_param равной null, а в другом — какому-то значению. И уже в коде мы в любом случае будет проверять other_flag равен true или нет перед использованием.
Или в динамических языках это делается как-то по другому? Мне (я серьёзно) интересно даже узнать.
Спасибо, прочёл. Но как это в коде реализуется?
Код знает какие типы могут быть. И может делать выводы о гарантиях. В typescript прочитайте про discriminated unions и type guards
https://www.typescriptlang.org/docs/handbook/advanced-types.html
if (some is Foo foo)
{
// Do something with foo.
}
Аналог typeof:
if (some.GetType() == typeof(int))
{
// В данном случае это заменимо на is.
}
Разве что у TypeScript есть свои особенности, связанные со своей динамической природой, например, проверка наличия конкретного метода.
А discriminated unions прям как в C#. Разве что выглядит чуть по другому:
interface IShape { }
interface ISquare : IShape
{
double Size { get; set; }
}
interface IRectangle : IShape
{
double Width { get; set; }
double Height { get; set; }
}
interface ICircle : IShape
{
double Radius { get; set; }
}
static double area(IShape shape)
{
switch (shape)
{
case ISquare square: return square.Size * square.Size;
case IRectangle rectangle: return rectangle.Width * rectangle.Height;
case ICircle circle: return Math.PI * circle.Radius * circle.Radius;
default: return double.NaN;
}
}
Такая похожесть, скорее всего из-за того, что C# (судя по вики) повлиял на TypeScript.
Одна компания разработчик, один автор языков… Хмм… Да c# похоже все-таки повлиял на тайпскрипт!
Нет, type guards — это вынос проверки типа во внешнюю функцию, применяются совместно со смарткастами. Аналогов в C# у этой техники нет, за отсутствием смарткастов.
Такая похожесть, скорее всего из-за того, что C# (судя по вики) повлиял на TypeScript.
Тут все как раз наоборот — это в C# гварды как в typescript (нормальный паттерн-матчинг не осилили, вот и прилепили гвардо-костыли) :). А в typescript — они как в Racket.
А я два типа из двух и трёх параметров и проверкой типа перед использованием. Код типа
function parseResponse(data) {
return some_flag ? new SomeClass(data) : new AnotherClass(data);
}
const response = parseResponse(data);
if (response instance of SomeClass) {
console.log(response.id, response.otherFlag)
} else if (response instanceof AnotherClass) {
console.log(response.id, response.otherFlag, response.anotherParam)
}
При этом классы не имею поля other_flag и могут быть вообще не связаны друг с другом.
C таким типом, как выше приводили:
{
id: number;
some_flag: boolean;
other_flag: false;
} | {
id: number;
some_flag: boolean;
other_flag: true;
other_param: string;
}
не проверить флаг будет нельзя. И ЗТ никаких не требуется.
Ну это не ЗТ.
type Yoba = {
id: number;
some_flag: boolean;
other_flag: false;
} | {
id: number;
some_flag: boolean;
other_flag: true;
other_param: string;
};
function yoba(x: Yoba) {
console.log(x.id) // ok, ид есть в обеих ветках
console.log(x.other_param) // не компилируется
if (x.other_flag) {
console.log(x.other_param) // компилируется
} else {
console.log(x.other_param) // не компилируется
}
}
здесь нет зт, это по сути аналог паттерн-матчинга. просто если при стандартных АТД когда у нас data YobaType = C1 | C2, то C1 и C2 не являются сами типами, это просто конструкторы. А вот в случае типов-объединений — это именно типы самостоятельные, по-этому конструктор C1 возвращает один тип, конструктор C2 — другой тип, оба эти типы — подтипы типа YobaType. С-но в хаскеле когда мы находимся в какой-то из веток паттерн-матчинга мы имеем типа YobaType (т.к. типов C1/C2 нету, сузить тип в ветке матчинга протсо не на что), а вот в ts компилятор может сузить тип до более конкретного, т.к. соответствующие типы есть.
Ну и да, тут автолифтинг, то есть значения вроде true, false, undefined, 125, "yoba" автоматически лифтятся в соответствующий тип-синглтон, когда мы используем эти литералы в типовом контексте (вроде же кстати в хаскеле чот такое тоже прикручено в каком-то расширении).
То есть в объявлении типа выше true/false — это, с формальной точки зрения, именно лифтанутые типы-синглтоны, а не значения.
Ну так этот паттерн с синглтонами — это как раз классический способ выражения ЗТ в языке без них нативных, но с GADT (например) и DataKinds.
Ну это конечно да, в некотором роде буквоедство, но смысл в том, что отображения тип-синглтон -> тип это по факту отображения тип -> тип, то есть, формально, ничего кроме лямбда-омеги тут не надо.
Я знаю, что тут есть другая точка зрения, но сам считаю, что зависимые типы — они только тогда, когда есть фичи, которые без лямбда-P не реализуются. Причем, да, получается, мы без лямбда-Р иногда можем делать некие вещи, которые с практической точки зрения мало отличимы от того, как ведут себя зт в определенных кейзах, тут спору нет.
А YobaType как записать можно?
Ну так объединением и записывается, неразмеченным. Допустим, у нас есть два типа A и B, мы сперва каждому типу добавляем тег в виде типа-синглтона, т.о. получаем типы-кортежи <tag1, A> и <tag2, B> и потом эти типы объединяем через обычное (неразмеченное) объединение, получаем YobaType = <tag1, A> | <tag2, B> (опускаем всякие несущественные нюансы со структурным подтипированием, тем что у нас поля на самом деле будут именованные а не безымянный как в кортеже и т.п.).
В случае когда мы при помощи АТД строим тип-сумму, то мы выполняем эти два действия в один шаг, по-этому типы <tag1, A> и <tag2, B> у нас просто не проявляются, то есть операция суммы — примитивная. В данном же случае она не примитивная и выражается через другие операции — умножения и объединения, в итоге у нас типы-компоненты объединения существуют.
В примере выше у нас tag'ом выступает поле other_flag. Ну и тот факт, что внутри true-ветки внутри if мы знаем какой конкретно подтип у значения — он тождественен тому факту, что в ветке паттерн-матчинга мы знаем, что значение может быть рпазобрано паттерном этой ветки. Просто в случае хаскеля мы этот факт (что значение х может быть разобрано паттерном Y) не можем выразить в виде утверждения вида "значение х имеет тип Y", т.к. нету подходящего типа Y. А в тс такой тип находится.
Ну и да, конечно, существование в системе типа, который позволяет делать утверждения определенного рода — это и есть характеристика выразительной силы системы, то есть в определенном смысле тут можно говорить что зт есть, но просто в таком случае само понимание зт становится очень размытым.
так что тут х-ль соснул у ts.
Еще кстати есть такая штука как фильтры (в ракете так называютя, хз как называется в тс). Например есть ф-я filter (обычная над списками), и у нее есть перегрузка:
filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
и тут с-но вопрос, что значит это "value is S"? а это значит, что данная ф-я является гвардом (т.е. предикатом над T и истинность этого предиката влечет за собой сужение типа Т до типа S). Иными словами, мы можем подать на вход фильтру массив (A | B)[], гвард isA в качестве фильтра и получим массив A[] на выходе.
Но тут надо добавить что в тс гварды не чекаются. То есть можно взять любой предикат и объявить его гвардом любого типа, и компилятор это съест:
function isZero(x: number): x is 0 {
return true;
}
const yoba = [1, 2, 3, 4, 5].filter(isZero);
yoba: 0[] :)
Например?
Ну классическое, мы сможем как-нибудь описать Vec[N], создать вектор длины N с соответствующим типом, где N считано из файла, и потом сделать type-safe доступ по индексу?
И как там с этим в TS — сможем?
Нет.
В хаскеле вот сможем.
А можно результат посмотреть, с-но?
Хм, я сейчас посмотрел и понял, что походу через рефлы можно и на тс как-то такое оформить. Подумаю на досуге.
ЗЫ: кстати, для работы еще нужна ф-я length специальная, т.к. без нее вы просто не сможете сконструировать рефл для массива, длина которого в компайлтайме не известна. Какой у нее тип будет?
Сделать параметризованный длиной массив такой, что статически гарантирована невозможность при обращении по индексу выйти за пределы массива. Чтобы код, в котором такое потенциально обращение было бы — не компилировался.
addElement
, как вот тутfixed size array? Так в TS есть кортежи, которые он чекает.
Размер кортежей известен во время компиляции.
А, ну на TS такое не реализуемо из-за отсутствия арифметики времени компиляции.
По факту она (конкретно арифметика) и в примере на хаскеле не совсем чтобы важна.
Решить обсуждавшуюся задачу.
Я кстати так и не понял, как это возможно.
Компилятор может убедиться в том, что к тому моменту, как мы сделали arr.ref(i), мы где-то выше убедились, что i < arr.length. При этом нам в этом случае не важно, что мы не знаем какой актуально arr.length. Главное, знать, что i — меньше.
В данном случае мы передаем в ref дополнительный аргумент, тип которого является утверждением о наличии такой проверки (выше это тип k <@ n). Если мы можем сконструировать значение данного типа (тип населен), то это является доказательством данного утверждения (типы = утверждения, термы = доказательства, ака измофоризм Карри-Говарда). С-но, чтобы вызвать ф-ю нам надо иметь соответствующее доказательство. Если проверки нет — мы данное значение просто не сможем построить, с-но и ф-ю вызвать не сможем, т.к. она будет требовать аргумент, который мы не можем ей дать.
А с каких пор она НЕ ДОЛЖНА решаться на уровне типов?
«Описываете обычный тип состоящий из всех четырёх параметров.»
Вы работали с Haskell? Если бы работали, то не констатировали эту чепуху. То, что это реализуемо в JS (flow, typescript), не означает, что такое прокатит в других языках, где есть типы.
Мой изначальный вопрос ничего общего не имел с 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. Ну и докинуть все нужные проверки.
в проектах совсем мало ошибок, которые уберёт статическая типизацияИ это очень важный фактор, с которым, судя по «дебатам», не согласны сторонники статической типизации.
К сожалению без "as any
" и "// @ts-ignore
" в TypeScript пока не обойтись.
Потому что существуют библиотеки вроде React, в которых типизация поставляется отдельно (в пакетах "@types/...
"). И часто типизация написана неаккуратно или отстает от последней версии библиотеки.
Специально для таких случаев существует Declaration Merging https://www.typescriptlang.org/docs/handbook/declaration-merging.html. Не нравится как описан какой то тип из тайпинов? Допиши сам!
А ставить any или того хуже @ts-ignore это подкладывать грабли себе же самому
А хотите пример? React useRef()
. На flow он выглядит так (исходник):
function useRef<T>(initialValue: T): { current: T };
В @types/react
так:
function useRef<T>(initialValue: T): { current: T; };
function useRef<T>(initialValue: T | null): { readonly current: T | null; };
А теперь попытаемся открыть в TypeScript пример из документации
const intervalRef = useRef();
intervalRef.current = setInterval(() => {});
Для Flow все нормально, он даже типы выведет. А TypeScript скажет: [ts] Expected 1 arguments, but got 0
. И пока саму библиотеку и ее типизацию поддерживают разные люди, такое будет встречаться регулярно.
Или вот прекрасная библиотека mobx-state-tree. Все в ней хорошо: и реактивность, и снапшоты, и статическая типизация, и проверки типов в runtime. Но похоже, что ее TypeScript интерфейсы не являются публичным API. Несмотря на то, что они эскпортированы из модуля. Да и сама библиотека написана на TypeScript.
Внезапно, между двумя минорными версиями 3.5.0 и 3.6.0 мы видим:
- export interface IMSTMap<C, S, T> {
+ export interface IMSTMap<IT extends IAnyType> {
Ну и как таким пользоваться?
Это все не проблема TypeScript как языка. Это проблема экосистемы в целом. Я до такого докопаюсь. Возможно, напишу свои декларации. Буду их скрупулезно поддерживать. Но придет мой коллега из лагеря динамической типизации, и скажет: "Почему я должен тратить время на все это?"
Так не используйте такие библиотеки, какие проблемы?
Вы начали с Шарпа и строгая статическая система типов потребовала от вас умения проводить свои мысли в порядок. Те же кто начал со скрипта и не имел врожденной склонности к порядку получили в голове кашу, причесать которую очень сложно. поэтому их код похож на доску конспиролога.
Хотя корелляция между и есть, но разница между подходами, описанными в статье — примерно как между строителем мостов типа нашумевшего Крымского и строителем мостов через ручьи. Где можно натянуть несколько верёвок, прикрутить к ним палок — и всё.
Попытка построить таким путём мост длиной в 10 километров приведёт к тому, что его шторм унесёт в море задолго до того, как вы закончите его строить… а попытка строить мост через ручей «по феншую» приведёт к тому, что он обойдётся в 100 раз дороже… хотя и стоять будет дольше — но это не всегда оправданно.
Добавлю ещё что ООП и типизация перпендикулярные понятия. ООП получается если прикрутить код к данным (объекты носят свой код с собой).Истинно так. ©
В JS незачем тащить никакое ООП как стандарт. Есть внешняя JS библиотека (а может их и много существует), используя которую, вы можете «породить» такое ООП (со своими правилами «наследования», «использования кода объектов») которое только придумаете сами!
Незачем «приколачивать» конкретную реализацию ООП в стандарт JS. Это уже(!) смысла не имеет никакого. — Хватит, что бессмысленный «const» ввели в стандарт JS.
JS чем хорош — что это свободный(!) язык. Как там говорится — используя JS вы можете делать что захотите, в том числе и плохие вещи — не каждый язык программирование это позволяет, да ещё в таких(!) масштабах. ;-)
Те же кто начал со скрипта и не имел врожденной склонности к порядку получили в голове кашу, причесать которую очень сложно. поэтому их код похож на доску конспиролога.
Понимаете ли какое дело… Будь у этих ребят в голове каша, мы бы не видели весьма сложных и нетривиальных библиотек ни на js, ни на питоне, ни на чём-то подобном. Но мы их ВИДИМ. И КАК они разработаны, для меня, как приверженца типов и статики, полная загадка… Возможно я чего-то в этой жизни не понимаю. И в этом мне дико хотелось бы разобраться, хотя сам скорее всего так со статикой и останусь.
Откуда вы знаете, может это private метод, к которому просто никто не обращался)
Те же кто начал со скрипта и не имел врожденной склонности к порядку получили в голове кашу, причесать которую очень сложно. поэтому их код похож на доску конспиролога.
Вы оскорбляете конспирологов, у многих из них доски поддерживаются в чистоте и порядке.
Это не значит, что динамически типизированные языки не нужны (на самом деле я и правда думаю, что не нужны).
Но сейчас я так крепко прибит к своему мышлению, что не вижу и не хочу видеть плюсов динамической типизации. Они есть, но вне поля моего зрения, и все что мне остается — закрывать на них глаза, как на любые чуждые мне проявления мира, с которыми приходится жить
На самом деле, думаю, что вы совсем не одиноки. Я уже как-то скидывал однажды картинку из доклада Романа Елизарова про развитие ЯП, скину еще раз, так как она весьма показательна.
![image](https://habrastorage.org/getpro/habr/comment_images/ad8/b8b/ba8/ad8b8bba83394aaa7ca75ba5e3487d23.jpg)
Чертой отделены языки, вышедшие в 20 и 21 веках. Здесь отлично видно, что индустрия в последнее время однозначно делает выбор в пользу статической типизации. Добавим сюда факты, что в своем развитии php пришел к строгой типизации, в python вроде добавляли статическую типизацию, в мире фронтенда набирают популярность ts, dart, flow, в реакте сделан тайпчекинг. Ну, и наконец, учтем тяготение императивных современных языков к функциональщине, где есть стремление выражать возможные состояния через типы и сама система типов еще более строгая.
В общем, я тоже не понимаю плюсов динамической типизации, и, как можно увидеть на практике, индустрия старается от неё уйти в сторону строгой статической типизации.
В общем, я тоже не понимаю плюсов динамической типизации, и, как можно увидеть на практике, индустрия старается от неё уйти в сторону строгой статической типизации.
Системы типов в мейнстрим языках становятся всё более развитыми и позволяют всё большую гибкость (= меньше напрягают, больше помогают), поэтому остаётся всё меньше поводов выбирать динамическую типизацию.
В свою очередь, моя интерпретация вашей интерпретации, связанной с экспоненциальным ростом экосистемы Javascript, заключается в том, что без вагона костылей на Javascript написать ничего вменяемого ни у кого не получается. Отсюда и перманентные попытки придумать серебряную пулю.
Сильно подозреваю, что с js именно так оно и было.А не надо подозревать. Брендан сам рассказывает, что у него было 10 дней на всё-про-всё.
Получился, конечно, уродец тот ещё… но могло быть и хуже…
TS на бэке используют только консерваторы типа автора статьи.
В чем это выражается? JS за последние 5 лет что-то принес остальному миру, новые идеи, практики? Или он растёт только относительно себя же?
> экосистема растёт экспоненциально
Рост экосистемы ценен тогда, когда каждый элемент работает из коробки, а вместе дают синергетический эффект. А когда «часто типизация написана неаккуратно или отстает от последней версии библиотеки» — это рост в сторону бесконечного решения специфической «проблемы ромба» из за несовместимости кусочков инфраструктуры и вечных войн тупоконечников с остроконечниками, в каждой итерации заканчивающихся созданием новых, несовместимых со всем остальным, кусочков. И переписывания старых на TypeScript.
А есть ли за всем этим синергетический эффект? Или только экспоненциальный рост сложности?
В чем это выражается? JS за последние 5 лет что-то принес остальному миру, новые идеи, практики? Или он растёт только относительно себя же?
Вот тут пожалуй с Вами не соглашусь. Да, развивается. И пожалуй да, быстрее чем всё прочее вместе взятое. Поглядите на библиотеки! То что js ИСКЛЮЧИТЕЛЬНО гибок, этого у него не отнять. Гибче пожалуй только Forth(хотя на мой взгляд это гибкость самоубийцы). И именно потому что бурно развивается, мне очень хочется понять ПРИЧИНЫ такой дикой популярности… Чисто любопытства ради.
Поглядите на библиотеки!На какую стоит посмотреть, чтобы увидеть что-то новое и интересное?
Пока что всё, что я видел — это, в основном, новые способы сделать то, что раньше можно было уже сделать и так — только с новым синтаксисом и порождением большего количества кода в результате.
В процитированном вами тексте находятся одни вопросы. Как можно "не соглашаться с вопросами"?
С риторическими — прекрасно можно!
- Можно пример?
- Как вы определяете считается вопрос риторическим или нет? Решаете ли вы тем самым за вашего собеседника ожидает ли он ответа на вопрос? Если да, то по какому собственно праву?
Можно пример?
Так выше, тот самый пост, на который вы отвечали. Риторические вопросы являются вопросами синтаксически, но при этом содержательно — они являются утверждениями. А с утверждениями можно соглашаться либо нет:
Ритори́ческий вопро́с — риторическая фигура, представляющая собой вопрос-утверждение, который не требует ответа.
такие дела
Как вы определяете считается вопрос риторическим или нет?
В точности так же, как я (и любой другой человек) вообще определяю смысл сказанных (или написанных) слов. Исходя из своего опыта, что те или иные речевые конструкции значат в данном контексте то или иное.
Если да, то по какому собственно праву?
Раз человек мне что-то говорит, то он дает мне право решать, какой смысл в эти слова вкладывался. Потому что никто другой это решить не может.
js ИСКЛЮЧИТЕЛЬНО гибок
В чем же состоит ИСКЛЮЧИТЕЛЬНАЯ гибкость самого JS, которая оказывается вдруг не свойственна остальным языкам общего назначения?
Полагаю автор комментария выше имел ввиду, что в JS в runtime вы можете вытворять практически всё, что угодно, кроме перегрузки операторов (её нет). Всякие там proxy, символы, defineProperty с хитрыми полями, getter-ы и setter-ы, toPrimitiveValue|toString|valueOf и пр. позволяют с виду простой очевидный донельзя код заставить делать что-то совершенно иное. Чем-то сродне define true false
но в runtime. Весь этот хаос может как позволить написать супергибкие решения, так и опрокинуть вас в пучину хаоса с использованием минимума кода. Мне кажется, что JS это язык с беспредельным количеством анархии. Этакий godmod. Даже прототипная система наследования устроена так, что вы можете менять предков, поля\методы предков в мановению щелчка пальцев. Вы можете легко переопределять почти любые объекты и методы из "стандартной библиотеки". Ну в общем если в двух словах — уровень допустимого беспредела неограничен. И всё это в runtime. Хорошо это или плохо — судите сами.
За другие языки "общего назначения" не скажу. Полагаю таких языков полно.
в 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 получился. Даром не нужен :)
А что не так-то? 9 лишних символов (.Set(s>,)
) так принципиальны?
А всё не так. В реальном redux-reducers коде у вас только эти 9 символов, помноженных на каждую мутацию, и останутся. За ними мало что разглядеть можно будет. В гробу я видал такую гибкость и наглядность. redux-reducers в себе хранят по сути всю бизнес-логику приложения. Т.е. весь треш и угар как раз здесь. Чем более наглядно здесь описана логика, тем проще с этим работать. Так что да, более чем принципиальны. А если ещё вспомнить, что это поделие (immutableJS) тормозит, то я вообще не понимаю, зачем им кто-то пользуется.
Решение влоб — через обертку над стором, который бы внутри пересоздавался на каждое изменение. Основная проблема, над которой придется пободаться — это поддержка синтаксиса, ибо для поддержки кастомного сеттера проксировать необходимо весь граф объектов внутри. Но это должно быть решаемо и сильно зависит от исходного API доступа к свойствам.
ЗЫ Но, да, в .NET вряд ли появится redux, ибо глобальное состояние — это отличный маркер плохого дизайна и за это там гоняют тряпками.
Так вот, в 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, если интересно.
Тогда бы весь этот код выродился во что-то типа
const newStore = { ...store, list: [...list, 12: {...list[12], someValue: 12 } ] };
Хрен редьки не слаще. Глаза от этих точек и скобок разбегаются :)
я бы вот так написал
const newStore = {
...store,
list: list.map((item, index) =>
index === 12 ? { ...item, someValue: 12 } : item
)
};
Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.Все можно, можно даже сборку декомпилировать и заново собрать, если она в IL, но это адский труд по сравнению с
someObj.existingFunction = function() {
console.log("Ha-ha!");
}
someObj.existingFunction должен иметь тип Func или Action.
и вы спокойно сможете подменить функцию.
Либо делегат (delegate ), либо event (он всё равно подтип делегата). Всё это является указателями на функции и может быть переприсвоенно, либо в случае с event — на него можно подписаться-отписаться любым своим обработчикам.
Хотя всё это вы и сами знаете.
Это у вас пример решения. А зачем вы такое решение используете?
Обеспечить похожую семантику (map-объектов и прототипного наследования) — это как раз не проблема.
Моей целью было показать, что такую же семантику обеспечить можно, это не убер-фича JS, которой нигде больше нет. А то, что фактически разный код будет работать по-разному — это достаточно очевидно на мой взгляд (=
Что если мы хотим подменить Count у стандартного класса List<T>
? Просить майкрософт поменять свойство на делегат?
А не надо этого хотеть
Вы спросили что нельзя сделать в шарпах — вы получили ответ. Начать после этого мантру «вы не того хотите» немного некорркетно, не находите?
Не говоря про то, что это используется в тонне фич того же дотнета, начиная от всяких DynamicProxy (есть такой класс, реализует все интерфейсы. в WCF активно используется), и заканчивая всякими Moq/MS.Fakes/…
Мне кажется, это некорректный ответ. Ну как и, например, «чего нельзя сделать на 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-коде константы, потому что майкрософт захардкодили там коннекшн стринги и имена своих таблиц, которые иначе нельзя было изменить. В итоге патчили хекс-редактором сборки, и загружали обратно, с красной пометкой "НИКОГДА НЕ ОБНОВЛЯТЬ И НЕ ЗАМЕНЯТЬ". Костыль, конечно, но вот один случай когда такое понадобилось у меня в коллекции есть.
Так их не больше, они просто доступнее.
Ну то есть, основные кейсы по сложности мало отличаются, фактически только синтаксисом.
А когда проблема не в программировании, а в SharePoint — это точно проблемы с автором. Я в hex-редакторе драйвер принтера правил, который лишнюю команду слал. Он был на каких-нибудь сях написан, это был очень плохой язык программирования, там даже IL кода нет. Причем, на каком языке печатать ни пытайся — тот язык тоже плохим становится. Даже JS.
Так что я предлагаю ограничиться тем, что проблемы языка заканчиваются вашими (доступными вам для изменения) исходниками и BCL. А проблемы сторонних продуктов — уже нет.
Допустим, есть Алиса из отдела биллинга и Боб из отдела доставки. Алиса получает объект, патчит его, передает в функцию, написанную Бобом. А в этой функции Боб тоже патчит этот объект, но уже на свой лад. Поведение системы становится непредсказуемым.
Такое можно сделать и на статически типизируемых языках с Reflection, вроде Java.
"Простой" — это весьма легко сказано. Если для кого-то перекладывание части забот заключается в изменении пары строки в конфиге билда и начале использования типизации, то для кого-то это будет означать полную смену технологического стэка, причём не только для разработки, но и для эксплуатации.
Ну, как пример, решили перейти с LEMP(HP) на .Net +C#. Вроде что-то делается для кроссплатформенности активно самой MS, но в целом пока впечатления по новостям, что такой переход будет означать полный перевод инфраструктуры разработки и эксплуатации на Windows и другие продукты MS типа VS, IIS и, не дай бог, SQL Server, что уже будет далеко не локальным техническим решением, облегчающим жизнь разработчикам, а глобальным экономическим, административным и кадровым решением.
Переходить на Windows не нужно: в Linux есть куча типизированных языков. Go, Java, тот же TypeScript.
А что культура разоработки нужна другая — это да. Об этом и статья.
Я про конкретный C#. Допустим только его команда более-менее знает или готова изучать.
И не вижу я никакой другой культуры разработки. Вот в чём она другая? Только он конкретных людей зависит. Один в динамически типизированном обложится аннотациями, тайп-хинтами, тестами и статанализаторами с максимально строгими настройками, а другой навставляет any/variant везде и настройки строгости на минимум.
Mono.
Уже 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);
}
Весь «чит» тут в подмене ссылки на скомпиленный метод, если бы не защита памяти, то даже PInvoke не пришлось бы использовать и всё в пределах C# (хоть и в unsafe) можно было бы делать.
Или typeof(System.Int32) — ещё C#, а typeof(System.Int32).Attributes — уже CLR?
Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.
Любую задачу можно сделать на любом языке, потому что в худшем случае сначала пишете на языке А интерпретатор В, а потом пишете оригинальную программу на В и все работает. Только это не та мощность, которая нас интересует.
Считается, что использование рефлексии должно быть явно оправдано, какой-то конкретной необходимостью. Процесс обнаружения типов, процесс их создания на лету, изменения и прочего всего такого несколько расходится с основными принципами статической типизации.
Как сказал товарищ Free_ze если вы пользуетесь таким функционалом, вы либо совершенно точно понимаете, что вы делаете, и иначе это не сделать (Mocks-библиотеки, IoC-контейнеры, AOP-фреймворки), либо вы претендуете на свой зал славы на говнокод.ру.
Если на то пошло, то рефлексия в статически типизированных языках в разы сильнее этой идеи «обратись к методу по строке его имени» — поэтому там и существуют пресловутые IoC-контейнеры и Mock-библиотеки. А в JS приходится довольствоваться spy(«myMethod») и иже с ним.
В PHP, в слабо динамически типизируемом языке, есть и IoC- контейнеры, и Mock-библиотеки.
Типизация в PHP всегда была. Слабая динамическая. Есть тенденция к переходу на более строгую динамическую. Из 23 лет своей публичной истори 18 лет PHP живёт с ООП, с версии 4.
И без стандартной рефлексии вполне можно обойтись обычным кодом на PHP — это, по сути, не фича языка, а обычная библиотека.
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
.
Expression это не просто исполнение выражений, вы можете написать на C#, с проверкой типов и прочим, а на выходе получить… да хоть JS, можете склеить несколько выражений и т.д., при должной сноровке инструмент невообразимо мощный.
P.S.: как вы nameof(anything), eval и т.п. форматируете?
Да, в таком разрезе это выглядит полезным. Интересная штука. Обе из них.
Наверное, всё же, не в С#, а в заточенных под него IDE. В каком-нибудь vim без полноценной поддержки языка вы вряд ли получите те же гарантии правильности рефакторинга, которые есть в средах, заточенных под C#, а особенно использующих его родной анализатор (не знаю, возможно ли это, но со стороны MS было бы логично использовать "кишки" компилятора C# в VS)
github.com/OmniSharp/Omnisharp-vim
Ну т.е. править структуры можно и на лету. Это другое дело. И да, я не телепат )
Процесс обнаружения типов, процесс их создания на лету, изменения и прочего всего такого несколько расходится с основными принципами статической типизации.
Необязательно. Если весь процесс инкапсулирован и прозрачен, то никаких расхождений нет. Я писал имплементацию записей на шарпе и все методы записей были сгенерированы. Поскольку весь процесс закрыт, реализация была неубиваема. Это примерно как небезопасный код. Его испльзование не делает вашу программу багованой, просто подходить к вопросу надо ответсвенно.
Вы про синтаксический сахар или про возможности языка? Что из того, что вы перечислили нельзя реализовать в коде, Кроме перегрузки операторов (про неё я как раз упомянул). Про рефлексию смешно, да. Шампунь для лысых.
Если вы всё же про сахар, то есть куда расти. Сейчас обсуждается очень много "вкусных" вещей, которые нас ждут ближайшие год-два. Правда опять будет очень много криков про кривую обучения и перегруженность языка сахаром.
С такой формулировкой соглашусь. Я сам тот ещё "сладкоежка". Согласен, что в JS есть не все виды сахара, которые доселе были придуманы. Но за последние 5 лет их добавили столько, что спецификация языка, наверное, выросла раза в 4. И на подходе новые и новые. Уж кому-кому а JS разработчикам на острый дефицит сахара жаловаться не приходится. Я вот с нетерпением жду pipe-оператора.
я тоже не понимаю плюсов динамической типизации
Ну вообщем-то есть один плюс: компиляторы/интерпретаторы для ДЯП писать гораздо легче :)
Из книг можете взять следующие:
1. Грэди Буч «Объектно-ориентированный анализ и проектирование с примерами приложений».
2. Роберт Мартин «Принципы, паттерны и методики гибкой разработки на языке C#» — там описываются принципы SOLID, которые очень важны в ООП (особенно Inversion of Control, IoC).
3. Сергей Тепляков «Паттерны проектирования на платформе .NET» — описание классических паттернов банды четырех с использованием C# + SOLID. На мой взгляд, эта книга написана поинтереснее, чем у GoF.
Кроме того, если выберете определенный язык, то в книгах и мануалах по этому языку всегда будет описание того, как работать с типами, наследованием, полиморфизмом.
Динамическая типизация несет социальныую функцию — привлечение к программированию непрограммистов. С ростом сложности задач каждый сам приходит к статической типизации.
Эрланг в телекоме имеет место, там же и был придуман изначально.
То есть перечисленные претензии к вотсапп это личные проблемы очень маленького количества гиков. Не нравится, не используй.
то, что оно потом не может ни жить на двух устройствах, ни даже бекап сделать инкрементальный… как раз указывает на то, что с идеями — у них хорошо… с программистами плохоПро невозможность жить на двух устройствах — это была принципиальная позиция Яна Кума.
Есть сложные задачи, в которых нет однозначного алгоритма, а его еще надо найти. Писать такие на статических языках — ад.
Честно признаюсь, ни разу не замечал. Хотя занимаюсь подобными достаточно часто.
А по моему опыту, такие задачи как раз таки решать проще с статической типизацией: набросал тип, а дальше с ним работаешь. Если нужно добавить обработку — добавил, нужно новое Поле — добавил. Никакого геморроя из разряда "договориться с коллегой об интерфейсах", все отлично читается из кода (аннотации и комментарии — костыли. Тип — самодостаточен).
Есть сложные задачи, в которых нет однозначного алгоритма, а его еще надо найти. Писать такие на статических языках — ад.
Если у вас нет однозначного алгоритма в голове до того, как вы начали имплементацию, вам не поможет ни динамическая, ни статическая типизация, вы уже обречены.
Динамическая типизация просто делает этот фейл менее очевидным и откладывает осознание проблемы. Если вы не знаете, что именно вы хотите сделать, и как именно оно должно потом работать, вы в конце получите невнятное говнецо, которое вроде че-то делает, но все время как-то немного не так и вообще кривовато.
И так раз пять.
Т.е., утрировано, дано «Абляция», а в результате надо получить «Прецессия».
1. Придумываем «как решать»
2. Реализуем
3. Что-то не устраивает — скорость, качество, потребление памяти, удобство использования, косяки с граничными случаями, вообще не работает, etc.
4. goto 1
И, как мне кажется, в таких случаях таки да — проще прототипировать на объектах JS или ассоциативных массивах PHP, чем каждый раз переделывать статически типизированную модель данных.
О каких издержках кстати речь? Что код тормозит? Или процесс разработки слишком долгий? Или памяти много потребляется?
Банальный пример. Напишите с нуля систему обработки естественного языка при ограничениях на скорость и память.
Чем вам поможет типизация?
Введете тип Text и его метод Process?
Введете типы String и Word? А нужны ли они? И если нужны, то как должны быть реализованы? А если реализация оказалась неудачной, но просто заменить ее внутренности, не затронув верхние слои, не получается?
Типизация мне поможет выстроить иерархию объектов, написать короткие и безопасные методы, поможет избежать мне ошибки «не туда».
А еще как очень правильно написали выше — типизация структурирует и приводит в порядок мысли.
Статические аннотации требуют лишней работы.
Вы так говорите, как будто конкретно написание кода занимает сколько-нибудь существенный процент времени.
Отнюдь не все статически типизированные языки требуют аннотировать свои типы.
Если у вас нет однозначного алгоритма в голове до того, как вы начали имплементацию, вам не поможет ни динамическая, ни статическая типизация, вы уже обречены.
Таварисчь наверно имел в виду ситуацию, когда задача сильно изменчива. Тогда возможно и да. Типонезависимые синтаксические формы высокого уровня возможно лучше будут выглядеть на чём-то динамическом. Хотя я правда для для подобной задачи (моделирование алгоритма БПФ Кули-Тюки с целью его конвейерезации и запихивания в FPGA) успешно юзал С++. Кстати это был вообще первый случай, когда добровольно и сознательно заюзал классы, до того писал в чисто процедурном стиле. А ведь есть ещё современные языки с выводом типов, такие как Rust! На чем-то подобном вообще всё должно ложиться мягко и пушисто…
Динамическая типизация несет социальныую функцию — привлечение к программированию непрограммистов.
Уже несколько раз здесь писал, повторю ещё раз. Готов был бы в это поверить, если бы не сложность библиотек, написанных на js, питоне и прочем динамическом. Это говорит о том, что скорее мы, сторонники статической типизации чего-то не понимаем.
Вот чего всё эта бурная деятельность породила? Более скруглённые уголки? Сравните, хотя бы, с 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 своим дизайном не способствует росту скилла «рабочей командной лошадки». Получаются именно «ниндзи», творцы с полетом мысли, когда код пишется быстрее, чем обдумывается ТЗ (как тут уже неплохо подметили), которым путь в другие области и языки уже практически заказан (ибо слишком много шаблонов придется рвать, а тут уже неплохо кормят).
Ничего подобного не было ни в Office 95, ни даже 2007. По-моему, сложность реализации этого не меньше, чем форматирования ячеек в таблице.
Зато не то два, не то три раза изменили весь дизайн и переписали код с нуля, неизмеримое число раз «игрались с цветами» и прочее.
Так Gmail-то как раз — про статическую типизацию. Исходно там были всякие Java+GWT и Closure со включённой строгой типизацией при компиляции. Финальный JS, который грузится на клиент — машиногенерённый, как байткод Java, который, к слову, тоже не особо statically typed со своими дженериками через object.
байткод Java, который, к слову, тоже не особо statically typed со своими дженериками через object
Вы так говорите, будто из-за дженериков там пропадает вся остальная типизация. Я не эксперт в этой области, но говорят, что там рефлекшном вытаскивается типовая информация даже из «затертых» объектов.
Вытащить информацию о типе можно и в питоне, например. Питон — строго типизииованный, но не статически. К слову, в стиле duck typing можно херачить на, наверное, любом языке с достаточно мощным reflection'ом, например на Go.
Ничего вы не вытащите.
Причем, все настолько плохо, что по сути вы можете с этим столкнуться и во время компиляции (А не только в рантайме):
private interface IFoo<T>
{
}
private class A {}
private class B {}
private class Foo implements IFoo<A>, IFoo<B> {
}
Наоборот — чем дальше в лес, тем более сложными становятся решения для тех же задач.
По мне — так это скорее регресс, а не прогресс…
Значит Gmail — плохой пример в данном конкретном случае. Ибо бурная деятельность тут происходит не на уровне JS, а как раз на тех уровнях, где статическая типизация. И JS мог развиться до любых высот, но есть груз легаси в инструментах компиляции и потребность запускаться на legacy-браузерах.
но зато количество кода, который нужно писать руками для получения сравнимого результата падало
Ну дык кол-во кода для написания одного и того же на портянке jquery+велосипеды или, скажем, backbone+any_template_engine радикально упало. В разы. А сам код стал проще. Местами даже с элементами FP.
Наоборот — чем дальше в лес, тем более сложными становятся решения для тех же задач.
Дык почему же? Наоборот. Во frontend эти задачи даже и не пытались решать. Даже само слово frontend стало популярным лишь недавно.
Или вы всецело имеете ввиду? Ну я думаю со времён изобретения MVVP и vDom люди только и делают, что экспериментируют с уже известными подходами и технологиями, пытаясь найти что-то оптимальное. Скажем команда React в одном из новых релизов обещает COMMIT (прямая работа с DOM) фазу разбить на подзадачи с использованием iddleCallbacks. Что в этом плохого?
Фреймворки двигаются в сторону ускорения разработки, уменьшения времени исполнения и лучшей поддержки инструментов отладки. Я вижу один прогресс.
Не понимаю вашего пессимизма. Есть кривой и косой gmail. Это следствие менеджмента в Google. Можно его же сделать быстрым как метеор. Благо технологии позволяют. Подходы все известны. Никакой магии нетребуется. Но надо чтобы кто-то взялся за это. Но ведь премию и повышения дают только за редизайны.
Меня например лично печалит, что когда мне поставили задачу "определить, есть ли на страницу google pixel", мне пришлось селениум в проект затаскивать, просто чтобы понять, есть ли некоторая подстрока на странице. А все потому, что я не могу просто сделать GET
урла и поискать текст, ведь всё, что я там найду это <html><body><script src='app.js' /></body></html>
.
Ну так себе проблема, если честно. Вот поставят вам задачу, определить используется ли библиотека X в бинарном приложении Y, там никакой Selenium вам уже не поможет. Хорошо если исходник найдёте. Ну или есть навыки джедая в дизасемблерах. А если нет? :)
Ну так себе проблема, если честно
ДА нет, нормальная проблема. Раньше можно было вызвать GET и получить страницу. А теперь ты вызываешь GET и получаешь непойми что. Семантика HTTP как по мне поломана на прочь. Тебе нужен целый рантайм и куча несвязанных вещей чтобы появился контент. Не даром в HTTP урлы называются «ресурсами», на которых хранится контент. А сейчас это тупо заглушки, в которых нифига нет.
Семантика HTTP как по мне поломана на прочь
Да её вообще уже к лешему послали. Как и почти все остальные устои. Тут где-то что-то мелькало, что мол Google от URL хочет отказаться, а вы про гипертекстовый фидоинтернет :)
Да, но любой запрос (до недавнего времени) содержал полезные данные.
Теперь же запрос страницы отдает заглушку-лоадер, который и будет загружать непосредственно страницу.
В общем, как я уже сказал, мне как бек-разработчику это добавило очень много геморроя и небходимость тащить браузер в приложение, где его вообще быть по идее не должно.
Кое-где она поломана, да, но общий подход по GET / получить html-страницу-заглушку не ломает семантику. Это вполне себе ресурс — A network data object or service that can be identified by a URI. Про какой-то определ'нній вид контента речи нет.
Языки с динамической типизацией — суть исключительно скриптовые.
А потом в результате в соседней теме спрашивают почему софт тормозит? habr.com/post/431282
PS похоже, сейчас, у многих от этой правды бомбанёт и мне сольют Карму.
.
.
.
.
.
.
.
.
.
.
.
Ну вот, хотя бы, пресловутый новый дизайн GMail: огромное количество кода, «сложнейшие» библиотеки… и что в результате?
Вот чего всё эта бурная деятельность породила?
Тормоза и отжирание памяти конечно породила!
Тормоза и отжирание памяти конечно породила!
Ещё раз. Это не проблема js. Это проблема с одной стороны тупых говнокодеров, не думающих об оптимизации от слова совсем. С другой стороны текучки у фирм-разработчиков, когда кто-то начинает работать, потом увольняется оставив кучу говнокода, в котором толком никто не может разобраться. Затем приходит другой, нарастив сверху один слой точно такого же говнокода. Затем третий и т.д. В итоге несложная веб-страничка превращается в море жидкого дерьма. Которую по-хорошему надо бы переписать с нуля, но во-первых лень, во вторых жаба душит. В третьих это проблема человечества, слишком увлёкшегося IT и тратящего на это кучу усилий, абсолютно не соответствующую полезности этой деятельности. А когда коту нечем заняться, он как известно яйца лижет. Что мы и наблюдаем.
P.S. Минуснул не я. Честное пионерское! :)
Вспомните, как появились V8, HipHop, KPHP и другие подобные проекты. Когда язык поощряет пресловутую «свободу самовыражения», писать костыльный код на нем становится легко и приятно. Говнокодеры радуются тому, что им теперь удобно работать, и вкладывают миллионы человеко-часов в решение прикладных задач. В итоге у бизнеса есть система, которая худо-бедно выполняет свою функцию, но переписать ее как следует уже экономически невозможно, и приходится изобретать умопомрачительно сложные способы оптимизации.
Ну да. Причем я часто слышал это как раз от С++ разработчиков, но там тоже с типизацией не всё хорошо, поэтому можете ознакомиться с их аргументацией, что "настоящий программист" должен уметь держать в голове. Думаю, будет довольно много пересечений с потенциальными ответами того же товарища Aingis.
HTML тоже называли недостаточно строгим и хотели перейти на строгий XML. Не взлетело. С JS, думаю, если бы его попытались сделать «дисциплинирующим», вышло бы то же самое.
Жалко, что не взлетело.
«Проблема в JS» ≠ «проблема JS».
Если у кучи людей регулярно возникает проблема с Х, то проблема не в людях, а в Х.
Если у кучи людей регулярно возникает проблема с Х, то проблема не в людях, а в Х.А если у кучи людей регулярно возникают проблемы с математикой или с грамотностью — то проблема, конечно же, в математике и в грамоте, я правильно понимаю?
В общем, я тоже не понимаю плюсов динамической типизации, и, как можно увидеть на практике, индустрия старается от неё уйти в сторону строгой статической типизации.
Я тоже. Вот поэтому-то мне и было бы крайне любопытно услышать серьёзные аргументы сторонников динамической типизации. То что они есть, я не сомневаюсь. Уж больно нетривиальные вещи пишут на том же js. К сожалению то что пока прочел от них в комментариях, увы детский лепет. Правда читал выборочно, сейчас начну сплошняком. Ну и очень надеюсь что подтянутся люди, серьёзно работающие на js.
Уж больно нетривиальные вещи пишут на том же js.А какие именно? Нет, мне правда интересно.
Я вижу огромное количество бурной деятельности и огромное количество… не хочу ругаться матом… но вот чего-то, где я мог бы понять — почему оно такое большое и сложное… не вижу. Вижу поделки, похожие на то, что в прошлом веке делались с помощью Visual Basic или Delphi — только с примерно в 100 раз большими требованиями к ресурсам и со в 100 раз большим количеством кода.
А какие именно? Нет, мне правда интересно.
Признаюсь абсолютно честно. Я тут особо не специалист. Я таварисчь в основном очень и очень низкоуровневый. FPGA, микроконтроллеры, реальное время и т.п. Сходу так приходит из того чем пользовался и что дико понравилось (а пожалуй даже где-то как-то удивило) это jsplumb (рисование графов) и d3.js (визуализация графиков и диаграмм).
Вижу поделки, похожие на то, что в прошлом веке делались с помощью Visual Basic или Delphi — только с примерно в 100 раз большими требованиями к ресурсам и со в 100 раз большим количеством кода.
Вот с этим остаётся только согласиться. Сейчас ваяю потихоньку некий чисто свой проектец. Пишу на ts. Так из всех библиотек у меня там только require.js и то только потому, что без неё непонятно как грузить то безобразие, которое ts собирает в единый файл. Даже jquery не использую. И rich text editor у меня свой (он довольно специфический). Глянул в своё время на angular, это просто ужос, ужос, ужос. Дерьмовина типа hello world тащит два с половиной мегабайта всякого говна! У меня весь проект (на данный момент) меньше 200 килобайт! Так что да, ощущение того что всё сделано через жопу, самое что ни на есть явственное. Но повторяю ещё раз, я человек из совершенно другой области. За веб-программированием наблюдаю с интересом и даже кое что пишу. Но оценить профессионально не в состоянии, для этого в этом надо жить. Но на мой тупой дилетантский взгляд со стороны, развивается всё очень и очень бурно. Хотя согласен, качество порой сильно оставляет желать…
А куда слияние в один файл пропало-то? Просто указываете outFile
вместо outDir
и наслаждаетесь жизнью же...
Вся разница — в том, что бинарники на go не настолько часто передаются по интернету как javascript-бандлы.
Я сам .NET дев, но при всей любви к статический сильной типизации на бэке, до появления анонимных типов и dynamic С# временами подбешивал необходимостью в очередной раз менять одноразовую фигню потому что-то переехало в другое место на странице, и количеством DTO.
Второй случай — когда я тяжело болел форексом, писал некую софтину для финансовой аналитики. Серверная часть писалась на С++ в виде некоего ядра и набора плагинов. А отображалось всё в броузере. Проблема была точно такая же, как в первом проекте с FPGA. Я не знал заранее, что и как я в следующий момент захочу изобразить. И решена была точно так же. Вместе с данными в броузер отправлялся некий заголовок в текстовом формате, их описывающий. И всё работало хотя и не на пять с плюсом, но на твердую четверку.
Мораль сей басни такова. В любых, самых изменчивых проектах, всегда есть некоторое относительно стабильное ядро. Скорее всего не совсем простое. Управляемое некими командами. Вот это-то ядро и имеет смысл реализовывать тщательно, не скупясь, на чем-то хорошо помогающем отлавливать ошибки (в частности статически типизированном). А все изменения выносить в некий управляющий этим ядром простой язык. Думаю (хотя могу ошибаться, ибо не в точности знаю Ваших задач) на Вашем месте я точно так бы и поступил. Хотя Вам конечно виднее, на истину я никак не претендую.
А зачем dynamic в UI?Например
return View(new
{
User = user,
Upjachki = upjachki
});
...
@model dynamic
@foreach (var upjachka in Model.Upjachki)
{
}
Завтра дизайнер скажет, что упячки больше не нужны, нужны уточки. Причем на одной странице, на второй оставить. Я такой подход не приветствую, но…
Option<Upjachki>
чем не подходит?
Зато не будет проблем с Model.Upjacki
.
Динамики вообще не нужны. В языки испокон веков был Hashtable (позже Dictionary), который делает все, что нужно. Никаких проблем вместо foo.a.b
написать foo["a"]["b"]
. Зато исчезает куча проблем как с производительностью, так и с неявностью, что есть у объекта, что нет, где упасть может, а где нет.
Тем, что его нужно описать.
И хорошо. Описано в одном месте, все инварианты проверены только на границе. Дальше можно безопасно пользоваться. Слышали про защитное программирование? Удобная концепция.
Когда у вас сотни страниц и одноразовых DTO, начинает напрягать. Зачем Option, кстати?
Кодогенерация в помощь. Ну и да, я люблю согласовать схему данных, а потом тыкать в неё коллег, которые её не соблюдают, когда вместо данных передана фигня и все ломается. Или вы предпочитаете делать за всех чужую работу, и вставлять костыли у себя?
Явность foo[«a»] ничем не лучше foo.a, так же замечательно падает.
Ничем не лучше, только вот во всей программе (кроме той, что работает непосредственно со входом) ничего падать не будет. В самом худшем случае упадет при десериализации, это значит, что на вход подсунули чушь, которую программа не ожидает. Она может по счастливой случайности смочь обработать и этот новый тип, но чаще ошибка вылезет где-нибудь в километре от того места, где реально произошла, и ищи потом ветра в поле.
Язык QuickBASIC (компилятор, не путать с интерпретатором QBASIC), созданный в середине 80-х, позволяет одной директивой заставить разработчика декларировать типы всех переменных, и там тоже нет ООП.
При этом современный статически типизированный язык Crystal, например, который по задумке тотально ООП, не только в коде самого компилятора очень часто процедурный, но и позволяет без проблем писать в процедурном стиле.
При этом z = x + y в большинстве статически типизированных языков запросто приводит к неверному результату, и упомянутый тут язык Java долгое время таскал внутри себя баг в алгоритме бинарного поиска, когда результатом m = (l + r)/2 было число намного меньше l. Ибо Int склонен переполняться (впрочем, как и float, там просто разрядности мантиссы становится недостаточно), а проверить это разработчикам компиляторов не досуг.
При этом многие интерпретаторы динамически изменяют разрядность переменной при переполнении.
Я, собственно, к тому, что в статически типизированных языках нет ничего «современного», они появились вообще-то раньше динамических, в статически типизированных языках совершенно не обязательно использовать ООП (и совершенно не факт, что в динамически типизированном языке можно не использовать ООП), ну и да, в конечном итоге мне совершенно непонятно, причём тут вообще проектирование классов и статическая типизация.
P.S. И да, если убрать колоссальный пласт «неправильного» кода без ООП на том же языке Си — боюсь, что автору не пришлось бы написать свой пост, ибо нечем и некуда.
Так что, давайте оставим статическое статическому, а динамическое динамическому и не будем пытаться сделать из одного другое и наоборот
бороться с быстро развивающимися библиотеками (а тайпинги для них отстают)
Сейчас все больше и больше "быстро развивающихся библиотек" пишут сразу на ts.
Вместо any нужно использовать unknown и тогда без проверок не получится использовать объект неправильно.
Вот, например, отличная презентация с демонстрацией такого подхода к дизайну (что забавно — тоже на примере игры), притом человек пишет на Clojure — языке с динамической типизацией (но строгой иммутабельностью).
Это не значит, что динамически типизированные языки не нужныЕдинственный объективный плюс динамической типизации — можно очень легко и быстро слепить наивный интерпретатор для данного языка. Для разработчика, использующего такой язык, плюсов по сравнению с достаточно гибкой статической системой типов нет.
Возможность не описывать некоторые типы в статически типизированных языках тоже бывает: например, в C# есть
dynamic
, в Typescript — any
. Но в хорошем коде это допустимо только в качестве локального исключения из правил, когда нужно вкрутить какой-нибудь незначительный костыль.в C# есть dynamic, в Typescript — any.
В dynamic/C# не означает потерю информации о типе в рантайме, в отличие от any/TS.
Разве в TS информация о типе в рантайме как-то теряется?
А C# проверяет типы всю дорогу, dynamic лишь отключает проверку типов на этапе компиляции, а в рантайме они продолжают бросаться экепшнами, если что-то пошло не так.
Иначе не было бы type guard'ов и то как костылей.
Напротив, тайп гуарды потому и могут существовать, что информация о типах в рантайме не теряется, и они ей способны пользоваться.
Как я их понимаю, на этапе проверки типов компиляции, анализатор делает предположение, что он возвращает реальный результат о типе как instanceof.
Вся ответственность за саму проверку на программисте, он, например, анализирует наличие методов из интерфейса в объекте, чтобы "сохранить" в рантайме информацию о том, что объект реализует интерфейс. И если проверка не очень тщательная, тайпскрипт на этапе компиляции будет обманут.
Так ведь наличие методов потому и можно проверить, что информация о типе не потерялась, и ее можно вытащить через Object.keys
, Object.getOwnPropertyNames
или оператор in
...
Нет, о типе информация теряется, проверка наличия метода обычная операция для JS с его манкипатчингом, где метод может біть добавлен почти куда угодно. И это не информация о типе, это информация о наличии конкретного ключа в таблице.
А чем "информация о наличии конкретного ключа в таблице" концептуально отличается от "информации о типе" в контексте структурной типизации?
Мне кажется, прежде чем продолжать разговор вам следует уточнить, что вы все-таки подразумеваете под информацией о типе, и в каком смысле она теряется. Иначе ваши слова выглядят странно.
Про нейронную сеть интересно. А она точно нейронная? На каких данных обучалась?
Я посмотрел на пару твитов, и это больше похоже на подготовленные человеком ответы:
Context in #reactjs is a crutch – никто так не пишет, в результатах гугла только этот самый твит. Больше похоже на русского программиста, который буквально перевел слово "костыль" на английский.
Есть твиты с картинками. Откуда нейронная сеть их возьмет? Поиск по картинкам в гугле ничего не дал, а в Яндексе находит только этот же самый твит. Получается, что ИИ сочинил текст, сделал его скриншот и вставил в твит? Слишком фантастично.
Получается, что если там и есть робот, то он постил твиты, которые для него заранее сочинили люди.
Вариант С/С++ (впрочем с некоторыми улучшениями — что-то я бы усилил, что-то ослабил) мне нравится больше всего (хотя наверное здесь есть фактор привычки).
А зачем ноль преобразовывать в false? Откуда взялся этот ноль со значением false?
Ага, особенно "удобна" вот такая форма условного оператора: if (count=0)
:-)
А «удобно» — это когда при виде `if (count)` не возникает вопросов:
— Это проверка на «проинициализирован ли count»?
— А count вообще проинициализирован?
— А count где-то инкрементируется? Точно один раз?
Вот `if (items.Any())` — удобно. Ни переменной лишней не надо, ни следить за ней.
Это даже не вспоминая про удобство `if (count = 0)`…
Это не просто экономия, это паттерн, распознаваемый с одного взгляда. При явном сравнении с нулем распознается паттерн сравнение со значением и только на втором шаге анализируется с каким значением.
Тяжёлое детство, лишенное витаминов, восьмибитные игрушки, паттерн, распознаваемый с одного взгляда…
> паттерн сравнение со значением
Только семантика у него не про сравнение, так ещё и потерю выразительности в удобство запишите.
В мою бытность студентом (шесть лет назад) у нас был компьютерный класс. Ubuntu на терминалах, первый урок — как перейти в консоль и выйти из случайно запущенного vi, код пишется в mcedit… Первые эксперименты (примитивная fork-бомба, завесившая намертво терминал), первые куски кода, про которые никто не понимает, как это работает, и прочие радости юного кодера.
Одной из этих радостей был gcc, настроенный лично завкафедрой. -Werror — наш лучший друг, в этом я убедился ещё тогда; особенно, когда количество активных предупреждений начинает потихоньку переваливать за рамки очевидного.
Писали мы какую-то программу для работы с массивами. Уж не помню, что такое требовалось, но помню, что решил поместить индексы к массиву во второй такой же массив (типа double). Никакой арифметики с ними не производилось — только перестановки. В конце же, соответственно, требовалось взять элемент первого массива, индекс которого лежит в нужном месте второго массива. Казалось бы, дело простое, да?
arr[indexes[i]]
Ta-damm! Ошибка — за давностью лет уже и не вспомню, какая именно, но я, глядя на неё, решил попробовать явное приведение:arr[(int)indexes[i]]
Фиг вам! Та же самая ошибка.… В общем, конечная строка кода выглядела так:
arr[(int)(float)indexes[i]]
И удаление любого из двух явных приведений типа приводило к выбросу предупреждения (ну а в силу -Werror — к ошибке компиляции).Явно преобразовывать между int и bool, говорите?..
P.S. А ещё я про этот случай периодически вспоминаю при работе с Rust. Со всеми этими
let int: i32;
let float: f32;
...
let double: f64 = (float as f64) * (int as f64);
Но здесь хотя бы понятна суть: весь язык устроен так, чтобы не давать выстрелить себе в ногу, обрезав результат под выходной тип.соглашение ноль==false, не ноль==true кажется вполне однозначным.
Это кажется.
Возьмём логический тип. Пусть false – это 0. Тогда ~0 (не ноль) — это 0xffffffff или -1 в дополнительном коде. Применяя как операции «&», «|», «^»,, так и операции «&&», «||» мы получаем правильные результаты.
Для числового типа это не так. Возьмём значение 1 и значение 2. И то, и другое — это true. Применив к ним операцию «&» (побитовое «и»), получаем 0, т. е. false. Получается, что true & true равно false. Для операции «^» тоже можно подобрать примеры противоречий.
Вывод: между типами int и bool разница есть.
По старому соглашению, a && b
это сокращенная запись a & b
, где b не вычисляется, если a false. Аналогично с |.
Для bool-like типов данных они должны давать один и тот же результат.
Насчет функций, то в случае && вторая часть не выполняется, если вернулось false, а в & обе части выражения вычисляются. Но я это уже говорил буквально двумя строчками выше…
Я не зря про bool-like говорил.
Для остальных типов побитовое И может быть любым.
если у вас есть
bool a = ...
bool b = ...
bool c = a && b;
bool d = a & b;
результаты c и d должны совпадать для любых a,b.
#include <stdio.h>
int main()
{
int a = 1;
if (a)
{
printf("I'm a and I like true!\n");
}
int b = 2;
if (b)
{
printf("I'm b and I like true as well!\n");
}
bool c = a && b;
int d = a & b;
printf("bool c = a && b: %04X\n", c);
printf("int d = a & b: %04X\n", d);
if (!d)
{
printf("I will not argue with the standard");
}
}
Я надеюсь, вы все так пишите, удачи с отладкой.
Все возможно. Функция без проблем может вернуть number | string
(Typescript), Either Int String
(Haskell) или std::variant<int, std::string>
(C++).
Никак без очень дурно пахнущих проверок типов. Как, впрочем, и в динамически типизированном языке, просто эти проверки конкретно для сложения написали за вас.
У нас получается не статическая типизация, а кустарная (вручную написанная) динамическая типизация.
А проверки типов вам придется писать в любом случае, если только вы не передаете объект в чужую библиотеку целиком (в таком случае эти самые проверки типов написаны в этой самой библиотеке).
Да написать-то так можно. но получается потом не по фэншую. Дальше придётся писать проверки типов
В динамике вам точно так же придётся писать проверки для обработки собственно значений
У нас получается не статическая типизация, а кустарная (вручную написанная) динамическая типизация.
Типы-суммы это очень даже статическая типизация. Вы статически знаете, что значение внутри или типа А, или Б. Назвать её динамической — примерно как назвать int динамически типизированным, просто потому что там могут лежать разные числа.
Представьте, что через год в вашу функцию-парсер пришлось добавить ещё один возвращаемый тип (денежный, например). Со статикой вам придётся изменить возвращаемый тип функции, и компилятор начнёт ругаться сразу во всех местах использования этого типа и заставит вас добавить ещё одну ветку везде. С динамикой вам никто ничего не скажет, но по закону Мерфи вы наверняка где-нибудь забудете добавить проверку, и программа либо упадёт в рантайме, либо ещё хуже: будет молча делать не то, что нужно, пока кто-нибудь не заметит.
Статическая проверка — это способ установить a single source of truth для области значений и допустимых операций у каждой величины, используемой в программе. Альтернатива — размазывание этого знания по всему коду и проблемы с рефакторингом.
Альтернатива — размазывание этого знания по всему коду и проблемы с рефакторингом.Какие проблемы ваще? Смотрите: со статической типизацией вы заменяете тип возврата — и потратите три дня на то, чтобы заменить на всех уровнях все функции.
Закроете одну таску, да ещё и нагоняй получите за то, что возились три дня с плёвой задачей.
А с динамикой-то как хорошо: за день замените первые три функции и закроете таску. А потом — спринты, багфиксы, доводка и прочее. И всё такое клёвое — прям можно показывать заказчику и денежку требовать. Ясно даже за что. Вот за то, что вот эта формочка перестала чушь типа $$$$ вместо $100 показывать и за то, что вон там в списке новый тип появился.
Ну и что, что результат втрое дольше порождается? Зато заказчик видит, что работы — вагон! Прогресс — прогрессирует, аджайл — аджалирует, все довольны. Даже заказчик, который деньги платит. Чего ещё, собственно, вам нужно?
Вот я не понял, это сарказм или нет?
Но суть в любом случае та же: три дня идёт работа, которая не порождает видимого выхлопа — а потом, за один день, появляются сразу все запрошенные фичи. Если вы знакомы с эффектом айсберга, то понимаете, что с точки зрения бо́льшей части современного менеджмента — это ужасно: «они что-то там делают, а я не понимаю что… наверное баклуши бьют». Если у вас каждый день происходит «бурление» и что-то в проекте меняется — они это вспринимают гораздо легче.
Даже если всё происходящиее, с точки зрения программиста, напоминает, извините, кипячение дерьма, при котором всплывают и лопаются пузыри — это воспринимается лучше, чем когда просиходит что-то «в глубине» без видимых результатов.
Да собственно это хорошо видно везде. Посмотрите на современное описание Turbo Pascal 2.0: Turbo Pascal 2.0 — почти такой же, как и Turbo Pascal 1.0 со слегка изменённым интерфейсом (палитра серая с белым вместо желтой с серым), несколько незначительных изменений в компиляторе.
Сравните с описанием 1984го года: политра «серая с белым» не упоминается вообще (как слишком минорное изменение), зато есть такие пункты:
- Поддержка overlays
- Поддержка динамической кучи (да-да, в Turbo Pascal 1.0 её не было… вернее была сильно урезанная версия).
- Дополнительные команды редактора
- Работа с цветом
- Поддержка графики (только CGA в данной версии)
- Работа с окнами
- Работа со звуком
Ничего так «незначительные изменения», да? Особенно первые два?
Собственно популярность динамические языки обрели, когда изменились критерии. Когда прогрессом стало считаться вот это вот «пузырение», когда можно продемонстрировать «прогресс» заказчику ежедневно. А то, что конечный результат получается хуже и дольше… ну кого это волнует?
Только в случае с динамикой раздолбанность будет неявной и чтобы выявить причину поломки понадобятся тесты/дебаггер, а в случае со статикой раздолбанность будет явной, так как ломать и бросать исключения Вы будете своими руками.Потому в первом случае вы можете с чистой совестью сказать, что «таска закрыта», а во втором — нет.
Понимаете — люди видят только пиксели на экране, но когда эти самые пиксели говорят им о том, что в программе ошибка — они, в общем, этому верят. С динамической типизацией такое происходит реже. И люди считают что и программа работает правильнее. Вот и всё.
А что потом делать с этим значением? Мне правда интересно, как новичку.
Я просто не совсем себе представляю код в котором мы из метода получаем: или число, или дата, или строка, а после свободно работаем с этим значением без каких либо проверок типа.
Эта функция должна вернуть значение числового типа (если пользователь ввёл число), дату (если введённые данные похожи на дату) или исходную строку (если ничего распознать не удалось).
Уже сами исходные условия вызывают вопросы. Если надо возвращать разные типы, то получается клиентский код не знает, какой тип ему придет. Как он вообще будет дальше работать с этими данными? А если ему неважно, какой тип, то можно и дальше со строковым форматом работать и тогда задача теряет смысл.
Если же вам все-таки надо вызвать определенную логику обработки в зависимости от типа, то это например в C# это можно быть сделано вот так
public void Recognize(string value)
{
DateTime date;
int number;
if (DateTime.TryParse(value, out date))
{
HandleDate(date);
}
else if (int.TryParse(value, out number))
{
HandleInt(number);
}
else
{
HandleString(value);
}
}
Для абстрагирования от типов можно также можно использовать ad-hoc полиморфизм (перегрузка операторов или методов), параметрический полиформизм (дженерики) или полиморфизм подтипов (абстрактные классы, интерфейсы). Инструментов куча.
И вообще, что это у вас за поле такое в интерфейсе, которое может быть и строкой, и числом, и датой?
Console.WriteLine(decimal.Parse("2018.12", CultureInfo.InvariantCulture).ToString());
Console.WriteLine(DateTime.Parse("2018.12", CultureInfo.InvariantCulture).ToString());
Не знаю, где это может понадобится, для синтаксического разбора что-ли. Но для этого более вменяемые инструменты существуют.
sealed class RecognitionResult
data class RecognizedNumber(val value: Int): RecognitionResult()
data class RecognizedDate(val date: Date): RecognitionResult()
data class RecognizedString(val str: String):RecognitionResult()
fun recognize(input: String): RecognitionResult = TODO("implement")
fun main(args: Array<String>) {
when(val result = recognize(args.firstOrNull() ?: return) {
is RecognizedNumber -> print(result.value)
is RecognizedDate -> print(result.date)
is RecognizedString -> print(result.str)
}
}
Всё статически определено, проверено и обработано. Профит.
abstract class UserInput<T>
{
public string Input { get; set; }
public abstract T Value { get; }
public abstract bool IsValid { get; }
}
В остальных 99% случаев вы знаете требуемый тип. И не забываем про
про whitelisting by default.
> Вот 2018.12 — это число? Вы уверены?
Я уверен, что неуверенность в этом только вам по ночам спать мешает.
Да, число. Если бы дату хотели ввести — ввели бы увереннее, например «2009-06-15T13:45:30.0000000-07:00»
> -0.01e+2 вполне себе валидное число
Понимаете в чем дело, ввод "-1" делают что бы быстро(!), не включая мозг, ввести дату «вчера». Если вам быстрее ввести -0.01e+2, то и оно корректно обработается — флаг в руки, чем бы дитя не тешилось.
То у вас синтаксический анализатор, что бы можно было ввести как минимум sum(workdays({requests.submited}, 11/03/2015)) * 8, то нельзя -0.01e+2… Не понимаю.
Emacs, Maxima, некоторые части AutoCAD (и там есть встроенный интерпретатор LISP), полётная система Boeing 747, Apache Storm, Datomic (база данных), бэкэнд Walmart, оригинальный Reddit, HackerNews,…
Немного, но достаточно, чтобы доказать, что писать крупные приложения на нём вполне возможно.
- 99,9% всех образовательных программ в школе и институте уже 40 лет (если не больше) обучают с самого начала только структурному программированию (в последнее время ООП)
- всякие пиарщики ездят по ушам менеджерам, а преподают люди давно отошедшие от разработки (или никогда ей не занимавшиеся), в итоге образованием и управлением занимаются люди, до сих пор пересказывающие сказки из 90-х про ООП
- на самых популярных платформах по подготовке школьников по программированию и проведению олимпиад (informatics.msk.ru, Codeforces, contest.yandex.ru) есть даже PHP и OCaml, но нет ни одного диалекта LISP
99,9% всех образовательных программ в школе и институте уже 40 лет
Проходят сейчас в универах и лиспы и пролог и даже хаскель иногда (отдельные курсы по функциональному и логическому программированию), пусть и немного. Исключение — только какие-то совсем непонятные шараги.
Как проходят? Как у некоторых: по языку в семестр? Пользы от такого зоопарка никакой, только каша в голове.
А надо 5 лет лисп учить?
Полистайте https://lisp-lang.org/success/
Проекты на Лиспах писали еще в то время, когда никаких Гитхабов не было.
Помните, как Маяковский писал о Хлебникове? Есть поэты для читателей, а есть поэты для поэтов.
void *
.Все то, что так вас раздражает — это все не от «динамики» типов, а от их «weakly typed» :-) Которая, вполне себе, может быть и статической при этом. Обратное, кстати, тоже верно… в том смысле, что динамическая типизация ортогональна сильной. И языки с сильной динамической типизацией вполне себе есть.
Так что, «динамика» тут вообще не причем… даже в плане «статических проверок». Динамическая типизация, совсем не означает их отсутствия. А вот слабая типизация (хоть даже и статическая) — в лучшем случае — сводит их к, скажем так, слабо полезной формальности.
P.S. Весьма сильно улыбнуло противопоставление «динамики» и ООП. На всякий — вообще ортогональные вещи.
P.P.S. И да… *система типов* — это, судя по всему, не совсем то, что вы думаете.
А в этом интервью Рич на вопрос о динамической типизации ответил, что «уважает право разработчика делать что угодно»
Я большую часть времени пишу на Javascript, а иногда приходится нырять в Java и Typescript. Для себя нахожу вот такие проблемы.
Есть какая-то функция, например создание модального диалога
function createDialog({title, content, footer, onSubmit, onDismiss, ...andMore}) {
// do something
}
В типизированном подходе вам понадобится описать интерфейс DialogOptions и перечислить эти поля еще раз. А если внутри появится еще функция createDialogBody()
, которая будет принимать только часть параметров, то ей понадобится еще один интерфейс. Эта писанина утомляет.
При парном программировании с дизайнером нужно быстро перекидывать блоки туда-сюда, не особо задумываясь о безопасности типов. В таких ситуациях динамическая типизация приходит на помощь.
По моему мнению, на Typescript удобно оформлять уже сложившиеся продукты, например при переписывании легаси. Там понятные форматы данных и протокол взаимодействия. А для стартапа или проекта на хакатоне, где решения принимаются за несколько минут, а финальные форматы данных еще не ясны, излишняя типизация будет ненужной рутиной, которая будет стопорить творческий процесс.
function createDialog(string title, string content, string footer, void=>void onSubmit, void=>void onDismiss) {
// do something
}
Меня вот просто выводила из себя (до желания стучаться головой о стенку) ситуация, когда я смотрю код на php или js, там какая-то функция с аргументами, или просто какие-то переменные, и я НЕ ПОНИМАЮ, каких они типов. Чего мне ждать — числа, строки, структуры (если структуры то какой, с какими полями каких типов), функции (опять же с каким списком аргументов)? Что я имею права с ними делать а что нет? Ну хорошо, что в вебе в основном строки. В основном — но не всегда. Вот честно, не представляю как вы с этим живете :)
Я тоже не люблю лишнюю писанину. Но только ли в этом вопрос?
А если эта функция используется одним разработчиком в тестовом проекте и в одном месте?То нужно её тело вставить в то место, где она используется и не парить нокому мозг.
У такого размазывания по аргументам есть и обратная сторона. Функции с большим числом аргументом считаются плохим кодом и предлагается избавляться от них созданием DTO. Вот эти DTO как раз утомительно писать, особенно в Java, где на каждое поле еще и геттер/сеттер нужно создать. Да, в редакторах есть макросы, но все равно получается простыня кода просто для того чтобы передать данные из одного модуля в другой.
Вот честно, не представляю как вы с этим живете
Читаем документацию, вставляем параметры как там описано. Еще современные редакторы (IDEA и VSCode) понимают декларации Typescript и показывают их для JS кода. Таким образом можно облегчить себе работу с библиотеками, не обкладывая при этом типами весь код целиком.
P.S. Я в целом, не против типов и тоже их умеренно использую. Но начинать новый проект с описания интерфейсов я не могу. Сначала накидаю кода, чтобы он хоть как-то запускался и выполнял задачу, а потом уже можно о типах задуматься, когда понятно, что проект в целом на верном пути.
Плохой код — это не когда много аргументов, а когда много зависимостей. DTO — это так, украшательство. Ну, точно можно отложить до «когда понятно, что проект в целом на верном пути».
> А сколько времени займет построение графика по данным из JSON на том же C#?
Вы, наверное, подразумеваете, что надо типы создавать под JSON, и это долго? В том же C#, внезапно, есть dynamic.
dynamic stuff = JsonConvert.DeserializeObject("{ 'Name': 'Jon Smith', 'Address': { 'City': 'New York', 'State': 'NY' }, 'Age': 42 }");
string name = stuff.Name;
string address = stuff.Address.City;
dynamic достаточно экзотическая штука, неудобная и опасная. Ну вот для «накидать график» она подходит, а как только возникнет желание, например, входные данные валидировать — то, наверное, уже тип опишите. И в любом случае оно вряд ли покинет границы функции.
динамический язык — достаточно экзотическая штука, неудобная и опасная. Ну вот для «накидать график» она подходит, а как только возникнет желание, например, входные данные валидировать — то, наверное, уже тип опишите.
Так что я исключительно про dynamic+C#+мозг людей привыкших к статике.
Просто исторически сложилось, что статически типизированные языки многословны.
Но в 21 веке можно получит и то и то бесплатно. Например
type Simple = JsonProvider<""" { "name":"John", "age":94 } """>
let simple = Simple.Parse(""" { "name":"Tomas", "age":4 } """)
simple.Age
simple.Name2 // compile-time error: no field or property 'Name' found
Тогда пишется функция, где пишется return. Goto ни в одном проекте, слава богу, не встречал.
Тогда пишется функция, где пишется return. Goto ни в одном проекте, слава богу, не встречал.
ОЧЕНЬ быстрые корутины на дотнете написаны с помощью goto
А еще yield return/ async-await разворачиваются в стейт-машины на goto, но это не повод так писать в проде.
Не говоря о том, что если так было написано, это не гарантирует, что это оптимальный код.
Очень приятно. https://habr.com/post/348074/
1) проверка инвариантов — тут да, проверили, false, досвидания
2) из логики функции. И вот тут такое себе. А успели ли мы подготовить данные? А корректно ли всё закрыли? И записали ли про выход в лог? И тут ради раннего возврата начинают какой-нибудь try finally накручивать на ровном месте (к вопросу об избавлении от вложенных блоков)…
Вот нет ощущения, что это всегда прям хорошо.
Читаем документацию, вставляем параметры как там описано.
Тут выше комментатор жаловался, что ему лень отдельный тип писать — а вы ему, фактически, предлагаете отдельную документацию на функцию сочинять.
Впрочем, по поводу хакатонов соглашусь. Если скорость важнее качества — то ДТ возможно будет лучше.
вы рискуете потерять гораздо больше времени на всяких дурацких моментах
До таких дурацких моментов еще нужно дорасти, и не факт что это случится. На JS можно быстро накидать интерфейс (какой-нибудь отчет с графиками, например) и показать его людям. Примерно для тех же целей аналитики используют Jupyter Notebook с динамически типизированным Python.
А сколько времени займет построение графика по данным из JSON на том же C#? Без сарказма, правда интересно, как бы вы это сделали и какие библиотеки использовали.
Касательно инструментов для построения графиков и анализа данных — конечно, у Python инфраструктура получше, однако и дотнет кое-что может. Например, в Azure Notebooks поддерживаются как Python/R, так и статически типизированный F#.
Лично я для исследовательских целей использую LINQPad — это REPL-среда для C#/VB/F#. Там можно использовать все, что есть в самом языке\фреймворке (LINQ, любые Nuget-библиотеки), и плюс встроенные утилиты (отображение таблиц, графиков, подключение к БД). Отрисовка графика по JSON выглядит примерно так:
![](https://habrastorage.org/getpro/habr/comment_images/6ed/eb1/0ec/6edeb10ec4091ce6ca244d457a9a4dd4.png)
К слову сказать, советую почитать про провайдеры типов. Оно пока есть только в F#, но концепция поразительно мощная. Например, если ваше приложение скачивает JSON по ссылке — IDE подскажет вам те свойства, которые в нем действительно есть (отправив запрос к серверу), если отправляете SQL-запрос — увидите список полей в таблице (или ошибку, если в запросе опечатка). Вот гифки с примерами работы.
Про использование и не использование типов я уже свою мысль донес. Есть ситуации работы с произвольными данными, и на таких динамическая типизация и оправдывает свое существование.
А правила написания читаемого и понятного кода примерно одинаковы для любых языков, статических и динамических. Им лучше, конечно же, следовать.
Что я буду делать в JS? Т.е всеравно TypeScript вам тут нужен будет если проект не прототип, и даже не факт что все эти IDE современные по TS помогут вам сделать то же самое что с системой типов элеиментарно уже десятки лет IDE делает.
В современном Javascript активно рекомендуется к использованию синтаксис import something from './something'
. Он отлично статически анализируется и найти все места использования something не составляет труда. Работает в том числе и автоматическое удаление/добавление импортов.
А с поиском использования типов все еще лучше: вам не придется искать, где именно используется этот тип, если у вас не используются типы вообще.
Был X,Y, надо Z добавить. Удачи добавлять. Искать не надо вам. Ну да без поиска попробуйте.
давайте уникальные имена вашим функциям в JS!
Пока все прогрессивное человечество стремится к простоте и наглядности наименований…
И допустим я не знаю имя функции. Я знаю что у меня есть сущность у которой X,Y координаты, и мне надо Z добавить. Какую функцию мне искать? Все которые работают с объектом у которого есть X,Y, но пока нет Z. Без семантики — кучу головной боли.
И да такие задачи сплошь и рядом в реальной разработке. Пример с X,Y,Z для упрощения. Реально бывает кучу других задач по расширению кол-ва свойств у сущности. А если вы сущности не ввели явно, а держите их в голове — ну удачи пройтись поиском.
И пример на самом деле не релевантный — вы же добавили Z с какой-то целью, вот там, где вам Z понадобилось — там и меняйте. Сразу видно, что на JS не пишите, новые свойства вообще не проблема (если не использовать ...), намного большая проблема, если вы удалили/изменили существующее свойство Z. Как узнать, что оно нигде не используется? Даже если вы нашли все места, что если в соседнем бранче новую функцию написали, в которой по прежнему Z используется? Это никак не обнаружится при мереже, компиляции нет… ну вы понимаете, только тесты. То, что вам может прийти любой мусор — вот это проблема.
давайте уникальные имена вашим функциям в JS
Ага. Ну, к примеру, pipe, it, connect, put, dispatch и другие очень емкие и уникальные словечки из современных хипстерских библиотек. Сколько у вас будет совпадений со словом «put»? Или «it». А еще у нас есть куча нод-модулей, где все функции повторяются по несколько раз, у этих функций разные версии и ты точно не можешь сказать, какая именно из этих используется. Знаете, иногда типизация TypeScript ломается. И тогда я беру тазик и могу наблевать в него до самых краев пока разберусь во всем при помощи вашего совета.
blazor есть, но тоже не production ready
А как вы исполняете C# в браузере?В браузере, увы, никак. Под браузер я пишу под ТС.
А в скриптах на 50 строк никаких библиотек никогда не используетеДа. Если скрипту нужна библиотека — значит он уже достаточно большой, чтобы использовать TS.
Нет, согласен, для скрипта в сотню-другую строк, да который ещё и пишется на один раз, можно особо не заморачиваться. Но в более серьезных случаях…
В этом и суть. С типами у вас каждая задача – "серьезный случай". В JS типы можно принести позже, когда совсем прижмет. Или не приносить, если и без них нормально, как в d3 или jsplumb.
В d3 исходники разбиты на маленькие модули (например). Эта модульность работает почти как типизация (вы не сможете вызвать функцию, если она не импортирована), только без сигнатур методов. Видимо, автору и без них понятно, что где возвращается, просто по их именам.
В jsplumb все похуже. Иногда там используются аннотации типов через комментарии, но очень мало. Если бы я работал над тем проектом, я бы предложил им перейти на Typescript, там это оправдано.
В d3 исходники разбиты на маленькие модули (например). Эта модульность работает почти как типизация
Вот за эту подсказку спасибо. Надо будет поизучать. Тогда может врублюсь, как народ на этом пишет.
О да я глянул только что вашу ссылку на arc
github.com/d3/d3-shape/blob/master/src/arc.js
Это жесть, куча копипаста в духе
" return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
И всеравно не понятны типы возврата. Тут будет приведение к int или float будет или строка? И в куче аналогичных случаев тот же вопрос.
А когда возврат структуры — вам еще внутри функции покопаться
И только не говорите что неважно. Вы с таким же успехом тогда аргументируете к вопросу «вот вам массив байт — в нем что — целое число и вещественное и с какой точностью, и какая в нем endianness если он сериализован, и что там за переменные в ней и каков размер массива если это он». А вы еще на функции будете проверять (по духу d3). А в колбеках будете выяснять что у вас в аргументах — строки (которые легко уже превратяться в int или float по вашей надобностью) или JSON сериализованная форма (и какого объекта серилизованного) — что даже в этом случае — строка всеравно, а семантика разная. Ну не дураки же придумали семантику. Ну потратите лишнее время на выяснения.
**
function arc() {
var buffer,
r,
r0
r1
и
export default function() {
var innerRadius = arcInnerRadius,
outerRadius
Это и есть объект. И система типов вот она. Даже автор d3 вводит. Только костылями, без явного объявления class (как в подмножестве 2016 или 2015 года не помню уже)
В компьютерной памяти, даже в интерпретаторе, не может «не быть int», можно либо представлять все байтами — что низкий уровень. Но мы же с вами говорим о Самантике. Оно либо целое число, уже приведенное, Из строки. Или это Функция принимающая int. Или любое другое число и не факт что уже округленное. Это семантика.
Но зато копипаст и дичь в духе
" return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
отличная. Тут автор d3 видимо с вами не согласен. Потому что он за каким то фигом проверяет function а в другом месте он может проверять на строку.
Давайте везде понатыкаем проверок на function. На всякий случай округлим числа, ведь у нас математика бывает целочисленная «по задаче надо округлить». А еще из строки будем переводить в int (parseInt и в таком духе) (вы наверное и не знали о ней? нафига вот ее придумали? строки везде? или объекты? да черт ногу сломит, но int у нас нету). И на всякий это в каждой функции сделаем. Лишь бы чтоб все можно было везде передать. Только не говорите что это не нужно. Тогда у вас все сломается, если «у нас нет int». А что у вас есть?
В компьютерной памяти, даже в интерпретаторе, не может «не быть int»В интерпретаторе — может быть. А в JS — вот так. Там просто нет типа
int
. Не предусмотрен. И float
не предусмотрен. Единственный «числовой», «базовый» тип — double
. Который возвращается унарным плюсом! То есть в JS +"123.45"
— это 123.45
и даже +null
— это нуль!Там что всё в порядке там с типами — сплошные
double
везде." return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
т.е у нас появляются внезапно и функции аргументы, а еще это может быть объект с заданными полями или массив.
Так вот попробуйте сначала почитать исходники любого норм написанного проекта, да хоть d3, и увидите что там стремятся как раз явно описать типы. И да меня похоже многие не понимают, я в комментах и о пользовательских типах пишу, и их тоже прописывать лучше явно, что это Arc с такими-то полями, а не что это хз что.
Только не берите C/C++ — неплохие языки, но количество ногострелов, прилагаемых «в комплекте», что называется «зашкаливает»…
А мне непонятно зачем его брать. Думаю, за месяц-два я легко перейду с PHP на Java/C#, как когда-то перешёл на PHP и JS с C/C++, пускай и не все их возможности знать буду. Но зачем? Что это даст мне лично и что бизнесу, на который я работаю? Ну вот есть у нас сейчас TS — за почти полгода работы ни разу он мне не помог обнаружить какой-то баг.
Ну вот есть у нас сейчас TS — за почти полгода работы ни разу он мне не помог обнаружить какой-то баг.
Ух. Первый негативный отзыв о TS который мне попался на глаза. Реквестирую статью про это :)
А вы не думали, что он, вместе с автодополнением в IDE, мог вам помочь этот баг не написать?
Не заметил существенной разницы между JS с ключом only type based competition и TS в одной и той же IDE
Хм, может быть вы просто не писали достаточно сложного кода? Или не пользовались библиотеками с нетривиальной типизацией?
Мне вот достаточно написать $.extend(Foo.prototype, { ... })
— и IDE уже не может понять что все методы внутри фигурных скобок могут обращаться к свойствам Foo в контексте this.
Или viewModel.BeginPeriod.typed
— и IDE перестает понимать что происходит, потому что свойства typed у ko.observable нету, а тайпинг через jsdoc чужому классу не добавить.
Стараюсь не писать сложного кода — KISS и всё такое :) И использовать библиотеки, о которых знает моя IDE (пускай она и использует для своего знания объявления на TS)
Но если ваша IDE использует тайпинги от TS даже для JS — вы уже не можете использован аргумент "Не заметил существенной разницы между JS с ключом only type based competition и TS в одной и той же IDE" в качестве доказательства ненужности TS.
Я не утверждаю, что TS не нужен в принципе никому и никогда. Решили разработчики IDE использовать существующие TS тайпинги для улучшения работы их IDE со стандартными или популярными JS-библиотеками — видимо нашли это полезным, я даже могу предположить почему.
Но вот при написании своего кода я особой разницы не замечаю.
Чем качественно хуже статические анализаторы динамических языков, которые точно так же не дадут, например, сделать коммит?
Либо они являются концептуально статически типизированным языком, написанным поверх динамического, либо они слабее поддерживаются или менее выразительны (т.е. например там нет каких-то аннотаций для типов).
Чем концептуально статически типизированный отличается от обычного?
Думаю, тем что там есть развитые инструменты для описания типов. Т.е. можно декларировать типы переменных значений и т.д.
Во-вторых обязательностью. Как только вы делаете проверку обязательной у вас уже получаются ситуации которые работали бы вполне пока бы вы не вошли в специфический кейз, но статическим контролем будут запрещены.
Они широко используют автоматический вывод типов, а также информацию о типах, имеющуюся в синтаксисе типа function name(string $param) или в *doc-аннотациях.
Какой тип можно выводить из чего, когда типов нет? Как только речь не идет о строчках и числах, то пиши пропало. Повсюду объекты с хрен его знает каким полями и функциями. В любом случае, даже все это это костыли по одной простой причине — динамическая типизация мешает и всяческими способами это пытаются решить. Неудивительно в этой ситуации, что typescript имеет такой взрывной рост в одного из лидеров сразу — он пришел в нужное место в нужное время, когда JS приложения переросли объем, когда с ними можно адекватно работать со всей этой динамикой.
Если придерживаться определенных правил при написании, то анализатору будут известны поля и функции объектов. Грубо, нужно писать на JS в стиле на который не заругается компилятор TS, полагаясь только на автовывод.
TS увеличивает порог входа в проект. Так уж исторически сложилось, что знаний одного TS недостаточно для полноценной разработки, нужно и JS знать. И тут вопрос возникает — а нужен ли на конкретном проекте TS, даже если проект новый, про легаси совсем молчу.
У TS есть плюсы, но есть и минусы.
Что интересно, я не нашёл полноценных учебников TS в своё время, в которых JS вообще не упоминался бы (ну пускай "под капотом компилируется в JS, но вам это не нужно", как может быть в учебнике по C про машинные коды или в учебнике по Java про байт-код). В подавляющем большинстве литературы речь о различиях, и подразумевается что JS уже знаешь. Разве что классы описаны "с нуля", но это как я понимаю, потому что по факту они пришли в JS из TS.
Что, кстати, огорчает, ибо в учебниках по JS я встречал достаточно мало информации насчет грамотного построения архитектуры ПО в контексте фронтенда, в стиле «Think in Java» и других хороших книжек.
Помог незаметно как-то?
Вот что значит "не дали написать"?
А компилятор — тот же стат. анализ, только не отключаемый.
Я не пойму про какой сценарий речь идёт. Мой:
- пишу код на TS
- запускаю компилятор
- не вижу ошибок
В какой момент мне не дали создать баг?
Тогда вы не совершаете ошибок типов, что ведет нас к двум возможным вариантам:
- Вы просто не пользуетесь типами. В стиле "напихаем везде any и компилятор доволен".
- Вы не совершаете ошибок. В таком случае мои поздравления, всегда мечтал познакомиться с таким человеком.
В итоге вы, например, не сможете случайно в процессе «получения сообщения» вызвать метод «end()».
Я не не смог, а просто не пытался выносить логику на уровень типов в чём отличном от песочниц, не вижу в этом смысла. В логике типов ошибиться гораздо проще чем в простом императивном коде, а отлаживать гораздо сложнее — банально в отладчике не посмотреть.
Ну так вам говорят, что это полезно, вы говорите «нет смысла». Смысл в том, что можно написать работающую программу с первого раза, вообще ни разу не запуская ни под отладчиком, ни вообще в принципе.
Собственно этот код выглядит на первый взгляд как смесь «ФП головного мозга» и «статической типизации головного мозга» — я лучше со своей смесью «ООП головного мозга» и «динамической типизации головного мозга» останусь — она гораздо ближе к натуральному мышлению человека, по крайней мере к моему мышлению.
Бытие определяет сознание. Программист на фортране способен программировать на фортране на любом языке программирования. Ваше мышление сформированно динамическими языками, и вам неприятно выходить из зоны комфорта. Но это необходимо, если вы вдруг хотите развиваться как разработчик.
Я уже не раз говорил, что игры с F# перестроили мое мышление, и позволили писать на моем обычном C# намного более структурированный и красивый код.
P.S. к этому
В логике типов ошибиться гораздо проще чем в простом императивном коде,
В логики типов ошибиться намного сложнее, потому что типы универсальны, а императивный код может не работать только для некоторых значений. Вторая причина — императивный код нужно проверять самостоятельно, а типы проверяет компилятор. Если типы сошлись, то программа корректна и в условиях тех ограничений, которые наложены, ошибок нет. Чем плотнее и больше ограничений наложено, тем больше выводов можно делать из того, что программа скомпилировалась.
В логики типов ошибиться намного сложнее
В логике типов ошибиться сложнее, потому что эта логика обычно не скрывает ошибок. То есть типы с ошибкой либо просто не работают в целом, либо работают сразу правильно.
Ну вот как какой-то тип с ограничением типа value > 0 "поймёт", что я решил почему-то что слово "неотрицательный" эквивалентно слову "положительный"?
«неотрицательный» — это
value >= 0
«положительный» — это
value > 0
От понимания двух этих слов человеком (равно как от отсутствия понимания) в коде программы ничего не зависит.
Вообще говоря, если определить функцию
doTimes(положительный times)
,а потом Вася попытается её вызывать как
doTimes(0);
doTimes(-1);
и его код не скомпилируется, то виноват Вася, а не компилятор.
В задаче говорится про неотрицательные, я поставил условие >0. Чем тут поможет типизация?
Ну так вам говорят, что это полезно, вы говорите «нет смысла». Смысл в том, что можно написать работающую программу с первого раза, вообще ни разу не запуская ни под отладчиком, ни вообще в принципе.
Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции? Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS.
Ваше мышление сформированно динамическими языками, и вам неприятно выходить из зоны комфорта.
Мое мышление сформировано Pascal, С и C++ в то время когда JS-ом или PHP ещё не пахло.
Я уже не раз говорил, что игры с F# перестроили мое мышление, и позволили писать на моем обычном C# намного более структурированный и красивый код.
Я уже несколько раз намекал, что я на динамических языках пишу так, чтобы максимально облегчить статанализаторам вывод и контроль типов. Если и использую сугубо динамические фичи, то с чётким осознанием плюсов и минусов.
В логики типов ошибиться намного сложнее, потому что типы универсальны, а императивный код может не работать только для некоторых значений. Вторая причина — императивный код нужно проверять самостоятельно, а типы проверяет компилятор. Если типы сошлись, то программа корректна и в условиях тех ограничений, которые наложены, ошибок нет. Чем плотнее и больше ограничений наложено, тем больше выводов можно делать из того, что программа скомпилировалась.
Вот в ограничениях ошибиться гораздо проще, а понять где ошибся гораздо сложнее. Опять же я равниваю исключительно JS и TS — не знаю что там с F#, но всегда был уверен, что он про ФП, а не про типизацию, типизация у него такая же как у С# — строгая статическая. И, кажется, мы по разному трактуем слово "ошибка" — мне почти без разницы вылезла ошибка в компайлтайме или в рантайме. Если вылезла — значит допущена 100% и её нужно исправлять.
Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции?
Разница огромна, если программа работает дольше времени компиляции или на удалённом компе. Очень печально осознавать, что обучаемая несколько часов нейронная сеть не была сохранена просто из за-того, что в какую-нибудь функцию передали не то количество параметров или ошиблись с их типом.
Причём может быть, что в одной ветке ветвления всё хорошо, а в другой — нет. И вроде бы работающая программа может оказаться не очень работающей, если что-то не совпало.
Рефакторить более-менее большой проект на питоне или на java/scala — тоже огромная разница. В статических языках та же IDEA хорошо справляется и можно совершенно свободно менять что хочется. Если не справляется — я узнаю об этом во время компиляции и тут же поправлю. С питоном среда разработки от тех же разработчиков справляет плохо и банальное переименование переменной/поля оставляет в различных местах "сюрпризы" на будущее.
Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS
Какое тестовое покрытие у вашего кода?
Близкое к 100%. Отдельных тестов на типы нет, если что.
А в фоне тесты гоняются (типа https://wallabyjs.com/ )
А ведь все тесты можно было бы поменять на типы, и только поднять качество кода… )
Как вы типами проверите реакцию системы на невалидные данные, пришедшие с базы или от пользователя? Или как вы типами проверите сложный численный метод, принимающий число и возвращающий число? Или система типов позволяет что-то вроде type A = tan(sqrt(number)) писать?
Или система типов позволяет что-то вроде type A = tan(sqrt(number)) писать?
Позволяет, почему нет?
Как вы типами проверите реакцию системы на невалидные данные, пришедшие с базы или от пользователя?
Валидируете один раз IO, дальше все работает с точными данными.
Не закончил мысль, не просто позволяет ли, а даёт ли гарантии, что для пи в квадрате значение будет 0?
Валидировать-то я могу и без типов. Как без хаков типа явного приведения или тайпгвардов прийти от почти нетипизированного json к какому-нибудь Dictionary<EntityId,<Model<Entity>>>
?
Не закончил мысль, не просто позволяет ли, а даёт ли гарантии, что для пи в квадрате значение будет 0?
При желании можно выразить. Я уже говорил, те же завтипы позволяют компилятору выводить утверждения в стиле "положительное число, делящееся на 3, от 14 до 25".
Валидировать-то я могу и без типов. Как без хаков типа явного приведения или тайпгвардов прийти от почти нетипизированного json к какому-нибудь Dictionary<EntityId,<Model>>?В том, что в IO (точнее в I, O то нас не интересует, т.к. переход к более слабой форме представления всегда успешен) это 1% программы, а остальной код работает с конкретной структурой, где гарантированно есть поле User, где есть поле first_name, которое является валидной UTF8 строкой длиной до 30. А если это ВДРУГ нарушается, то оно не валится хрен пойми где, а отваливается на самой границе [не наш код / наш код], и сразу понятно, в чем проблема, потом что контекст еще не потерян.
даёт ли гарантии, что для пи в квадрате значение будет 0?
А что вообще даёт такие гарантии (учитывая непредставимость пи в виде любого машинного числа), кроме чисто символьных вычислений? А их в типах вполне можно выразить, просто это самое пи будет подтипом числа, а функции — операциями на типах.
Пускай не пи, а 180 градусов (функции соответственно в градусах принимают)
То есть Вас удовлетворит, к примеру, tan(sqrt(pi^2)) = 1.45291e-16 (число от балды, лишь бы примерно порядок соблюсти)? Тогда что Вы имели в виду под "гарантиями" в самом начале? А то, что тест запросто может не пройти, — факт из практики: в Ruby Math::tan(Math::sqrt(Math::PI * Math::PI)) == -1.2246467991473532e-16
(в версии 1.92, по крайней мере, с которой я работал).
Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции? Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS.
Вы всегда способны продебажить все возможные состояния всей программы? Тогда я даже не знаю, если не совершать ошибки еще можно (я сам пишу на солидити, где нет дебаггера, со средней скоростью 1 небольшая ошибка на неделю работы), то подобном уровню аналогов просто не вижу.
Это очень весело, когда на том же питоне пишешь код и через год его исполнение внезапно доходит до какой-то давным давно написанной строчке, которая до этого ниразу не вызывалась. Как назло там вывод диагностики в лог, которую я потерял.
Так я о том, что случайные опечатки мне компилятор не ловит.
Просто человек всегда помнит имена всех свойств и методов, и никогда не опечатывается. Бывает ведь.
без явного их указания
...
var BaseClass|string $clasname
Вас ничего не смущает?
Нет.
Нет на этапе компиляции у меня таких ошибок. В основном инфраструктурные типа забыл добавить новый файл в tsconfig.json или мерж неудачно прошёл нарушив синтаксис.
Справедливости ради хорошие IDE нормально работают с (некоторыми) динамическими языками
Какие например? Я использовал поделия от JetBrains для js и python. Говно-говном. Хотя JetBrains реальный монстр, у них пожалуй самые сильные алгоритмы анализа кода. Просто они не волшебники. И если типы не выводятся в принципе, абсолютно логично что они их вывести и не могут. Тут не JetBrains нужен, а Гендальф!
А вообще со своим нынешним хобби-проектиком (надеюсь правда это продавать !) сижу сейчас на vs code и доволен как слон. Там у меня ts + php. Всё взаимно дружит и отлаживается в рамках одного проекта, тока в путь! :)) Хочу сейчас в добавок научиться писать для vs code свои расширения, как я это делаю для эклипса.
JetBrains прежде всего, да. Особенно если им всякие *doc аннотации о типах скармливать там где они вывести не могут.
А для PHP тоже говно-говном? Концептуально он мало чем от JS отличается же с точки зрения статанализа: интерпретируемый язык со слабой динамической типизацией. И даже typehints по сути сахар для if (is_string($param)) throw new TypeError();
interface IData {
title?: string,
content?: string,
footer?: string,
onSubmit?: Function,
onDismiss?: Function
}
function createDialog(data: IData) {
console.log(`data: ${data.title} , ${data.content}, ${data.footer}`)
}
createDialog({title:"Hello"});
createDialog({title:"Hello Again", content:" Content Text"});
/*
в консоли будет
data: Hello , undefined, undefined
data: Hello Again , Content Text, undefined
*/
interface IArticleData {
title?: string,
content?: string,
}
interface IArticleFullData extends IArticleData{
footer?: string,
onSubmit?: Function,
onDismiss?: Function
}
function createDialog(data: IArticleFullData) {
console.log(`data: ${data.title} , ${data.content}, ${data.footer}`)
}
IArticleFullData — это полный аналог IData из предыдущего примера
Такое решение через наследование или обертывание одного объекта в другой не всегда хорошо, если эти объекты не связаны. Просто, так совпало. Если потом DialogBodyOptions потребует еще одно значение, то оно внезапно высветится и в DialogOptions, а это может быть нежелательно.
Поэтому лучше держать эти типы отдельно и мириться с многословностью. Особенно, если учесть, что синтаксис в С# и Typescript более компактный, чем в Java. Может быть и там когда-нибудь сахар из Lombok включат в основу языка.
Утверждение "как только тайпчекер доволен, всё сразу работает" сильно переоценено.
Допустим, у вас есть некоторый счетчик в коде
var numberOfProcessedItems = 0;
for(const item of items) {
if(matchesCondition(item)) {
// тут много кода
numberOfProcessedItems++;
}
}
Внезапно, после неудачного рефакторинга или мерджа, у вас может проходить инкремент дважды
var numberOfProcessedItems = 0;
for(const item of items) {
if(matchesCondition(item)) {
numberOfProcessedItems++; // раз
// тут много кода
numberOfProcessedItems++; // два!
}
}
Компилятор счастлив, ошибки в типах нет, а баг в коде есть. И шансы накосячить таким образом намного выше, чем попытаться прибавить строку к этому числу.
// тут много кода < — проблема тут
Когда в IDE код легко поправить в
var numberOfProcessedItems = 0;
for(const item of items) {
if(matchesCondition(item)) {
ProcessItem(item);
numberOfProcessedItems++;
}
}
то и проблем, внезапно, не проявляется. У вас просто не будет правок кода тут — ни рефакторинга, ни проблемного мерджа.
И проблема именно в IDE: когда рефакторинг делается в один клик и без ошибок — руки сами тянутся мусор спрятать. А когда мне приходится работать в блокноте — у меня тоже «много кода» появляется.
> А с поиском использования типов все еще лучше: вам не придется искать, где именно используется этот тип, если у вас не используются типы вообще.
А когда IDE не помогает — приходится себя убеждать даже от типов отказываться )
Именно эту мысль я хотел донести: типы избавляют вас не от всех багов, а только от части проблем, ценой затрат на описание этих самых типов. На определенном количестве и сложности кода эти затраты окупаются, а иногда нет. В таких случаях динамические языки (или динамические типы в строгих языках) оказываются удобнее в работе.
Не будет. Типы декларируются, а не исполняются, то есть не имеют временнОго параметра. Выставив время за скобки, вы убираете целое измерение из проблемы. То есть вы создаете правила, которые работают всегда, а не только в конкретный момент исполнения.
Вот наприме метод:
ApiResult<PostCommentResult> PostComment(PostCommentRequest request);
Как бы вы не хотели, вы обязательно верно интерпретируете аргумент и вернете верный результат. Вы не можете вернуть строку или WebResponse или что-еще. Если метод выше требует ApiResult то вы обязательно вызовете этот метод, потому что иначе нужный объект вам не получить. Сам факт того что кто-то требует этот тип уже подсказывает вам что нужно сделать.
Это часто проявляется в работе с новыми библиотеками или незнакомыми исходниками. Не нужно лезть в доки для каждой новой вещи, нужно только найти тип который вам нужен и найти как его получить. Правильно сконструированный АПИ позволит это сделать только правильным образом.
В динамической декларации вы не знаете что должны передать и что получить обратно. Вы может даже не догадаетесь что этот метод нужно вызвать, так как объекты вокруг не могут сообщить вам что им нужно для работы.
Зато этот метод может запросто вернуть константу, которая никак не зависит от входного аргумента — и ваша система типов никак это не отследит.
Система типов сделана не для компилятора, а для вас, что-бы вам работать легче было. Если вы специально пишите неверный код, то зачем вам ЯП? Переименуйте любой файл в экзешник и пусть он неработает.
Но вот это ваше утверждение остается неверным:
Типы, в идеале, должны ограничить вас настолько, что единственный код который вы сможете написать будет верный.
В идеале. Я не просто так это слово использовал. На практике конечно нужно найти золотую середину, как и во всем.
Не превосходит, а максимум обладает той же сложностью. На самом деле меньше, потому что еще нужно описать процессы (время), они будут описаны уже исполняемым кодом.
В динамической типизации вся сложность находится в исполняемом коде. В статической она поделена и с ней проще работать.
Ваша… Никакая система типов не отследит константу возвращает метод или нет. Это уже не зависит от системы типов в принципе.
Вопрос же не в этом. Данный метод сразу нам говорит, что он вернёт объект конкретного типа и знать мы об этом будет ещё на стадии написания программы, а не в рантайме.
А я хотел донести мысль, что сложность и бажность зависит не только от типов, а от инструментов и практик. Если инструменты под типизированный язык есть, а под нетипизированный нету, то баланс сильно смещается в сторону «я лучше var name = (string)json[»name"] напишу, чем с динамикой свяжусь".
А корреляция между типизацией языка и доступностью инструментов есть.
В типизированном подходе вам понадобится описать интерфейс DialogOptions и перечислить эти поля еще раз.
Нет, можно написать вот прямо так как вы написали:
function createDialog({title, content, footer, onSubmit, onDismiss, ...andMore}) {
}
и выведет автоматом тип: createDialog: { [x: string]: any, title: any, content: any, footer: any, onSubmit: any, onDismiss: any } -> void
и потом можно написать createDialog({title: 1, content: 2, footer: 3, onSibmit: 4, foo: 6, bar: 7}) — ошибка, отсутствует onDismiss, createDialog({title: 1, content: 2, footer: 3, onSibmit: 4, onDismiss: 5, foo: 6, bar: 7}) — все хорошо.
Это будет не выведение типов, а implicit any, который отключается в strict-режиме Typescript. Именно возможность такого any где попало автор статьи и большинство комментаторов и называют происками Javascript в системе типов.
А в strict-режиме в этом коде будет ошибка, вот такая. Надо будет либо поставить any руками, либо описать типы по-нормальному.
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.
Вообще-то научные исследования говорят обратное: что защищённость через покрытие выше, чем через сильную типизацию.
То, что тесты, добавленные к системе типов сделают результат надёжнее — достаточно очевидно.
А вот что они могут заменить систему типов — не видел и не слышал ни разу, извините.
тесты защищают от ошибок типов и ещё многих других ошибок
конечно, если есть время и деньги, можно делать двойную защиту от ошибок типов: и системой типов и тестами
тесты защищают от ошибок типов и ещё многих других ошибокНесуществующие тесты не защищают ни от каких ошибок.
конечно, если есть время и деньги, можно делать двойную защиту от ошибок типов: и системой типов и тестамиА если денег нет — то можно писать тесты только для проверки на ошибки, не покрываемые системой типов. И это — будет быстрее и дешевле, чем писать на языке, которые требует написания тестов ещё и для обнаружения проблем с ошибками типов.
Ровно это, в общем-то утверждается в статье — и ровно это вы грозились опровергнуть.
Несуществующие тесты не защищают ни от каких ошибок.
зачем это здесь сказано? ситуацию отсутствия тестов здесь никто не упоминал до сих пор
ситуацию отсутствия тестов здесь никто не упоминал до сих порТо есть основную, ключевую и самую важную часть мы вынесем за скобки? Чего тогда обсуждать?
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.Перевожу с русского на русский: «защищенность через покрытие — иллюзия» == «никто не гарантирует вам, что вы покроете все случаи тестами». И ешё: «тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов» — если таки прочитать всю статью целиком, а не видирать из неё пару слов, то можно будет заметить там такой пассаж: «Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.»
Откуда видно, что автор категорически против приёмов, которые позволяют «обмануть» тайпчекер — что указывает на то, что он видит преимущество TypeScript именно в том, что проверки типов покрывают весь код, в то время как тесты могут этого не делать. А не будут они этого делать только в одном случае — когда они не написаны.
Про остальные темы я не подписывался спорить.Рекомендую всё-таки спорить со статьёй, а не воевать с ветряными мельницами…
Про остальные темы я не подписывался спорить.
Рекомендую всё-таки спорить со статьёй, а не воевать с ветряными мельницами…
К сожалению, минусом этого подхода является то, что видя своё уязвимое положение касательно какого-то аспекта, собеседники слишком часто не доводят его обсуждение до конца, а переключаются на другой аспект. Я помню о том, что аспектов много, но начав разбирать один, нужно это и закончить.
Переводить с русского на русский не нужно, каждый может перевести по-своему. Что написано, то и обсуждаем.
А если денег нет — то можно писать тесты только для проверки на ошибки, не покрываемые системой типов. И это — будет быстрее и дешевле, чем писать на языке, которые требует написания тестов ещё и для обнаружения проблем с ошибками типов.
когда я искал ссылку на статью, уже упомянутую выше, я видел исследования, которые и это тоже опровергают, но идти искать снова лень. Для разнообразия сторонники сильной типизации могут поделиться научными исследованиями вместо возмущённых постов. С данными в руках удобнее спорить.
Для разнообразия сторонники сильной типизации могут поделиться научными исследованиями вместо возмущённых постов.Вот, пожалуйста. Да, строгая типизация, сама по себе, панацеей не является (С/C++ несмотря на типизацию остаются самыми «опасными» языками… откуда и все попытки найти им замену), но если сравнить JavaScript и TypeScript — то разница очевидна.
Только проблема этого исследования в том, что оно меряет среднюю температуру по больнице у несравнимых проектов. И, кстати, оно пытается подтвердить вовсе не тезис «можно писать тесты только для проверки на ошибки, не покрываемые системой типов. И это — будет быстрее и дешевле».
Я же сослался на исследование, которое один и тот же проект без тестов и с тестами делал, и это гораздо более честное сравнение, на мой взгляд.
Я же сослался на исследование, которое один и тот же проект без тестов и с тестами делал, и это гораздо более честное сравнение, на мой взгляд.Это довольно-таки бессмысленное сравнение: я не видел ни одного человека, который бы утверждал, что тесты не нужны и их можно заменить на статическую типизацию. Собственно если вы бы вы не выдрали из контеста два предложения, а захватили ещё одно — прямо перед ними, то вопросов вообще бы не возникло:
Нельзя строить свою бизнес логику, основываясь на типах, но при этом ничего о них не знать в процессе разработки. Именно поэтому есть фронтендеры, которые покрывают свой код невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors.Как видите в том абзаце, который вызвал у вас отторжение речь идёт не о «тестах вообще», а о «невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors».
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.
И да — мне, как и автору статьи, кажется это бредом. Type errors должен проверять компилятор! Именно чтобы освободить время и силы для проверок ошибок другого рода.
Ага, здесь как раз тот случай, когда два человека по-разному поняли написанное. Моё понимание отличается, потому что я ни разу не видел юнит тестов, которые только type errors и проверяют. Я видел во множестве тесты, которые проверяют логику, ну и заодно типы тоже. По-моему, так и нужно делать. И в такой ситуации проверки компилятором добавляют мало пользы.
Раз так, то я сагрился на неправильно понятую цитату, а кроме неё мне в посте обсуждать нечего. Видимо, останемся при своих.
Моё понимание отличается, потому что я ни разу не видел юнит тестов, которые только type errors и проверяют.Ну дык это ещё хуже! Если вы проверили, что у вас всё нормально работает при задании какой-нибудь ставки в 3.0, 10.0 и 150.0 (проценты), но не проверили, что будет, если передать туда
null
или undefined
(которые запросто могут вылезти из-за того, что свойство в базе или формочке пропало из-за рефакторинга), то вы оставляете в программе потенциальную уязвимость.А назвать проверку на
null
«проверкой на логику»… мне кажется, что это перебор… это в чистом виде проверка на тип…Во 2х, я бы сказал что защищают они даже не в момент рантайма программы, а в момент думанья человеком. Вот я допустим читаю вашу JS прогграмму, я должен перечитать тысячи (потенциально) тестовых вариантов чтобы понять какой тип аргумента у вас в функцию передается и может ли там быть null и должен ли там быть int от 1 до 10 вместо enum общепринятого. Верно?
А еще мне документацию надо где-то отдельно от основного кода искать? Т.е не в коде я буду видеть
Move[] GetMoves (TypeOfShape) — для нормального ООП
а вот такое >>
function GetMoves (TypeOfShape)
причем тип возврата вообще не виден, и даже если задокументировать что вертает Move — вы уверены что это быстрей чем написать тип возврата? И тип аргумента?
Это очень популярная реакция людей на новую/непривычную концепцию, встречаю это регулярно. Все равно, что человеку, который всегда ходил пешком, показать велосипед. Мол, смотри, можно потратить немного времени, чтоб научиться кататься, а дальше будешь передвигаться гораздо быстрее, на что тот возражает, что мало того, что сначала надо потратить время, чтоб научиться кататься, так потом еще и велик рядом с собой катить — только быстрей устанешь.
Вы упускаете суть строгой статической типизации: она защищает от ошибок проектирования. Если система типов имеет еще и алгебраические типы данных, то при правильном проектировании она защищает от ошибок бизнес-логики. Это очень хорошо описано в книге Скотта Влашина "Domain Modeling Made Functional", настоятельно рекомендую.
защищённость через покрытие выше, чем ...?!
Она, мягко говоря, вообще про другое.
Кстати, замечу, что в отсутствие строгой типизации упомянутых в статье 2930 юнит-тестов, 100 тестов производительности и >400 интеграционных тестов было бы явно недостаточно, так как пришлось бы писать ещё пару тысяч тестов на то, что
DeviceFactory::createLightBulbDevice
действительно возвращает нечто, что ведёт себя в соответствии контракту LightBulbDevice
, а DeviceEvent::getType
никогда не возвращает строку.Тот же пример, что вы привели — относится скорее к доказательству того, что «если ходить в куртке и в шапке, то вероятность заболеть ниже, чем если ходить в одной куртке». Что, в общем, достаточно очевидно.
я отношусь к тем людям, которые считают, что порой потраченные на удовлетворение компилятора ресурсы мозга
Меня вот этот вот тезис всегда удивлял. Я могу понять, когда про потраченные ресурсы мозга заявляет человек, который пишет на идрисе. Или на хаскеле (хотя бы). Но если мы говорим о джаве? Или вообще в контексте тайпскрипта, в котором типы специально так спроектированы, чтобы при должны настройках компилятора код просто брал и работал? На что и кто там "ресурсы мозга" тратить собрался?
- тест на то, что при каждом вызове функции в неё будут переданы параметры именно того типа, который она ожидает.
- тест на то, что независимо от реализации функции, результатом её вызова будет нечто определённое.
Есть, конечно, случаи, когда по какой-то причине нужный Вам тип невозможно выразить в рамках системы типов некого языка, однако это, по моему глубокому убеждению, такие вещи являются скорее недостатком дизайна, и возникают не настолько часто, чтобы быть аргументом против типизации вообще.
Приведу пример (я буду предполагать что система типов моего языка достаточно развита, чтобы всё это поддерживать):
-
fun getLength
— всё, что мы можем сказать о этой функции — это её имя. Исходя из нашего опыта, мы могли бы предположить, что она возвращает(get) некую длину(Length), но чего — мы не знаем. Также мы не знаем, что такое этот Length на самом деле. Нам нужны несколько групп тестов для каждого из типов, которые мы потенциально собираемся передать в эту функцию в качестве аргумента. Исходя, опять же, из опыта, мы предполагаем, что длина — это неотрицательное целое число. Нам нужны тесты, проверяющие, что функция действительно возвращает число, что оно действительно неотрицательное, и что оно действительно целое (минимум три типа тестов, для которых мы не гарантируем полного покрытия, ведь всегда останется шанс, чтоgetLength("aaa") === 3
, ноgetLength("™") === 3.14
). Кроме того, нам нужны отдельные тесты на то, что функция не изменяет состояния своих параметров. fun getLength: List<Any> => Number
— теперь нам уже не нужны вручную написанные проверки на принимаемый тип, ведь мы точно знаем, что если переданный параметр не является списком, то вызывающий код не пройдёт этап компиляции. Если также мы знаем, что базовый тип List не поддерживает мутации, то мы знаем, что вызов этой функции не меняет сам список. Нам всё ещё нужны проверки на то, что результат функции возвращает неотрицательное, и целое число.- Пусть целое число у нас описывается типом
Int
. Тогда, если мы имеем
fun getLength: List<Any> => Int
— мы точно знаем, что длина всегда является целым числом, и каким бы ни было тело функции, она не сможет вернуть нецелый результат, или результат больше максимального или меньше минимального значения для Int. Но стоп, мы не знаем, что будет если передать в функциюNull
илиUndefined
. Какова их длина? Нужен тест! - Не совсем. Пусть в нашей системе типов, и
Null
, иUndefined
являются отдельными типами. Так как наша функция определена как
fun getLength: List<Any> => Int
, она не содержит ни того, ни другого типа (Что выражалось бы, к примеру, какList<Any>|Null
). Следовательно, никакой код не сможет передать внутрьNull
, и никакая реализация не сможет вернутьUndefined
. Потому этот класс тестов нам тоже не нужен. Нам по-прежнему нужны тесты на то, что результат неотрицательный. - Если мы определили
fun getLength: List<Any> => Length
, а типLength
у нас:
typedef Length as Int where $value >= 0
— то мы перекладываем ещё один из наших вручную написанных тестов на плечи компилятора, ведь теперь это его работа — проверять что функция не возвращает значений меньше нуля.
Итого, из первоначальных классов тестов нам останется только проверить, что для списков предопределённой длины вызов
getLength(list)
вернёт строго ожидаемое значение. Так система типизации позволила нам писать тесты только одного типа вместо шести или семи, которые я обрисовал вначале, все остальные группы тестов «написаны» и выполнены компилятором.Более того, обычно я даже не задумываюсь над тем, насколько огромно на самом деле количество тестов, которые я не написал вручную из-за систем типизации, но которые, тем не менее, постоянно выполняются в моём коде.
Я не думаю, что вам удастся найти живого JS\PHP\Ruby\Python программиста, кто будет писать все эти ваши тесты. Будет 1 тест вида:
it('length', () =>
{
expect(getLength([1,2,3])).eq(3);
expect(getLength([])).eq(0);
});
И всё.
ведь теперь это его работа — проверять что функция не возвращает значений меньше нуля.
Для общего развития поясните, пожалуйста, как компилятор будет это проверять в случае каких-нибудь сложных динамических вычислений результата. Ну, пускай, длина будет браться из базы.
Вот как компилятор проверит, что программист проверил? Я навскидку могу с десяток способ проверки сделать. Ну, например, проверить старший бит хоть c XOR, хоть с AND маской. Или сравнить n === abs(n). Компилятор всё это учтёт?
Вот как компилятор проверит, что программист проверил?
Ну делаете какой-то тип вида Either<Nat, BelowZero>, теперь чтобы со значением работать вам надо при помощи проверки выделить Nat.
Ну, например, проверить старший бит хоть c XOR, хоть с AND маской. Или сравнить n === abs(n). Компилятор всё это учтёт?
Компилятор освободит ваших коллег от необходимости писать вам замечание на ревью :)
А если интерфейс BitSet из интерфейса числа исключить, то остаётся так уж много способов проверить на неотрицательность, и уж с ними компилятор как-нибудь справится.
Я не исхожу в примере из представления, что числа имеют объектный тип с каким-то интерфейсом. Это просто скалярные значения, на множестве которых определены только простые операции типа арифметических и сравнения.
Вообще, сложилось впечатление, что логичная и развитая система это не про мэйнстрим большей частью императивные ООП языки. То есть логичная и развитая система типов это больше про академические задачи, а не про практические.
То есть логичная и развитая система типов это больше про академические задачи, а не про практическиe.
"Это не про практические задачи" — это такой тонкий способ сказать что конкретно? Вот язык Kotlin — по вашей логике, он решает больше академические задачи? Ведь там нет исключений, и число является объектным типом первого класса. Только заточен ли Kotlin под академические задачи, или всё же он ближе "к станку", раз уж его в Гугле полюбили настолько, что пытаются заменить им Java, в котором исключения из системы типов присутствуют? Достаточно ли Kotlin императивен и мейнстримен?
Вследствие того, что в официальной документации теперь куски кода не только на Java, но и на Kotlin, рука Гугла тут всё же в наличии. Никаких официальных заявлений я не видел, разумеется (может просто не там смотрел), но вывод о продвижении выглядит уместным.
С теми же const generics мы можете Описать сигнатуру
fn add_item<const N: usize>(a: [i32; N], b: i32) -> [i32; N + 1];
И например такой код во время компиляции скажет unreachable code:
let array = [1,2];
let array2 = add_item(array, 3);
if array2.len() == 2 {
...
}
Очень полезно во многих сценариях. Например, когда мы хотим работать только с квадратной матрицей. С этой штукой мы можем выразить это в типах и получить ошибку компиляции, при попытке посчитать например определитель для матрицы 2х3. А так остается только эксепшн в рантайме кинуть.
Для общего развития поясните, пожалуйста, как компилятор будет это проверять в случае каких-нибудь сложных динамических вычислений результата. Ну, пускай, длина будет браться из базы.
бтв не очень понял вопрос. Ну берется число из базы, что с ним не так? Делаете:
let connection = establish_connection();
let results = arrayLengthSettings
.limit(1)
.load::<ArrayLengthSetting>(&connection)
.expect("Error loading settings")?;
let index: i32 = results[0].index;
let index: usize = index.try_into()?;
Компилятор проверил, Как мы преобразовали типы, и что мы не забыли случай отрицательных значений.
На самом деле это не так уж сложно. В конце концов, пользоваться репами вместо папочек привыкли, и тесты писать тоже как-то научились. Но вот мысль о том, что недокументированный код == несуществующий код для большинства программистов всё ещё является глубоко авангардистской.
В итоге подавляющее большинство проектов не содержит вообще никакой документации кроме традиционной «шапки» с лицензией. Зачастую даже о зависимостях для успешной сборки приходится догадываться.
И вот разбираться в таком коде, если он написан на чём-то динамически типизируемом — это просто адский ад. Вот например, прямо сейчас я пытаюсь разобраться в коде PDF.js, для которой разработчики конечно же не завезли вменяемых доков, а примеры покрывают от силы 2% возможностей библиотеки.
И вот мне блин приходится гонять её под отладчиком, чтобы иметь возможность в рантайме проверить: что же, чёрт возьми, попадает в коллбэк при разрешении очередного промиса. Потому что из кода это как-то неочевидно.
Один из первых применил это правило некто по фамилии Гейтс, и причёсывает своё По до сих пор.А в каком месте он это применил, извините? Basic писался на ассемблере, более поздние вещи на Pascal, C и тому подобных языках, ни о какой динамике ни в какой момент там речь не шла, насколько мне известно.
Как то я пытался разобраться с модулем lirc для Linux. Он был написан со статический типизацией и комментариями. Но мне это не помогло!А будь этот модуль написан с динамической типизацией всё сразу стало бы лучше?
Вы всё же о другом говорите. Если вы с нуля пишете свою программу, то динамика может помочь, но если вдруг начнёте использовать стороннюю недокументированную библиотеку, то тут и появляются проблемы, которые описывает Alexey2005.
Мне кажется для большинства разработчиков инструмент просто является инструментом, и выбираться исходя из конъюнктуры, на которую способ типизации оказывает не очень большое влияние. Короче говоря, JavaScript стал популярным не только потому что он является языком с динамической типизацией; библиотеки, интерпретаторы и все прочее для него пишутся тоже не только по этой причине, как и для всех других языков. Следовательно ваша постановка вопроса не совсем корректна.
> Динамическую типизацию считаю Абсолютным Злом
И раз уж Вы допустили такое высказывание, то я допущу немного софистики. TypeScript стал популярен, во многом исходя из того, что был запрос на статическую типизацию для JS, и те кто пишет/писал на JS, в этой популяризации сыграли основную роль. Дальше совсем прикольно получается: выходит, что в целом, разработчики, которые принимают такие гибкие технологии как JavaScript, со всеми плюсами и минусами, готовы принять и нечто более строгое, такое как TypeScript, да впрочем и любые другие технологии и принципы: например функциональщина также очень распространена. А в обратную сторону, в случае разработчиков, вот таких как автор, сталкиваешься с какой-то древневековой косностью мышления и иррациональной верой в то, что всякому необходим код, который будет работать веками, а данные должны быть пропитаны десятками абстракций, якобы для какой-то еще более абстрактной «правильности» кода.
Интересно что вы хотите услышать в ответ? Что способ типизации сильно волнует разработчика при выборе технологии, или что динамическая типизация это хорошо, а статическая плохо?
Второе.
Потому что все примеры, что я видел, легко разбиваются современными системами типов. "А вот надо много писать" — не надо, всё автоматически выводится. "А вот надо 2 разных типа вернуть" — не вопрос, вот вам ADT… Ну и так далее.
В итоге я не видел ни одной статьи, где не использовались бы мифы про "в типизированном языке в 10 раз дольше писать, потому что все типы надо описывать" и подобных, где прямо показано, вот преимущество, в таких-то случаях.
Потому что для меня, в скриптах до 500 строк разницы на чем писать нет, потому что и то и то можно написать за пару дней на любом языке. А сложный проект лучше писать с поддержкой типами. В итоге очень узкий кейс, где динамические языки имеют минорные плюсы просто не дают оснований на то, чтобы тратить приличное время на их полноценное изучение.
Прямо совсем все типы автоматически выводятся? Скорее всего нет, какие-то все равно придется описать. Вот так мы снова и приходим к аргументу "не хочу, не буду писать никаких типов", за который топят некоторые сторонники JS (не я).
С другой стороны, если можно вывести типы без явного их указания, то может быть, можно то же самое сделать с Javascript, анализируя функции и как их вызывают? Бинго! Есть flow – запускаем этот инструмент поверх ванильного Javascript и получаем предупреждения о несовместимости типов. При желании можно и аннотации типов добавить, но это уже отступление от канона "не хочу писать никаких типов".
Прямо совсем все типы автоматически выводятся? Скорее всего нет, какие-то все равно придется описать. Вот так мы снова и приходим к аргументу «не хочу, не буду писать никаких типов», за который топят некоторые сторонники JS (не я).
По месту использования — не нужно вообще. Ну вот пример на расте (Который типизирован выше крыши):
fn send_internal(&self, method: Method, url: &str, body: Body) -> ResponseFuture {
let uri = format!("https://api.telegram.org/{}", url);
let request = Request::builder()
.method(method)
.uri(uri)
.header("Content-Type", "application/json")
.body(body)
.unwrap();
self.client.request(request)
}
который на JS будет выглядеть как-то
function send_internal(method, url, body) {
let uri = format("https://api.telegram.org/{}", url);
let request = Request.builder()
.method(method)
.uri(uri)
.header("Content-Type", "application/json")
.body(body)
return this.client.request(request);
}
Разницы почти нет.
В местах объявления типов и методов — придется писать, но jsDoc и прочее люди и так пишут. Типы это и есть такая документация, только всегда актуальная.
С другой стороны, если можно вывести типы без явного их указания, то может быть, можно то же самое сделать с Javascript, анализируя функции и как их вызывают? Бинго! Есть flow – запускаем этот инструмент поверх ванильного Javascript и получаем предупреждения о несовместимости типов. При желании можно и аннотации типов добавить, но это уже отступление от канона "не хочу писать никаких типов".
Типы, они как прививки — если 100% кода ими покрыто, они очень действенны, если 95 — то уже поменьше, а если 90 и меньше — то уже ничего не спасет этот проект.
Давайте так подытожим:
Для того, чтобы загрузить с сервера некоторый текст и посчитать сколько раз в этом тексте встречается выражение «Hello, world!»: строгая статическое типизация — плохо, слабая динамическая — хорошо.
Для того, чтобы построить систему, в которой используется сотня отношений в базе данных, и где бизнес-логика, которая связывает эти отношения, описывается огромным количеством всяких условий: строгая статическая типизация — хорошо, слабая динамическая — плохо.
А вот если задача находится между этими двумя случаями, то ответ не может быть таким однозначным. И например если бы передо мной стояла задача реализовать нечто не очень объемное и не очень маленькое, а самое главное быстро, то для меня та технология, которой я пользовался в последнее время, имела бы значимый вес при выборе, независимо от способа типизации в ней.
Ваш «очень узкий кейс» с «минорными плюсами», это и есть ключевая причина успешности интерпретируемых языков с динамической типизацией. Вспомните изначально для чего создавались такие языки как PHP, JavaScript, Bash и другие. То что они получили развитие, это мне кажется логичное явление после успеха. То что способ их применения мог значительно усложниться, это всего лишь некоторый этап развития, в следствие успеха. И например TypeScript, я считаю, достаточно естественным развитием некоторой технологии, которая внешне сильно ограничена (теми же браузерами), и в которой не хватает средств для построения больших абстрактных систем. Причем, я считаю, что TS появился слишком поздно, но если бы не он, то многие писали бы на каком-нибудь Haxe или Flow. У меня в голове и мысли нет противопоставлять различные способы типизации в категории: хорошо или плохо — этим я пытался показать абсурдность подобных обсуждений.
Динамические слабые языки хороши обычно во всяких сценариях, когда ошибочный результат не так уж сильно заметен. Типа нарисовали врага на полпикселя левее — ну и хрен бы с ним. Поэтому они почти всегда используются как скриптовые языки в игровых движках и редакторах карт.
А вот если задача находится между этими двумя случаями, то ответ не может быть таким однозначным. И например если бы передо мной стояла задача реализовать нечто не очень объемное и не очень маленькое, а самое главное быстро, то для меня та технология, которой я пользовался в последнее время, имела бы значимый вес при выборе, независимо от способа типизации в ней.
Я бы согласился, но буквально полгода назад был эксперимент, который мне было интересно провести: мы взяли небольшое JS-файл на 150 строк, который рисовал какую-то фигню, снежинки там и все такое. И переписали на TS. Переписывание заняло минут 30 (у человека, который TS в глаза не видел, я ему его рекламировал), и мы нашли 3 бага, в основном связанных с тем, что где-то записывались свойства, которых у объекта нет, а где-то они, наоборот, читались. На 50 строк кода была одна ошибка. Который в проде использовался больше года, и постоянно просматривался.
Таким образом, мое мнение, что динамические слабые языки удобнее лишь тем, что вам не нужно особо ничего знать, чтобы начать на них писать. То есть пока человек изучает там интерфейсы, паттерны проектирования, DI, вот это все, человек на JS уже давно написал прототип, добавил функционала, и стрижет денежки.
Но вот если человек вложил десяток лет в понимание, что такое типы, как работать с ними, то он пусть даже потратить на 20% времени больше на реализацию того функционала, что сделал неопытный JS-ер чуть быстрее, зато его решение будет работать. А главное, ему не придется помнить все длинные списки хаков и нюансов разных фреймворков, браузеров и т.п.
По сути есть два инструмента, один из которых слегка хуже работает в сценариях типа А, и нормально в сценариях типа Б, а другой нормально работает в сценариях типа А, и почти не работает в сценарях типа Б. При этом разница между "чуть хуже на сценариях А" и "нормально на сценариях А", при достаточном вложении времени в инструмент 1 такова, что совершенно теряется смысл изучать инструмент 2. Просто потому, что это слишком большая когнитивная нагрузка при практически отсутствующей разнице в результате.
А если уже оба инструмента знаешь (скажем, на момент изучения инструмента 2 инструмента 1 не было в принципе), то почему не выбирать инструмент по сценарию?
Потому что я не вижу особого применения динамическим языкам. Как сказал товарищ в этом топике или соседнем, его рекорд: 1300 строк идеоматичного ООП кода в день. А больше 1000 строк это уже объем, где статика предпочтительнее.
Таким образом, если у вас проекты длятся в среднем больше одного дня, статика предпочтительнее. Если нет, то нет, конечно.
Динамические слабые языки хороши обычно во всяких сценариях, когда ошибочный результат не так уж сильно заметен. Типа нарисовали врага на полпикселя левее — ну и хрен бы с ним. Поэтому они почти всегда используются как скриптовые языки в игровых движках и редакторах карт.
Да, а потом происходит вот так: https://habr.com/post/417435/
Прихожу на проект. Там 10 индусов писали полгода. Оно разваливается. Прод через неделю. КАКИЕ ТУТ НАФИГ ТИПЫ?!?!))))Прихожу на стройку. Там 10 индусов строили полгода. Оно разваливается. Сдавать через неделю. КАКОЙ ТУТ НАФИГ ФУНДАМЕНТ?
в динамике тоже есть преимущество для некоторых оптимизацийА можно пример? Я предполагаю, что в языках с гибкой типизацией и макросами можно сделать то же самое, только как следует, а не monkey patching-ом.
js js'у рознь. Я когда пишу на чистом js взял в привычку натравливать flow или ts на него, проверяя достаточно ли информации в коде для автовывода.
Приведу пример: JS — «Hello» + 123 = «Hello123»; Python — «Hello» + 123 = TypeError: must be str, not int.
Мне кажется что(как многие писали выше) большинство вопросов у сторонников того же C# к JS связаны с слабой типизацией, а не динамической.
P.S.: C++ имеет статичную но слабую типизацию.
В техникуме на программера изучал JS — это был 1й язык именно написания кода (а не GUI).
Потом был assembler :) круто показало внутрянку.
И всегда на протяжении кодинга хоть на JS хоть на asm, я думал как с точки зрения сущностей / объектов… у нас был преподаватель на объектах чуть помешанный, ну там «очередь», «слот очереди», «животное», хотя и делали то на массивах параллельных, ну типа x[], y[] вместо объекта Obj {x, y}
Т.е тут дело как это в голове уместить. И абстракции в духе «очередь» и «слот» и т.п. ложатся на мозг человека очень хорошо… уж не помню по каким результатам исследований, но чел не может в голове держать несвязанные разрозненные концепции — далеко не уедешь, надо пытаться свести к формальной модели с определенными входами и выходами, и разными уровнями абстракции… в том числе несколько абстракций нельзя держать, вроде как 7 шт и дале уже забываешь о других объектах…
Если же исходить не из ООП объектов (которые по опыту можно и в JS и а ASM даже, главное концепция в голове), а строить какие-то сотни функций (function DoX(a, b, c) в JS) то в них очень просто запутаться… помню писал и шашки, и крестики-нолики, шахматы начинал — все на JS, по опыту знаю что путался… в итоге всеравно к объектам приходил, и к разным уровням абстракции, вроде это «поле» а это «фигура» а функция построения возможных ходов принимает «тип фигуры» в шахматах например… на Си без плюсов а потом и на C++ переписывал шахматы и явно было видно что статические типы облегчают концепции, в плане возврата в этому. Да это не Write-only, а когда недели/месяцы пилишь прогу (пусть и учебную в начале) то облегчают.
А потом пришел к нормальному ООП в С++ и C# — это было круто и никогда не уйду от них в пользу динамических (уж не силен в терминологии, строгая там или нет и т.п свойства) как в JS.
Да даже на статических и ООП заточенных типа C# бывает код абы-как написан, не используя нормальные общепринятые концепции… это как описывать кучу функций, кучу get-set или public свойства что по сути одно и тоже, но без адекватного поведения внутри объекта, и инкапсуляции… т.е добавить в объект свойство — это прошлый век (начало программирования), надо в него еще поведение заложить и инкапсулировать (чтоб все кому не лень не пытались извне копипастить, а если этот функционал в подчинении класса он и должен быть в нем описан — так понятней где искать — потом, при чтении кода и доработках)… имхо от недостатка опыта, они не допоняли концепции объектов со свойствами и поведением.
Что до паттернов, которые типа унивесральные (даже к JS применимо есть паттерны), суть их в том чтобы другой чел (или ты через годы) вспомнил/понял как оно тут работает в соответствии с общепринятыми джентельменскими нормами… и не 100% программеров придерживаются, но они формально есть, все эти «State в динамических языках не нужны» от непонимания что есть самый State, т.е может у вас вся программа из иммутабельных объектов, а тут у вас есть класс который имеет внутренный вложенный private (или нет — в JS) класс и вот он мутабельный State т.е вы его там гоняете и собираете по колбекам (парсер какойто иерархии, да хоть XML/JSON/SVG для наглядности) и в итоге вот у вас StateOfParsingProcess — вот он формально есть. Даже если вы его не напишите в виде статически class StateOfParsingProcess и не обозначите private — он в вашей голове есть. Но вот напишете — потом вспомнить и вам же будет проще.
Какие-то люди могут не понять как запрограммить процесс парсинга какогото формата файла на JS (не говорю про какие-то спец инструменты вроде разбора грамматик и построяния деревьев и т.п), вот они придумаю свою реализацию на любом языке, и она будет не понятна другому кто будет читать, потому что не то что не очевидна (это тоже), в ней может и логики не быть, потому что ее и не рефакторили, и сделали «чтоб работало» / write-only. А комуто это читать и допиливать вероятно.
а) «Мой друг хотел делать наоборот — сразу писать код и не описывать никакие типы. Он не был готов определять проблему в виде семейства типов».
«Это не значит, что динамически типизированные языки не нужны (на самом деле я и правда думаю, что не нужны). Это значит, что при их использовании, тебе нужно отказаться от ООП, как главенствующей парадигмы».
«Канонические реализации большинства ООП паттернов пестрят кейвордом Interface, который совершенно бессмысленнен в динамически типизированном языке.»
Вы очень узко смотрите на ООП. ООП — это не какая-то одна цельная парадигма, а очень широкий спектр различных вариантов ввести в язык объекты.
Ну вот, например, вы сетуете на то, что в динамически типизированных языках интерфейсы не декларируются явным образом (хотя используются постоянно). А вы в курсе, что статически типизированный ООП-язык C++ не имеет интерфейсов вообще, и предлагает совершенно другие способы решения проблем?
Динамические языки вполне себе описывают типы — возьмите Smalltalk и всех его последователей. Мне как человеку из ruby-мира с его тщательно продуманной системой типов просто смешно читать про то, что динамическая типизация — это отказ от продумывания пользовательских типов.
JS — не лучший и далеко не самый типичный пример ООП-языка с динамической типизацией.
б) «Паттерны — штука кросс-языковая». Эээ? Нет, паттерн — это всего-лишь типичное решение типичной проблемы. И в разных языках наборы задач и решений разные. Множество паттернов упростилось (вплоть до полного вырождения) с появлением в языках лямбда-функций, это же не повод поливать лямбды грязью и требовать возвращения всех тех сложностей, которые приходилось без них решать заведением специальных классов.
в) «Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.»
Часть модулей вашего кода получила гарантии согласованности. Если код настолько немодульный, что вы не получили никаких гарантий, то это просто плохой код. Статическая типизация его не исправит (скорее уж наоборот, законсервирует лишние взаимосвязи).
в') Ага, а потом ваши гарантии разбиваются о какую-нибудь ковариантность коллекций, хе-хе.
Взамен появилось множество новых.
Это ли не аргумент против того, что "паттерны — штука кросс-языковая"?
Ну т.е. скажем фабрика — это паттерн в тех языках, где функция не является примитивом 1 класса, а там где функции есть, и пишутся легко — фабрика как правило не нужна, потому что это не более чем функция, и паттерн совершенно вырождается.
С другой стороны, map/reduce/filter/etc — это вполне себе паттерн для многих языков, претендующих на функциональность, и выглядит практически одинаково в Scala и в JS, с точностью до типизации и ленивости (т.е. внешне одинаково, а работать конечно будет по-разному).
Потому что функия с паттерн матчингом — совершенно типичная ситуация в ФП, и не требует выделения целого паттерна.
Условие было " функция не является примитивом 1 класса". если функции есть, а паттерн-матчинга нет?
ФП без паттерн матчинга это что-то из разряда фантастики.
Собственно, не бывает ФП языков, бывает ФП стиль написания кода. И паттерн матчинг является одним из ключевых кирпичиков фундамента, без которого остальное имеет мало смысла.
>теряет актуальность
Я немного не так сказал. Речь о том, что с появлением скажем лямбд, большинство паттернов, описанных в той самой книге GoF, становятся настолько тривиальными и очевидными, что по большей части уже не нужны. В частности, в виде отдельных классов. На примере Java 8 это очень ярко проявилось.
Он теряет актуальность как только появляется возможность передавать вместо абстрактного класса обычную функцию.
Идея ставить во главу типы серьезно повлияла на мое мышление разработчика.
Типы всегда находятся во главе. Динамическая типизация — это не отсутствие типизации, иначе бы вместо "динамическая" мы бы говорили "никакая".
Просто в статическом случае работать с типами проще и удобнее. В динамике — с-но, менее удобно и сложнее, требуется большая квалификация, большая дисциплина. Человек, который умеет писать на динамике — прекрасно понимает, как использовать статику. Если не понимает — ему в динамике не место.
Вы вот пишите, что у вас проблемы с js-подходом — они не от того, что вы начали со статики, а от того, что вы просто недостаточно квалифицированы, чтобы писать качественный динамический код. При этом достаточно квалифицированы, чтобы осознавать этот факт (что, кстати, для огромного числа людей, пишущий на js, не выполняется).
А желательно показать на практике, где динамическая типизация даёт серьёзное преимущество перед статической?
А вы точно на мой комментарий отвечали? Я не говорил, что динамика дает преимущества, я говорил, что на ней писать труднее, едва ли это можно назвать преимуществом :)
Разговор о преимуществах вообще не совсем верный, правильнее говорить о возможностях. Статическая типизация дает возможность на уровне типов фиксировать инварианты и автоматически их проверять.
Динамическая типизация сама по себе никаких возможностей не дает, т.к. это не фича, а ее отсутствие. Но косвенно она дает те возможности, которые плохо совместимы (или вообще несовместимы) с типами.
Например — макросы с типами плохо совместимы (по-этому самые годные макросы — в динамических лиспах), рефлексия — плохо совместима (по-этому самая годная рефлексия — в каких-нибудь рубях и тех же лиспах, вроде CL), нормальный репл и image-based разработка (смоллтолк и снова лиспы) — фактически в рамках статики невозможны.
А вы точно на мой комментарий отвечали? Я не говорил, что динамика дает преимущества, я говорил, что на ней писать труднее, едва ли это можно назвать преимуществом :)
Прошу прощения, наверно не так выразился. Хотелось бы немного проникнуться методами, которые используются при таком подходе. Понять как это делается. Советовать мне что-либо написать самому, дело достаточно бесполезное. Ибо пишу уже очень давно и только на статически типизированных языках. Сами понимаете, что я напишу. С тем что Вы сказали, согласен лишь отчасти. Макросы например это те же шаблоны С++. Достаточно уродские. Но скажем в D эта штука уже вполне удобоваримая. Нормальный репл прекрасно живет в Scala или Haskell. И даже дружит с jupyter. Рефлексия хоть и не без бубнов, неплохо работает в java. Впрочем возможно опять Вас не понял. Честно говоря просто не могу себе представить концепций, невозможных в статике, но при этом практически полезных. Напоминаю однако, что это лишь моё мнение. Не претендующее на истину, а судя по популярности того же js от истины весьма далёкое. Вот и пытаюсь к истине как-то подобраться.
Макросы например это те же шаблоны С++.
Просто нет.
Нормальный репл прекрасно живет в Scala или Haskell.
Что происходит в этом прекрасно живущем репле, когда вы сперва написали ф-ю f с типом Х, где-то ее поюзали в другой ф-и, а потом переопределили f так, что у нее тип стал Y?
Рефлексия хоть и не без бубнов, неплохо работает в java.
Ну тут такая ситуация, что кому-то и плюсовые темплейты — макросы, кому-то в джава есть рефлексия, а кому-то — и кобыла невеста.
Честно говоря просто не могу себе представить концепций, невозможных в статике, но при этом практически полезных.
Все концепции, условно говоря, возможны в любом тьюринг-полном языке. Вопрос в том, как они реализованы, насколько широкие возможности дают и насколько удобно их использовать.
Что происходит в этом прекрасно живущем репле, когда вы сперва написали ф-ю f с типом Х, где-то ее поюзали в другой ф-и, а потом переопределили f так, что у нее тип стал Y?
Ошибка происходит: функция уже определена.
Рефлексия с типами плохо совместима? Это вообще как?
Нормальная рефлексия должна предоставлять возможности свободно и удобно анализировать, изменять, конструировать типы в рантайме. Если вы конструируете тип в рантайме, то не можете потом как-то его статически чекать (если у вас не идрис офк), ну просто потому что к моменту чека этого типа еще нет.
То есть сама идеология рефлексии противоречит статической типизации, с-но хороший статический язык должен рефлексию ограничивать и делать ее использование болезненным, т.к. фрагмент приложения с рефлексией оказывается нетипизирован де-факто. Т.о. использование рефлексии в статике обычно позиционируется как некий специфический инструмент для специфических ситуаций, который дОлжно применять только при серьезной необходимости.
В динамике же использование рефлексии может быть стандартным приемом, использующимся свободно и повсеместно, т.к. добавить в рантайме метод классу — что значение к списку.
Вот вы пишите: «добавить в рантайме метод классу — что значение к списку». Класс — уже не тип? Тип. Так почему это рефлексия с типами плохо совместима-то?
Почему-то я всегда считал, что рефлексия — это анализ структуры программы в рантайме, а не конструирование.
Здесь есть некая доля путаницы, ага. Я подразумеваю под метапрограммированием работу с АСТ программы (макросы и другие варианты кодогенерации), а, с-но, рефлексия — это работа с программными объектами, которые этот АСТ может представлять. Если вы генерируете АСТ, который описывает класс — это метапрограммирование, если вы создаете программно некий класс как сущность, добавляете ему методы (как сущности) — то это рефлексия. Метапрограммирование может происходить при компиляции (к слову, тайпчек — формально одна из разновидностей метапрограммирования), рефлексия — в рантайме.
Класс — уже не тип? Тип. Так почему это рефлексия с типами плохо совместима-то?
С динамическими типами — прекрасно совместима (как я уже выше отмечал — динамическая типизация это не та, в которой типов нет, а та, в которой они в рантайме), со статическими — нет. Потому что добавление это предполагается в рантайме.
Чем динамический язык тут лучше? Вам не придется заводить обертки над значениями примитивных типов и руками распаковать их значения (а вам придется, если только у вас в языке нет union-type). Библиотека для работы с json в статически типизированном языке по сути будет реализовывать свою «динамическую типизацию».
Кроме того, в динамическом языке вы спокойно можете писать литералы, соответствующие нужному вам объекту. Я не знаю, есть ли хоть один статически типизированный язык, который вам позволит такое.
А задача работы с пришедшим извне json — типичнейшая.
С действительно произвольным json можно сделать не очень много полезных вещей, обычно все-таки программист знает схему пришедших данных. Эту самую схему можно или представить в виде типа данных (статического!), или держать в голове, но приводить типы по месту:
var name = (string)json["name"];
В любом случае система типов сработает как дополнительный слой защиты от некорректных данных и снизит вероятность появления ошибок подобных вот этой: https://habr.com/company/poiskvps/blog/422077/
Вы предлагаете (небезопасно) приводить типы по месту. Но где же тут хоть одно преимущество статической типизации реализуется?
На входе и на выходе обработки будут нужные типы (невелика задача и для динамической типизации), а в середине — как получится. Подвержено ошибкам в рантайме оно будет ничуть не меньше, чем код на динамическом языке. Единственная надежда — что компилятор заставит написать обработку исключения про неправильное приведение типов или заставит проверить тип перед приведением. Но компилятор так придирчив как минимум не во всех статически типизированных языках.
Не очень понимаю, как проверка типов помогла бы в том примере (не работал с монгой и не знаю, что за тип у этих операторов и чем такой словарик будет отличаться от обычного {string=>string})?
Вы предлагаете (небезопасно) приводить типы по месту. Но где же тут хоть одно преимущество статической типизации реализуется?
Преимущество тут в том, что компилятор не дает забыть привести тип перед использованием.
Не очень понимаю, как проверка типов помогла бы в том примере
Очень просто: если привести userInput.paramA
к строке, то для {$empty: false}
будет выброшено исключение, и никакой запрос к СУБД не уйдет.
Представить его в виде статического типа можно, но для этого нужно быть очень неленивым.
Лень нажать кнопку и запустить автоматическую генерацию типов? Ну знаете ли...
Вы предлагаете (небезопасно) приводить типы по месту. Но где же тут хоть одно преимущество статической типизации реализуется?
Ну вот кто-то поменял что-то на бекенде, например убрали поле из какого-то запроса. Жмакнули кнопку, перегенерили типы, жмакнули кнопку, запустили тайпчек на фронте — теперь все места в программе, где это поле (которого теперь нет) используется и в которых, с-но, ошибка — подсвечены. Нет тут преимущества?
Вы предлагаете кодогенерацию в качестве решения проблемы? Спасибо, но нет.
А в чем проблема генерировать типы? А то получается, что сначала "типы обязательно нужно писать руками, а это долго", А когда оказывается, что не долго и можно их генерировать "вырвырвыр, у меня нет на это времени"
Для примера на F#
let [<Literal>] questionsUrl = """https://api.stackexchange.com/2.2/questions?site=stackoverflow"""
type Question = JsonProvider<questionsUrl>
let questions = Question.Load questionsUrl
questions.Items |> Array.iter (fun x -> printfn "%s" x.Title)
Просто, понятно, читаемо, валидируется во время компиляции. Если вместо RetweetCount
случайно написать RetwetCount
, компилятор подчеркнет ошибку и ругнется. Удобно? Вроде, весьма. И не нужна никакая документация, потому что код + IDE сами по себе лучшая документация
Вы предлагаете кодогенерацию в качестве решения проблемы? Спасибо, но нет.
А в чем проблема? Легко, надежно.
Да, схема обычно известна (или некоторый кусок схемы, который при обновлении стороннего API может дополняться). Представить его в виде статического типа можно, но для этого нужно быть очень неленивым.
Вы какие-то ужасы рассказываете. О каком-то отвратительном апи с непонятной схемой. Как с такой вообще работать то? Что в динамическом, что в статическом? Все хорошие апи, которые я знаю очень легко описываются в статике. Да API, когда подключаешься к серверу — описывается в первую очередь, даже на чистом JS через JSDOC по той простой причине, что значительно легче посмотреть, что приходит в ответ на какую-то функцию в коде, чем запускать, ставить дебагер и пытаться поймать, что же там пришло.
P.S. Минус не мой :)
В случае динамической типизации можно запросто забыть выбросить ошибку.
Это — одно проблемное место во всей кодовой базе. Объект, вышедший из-под JSON.parse<someInterface>
, в дальнейшем обрабатывается с учётом всех статических типов. В то время как в динамически типизированном языке он так и останется "чем-то, определённым в рантайме".
Вероятно, у нас просто различаются задачи. У меня основной рабочий инструмент — скрипты и не слишком большие числомолотилки. Половина кода там будет тем самым проблемным местом, где статическая типизация бессильна, но тратит много сил на реализацию. Другая половина кода не настолько велика, чтобы нельзя было за время работы с ней не забыть, какого же типа объект передается в переменную с именем matrix.
null,
bool,
int,
str,
float,
list,
object
}
В том же rust'е спокойно делается match по типу. И оно не динамически типизировано ни в один момент времени.
см github.com/serde-rs/json
Я не спорю, что сделать это возможно, но там, где мне достаточно обратиться к элементу, вам дополнительно приходится громоздить type matching.
Просто десериализуйте в объект и все. У вас же есть хоть какая-то схема? Вряд ли вы лупитесь по случайным именам вложенных хэшмап в надежде получить что-нибудь, наверное вы все же предполагаете, что у вас есть объект юзер, у него есть поле мессадж, а у него есть поле мессадж_айди. Почему бы это не зафиксировать в виде типа? Кому от этого станет хуже?
Я немного выше написал, что большая часть кода, с которым мне приходится работать — скрипты. Описания всех схем и промежуточных типов, к которым мне приходится обращаться, увеличит размер кода раза в два, не давая взамен почти никакого профита. Охотно верю, что для крупных проектов статическая типизация имеет смысл, но для мелких проектов она даёт только проблемы.
Я понимаю, что в этом весь смысл, но во многих случаях я точно знаю, что у меня в аргументе, и не хочу тратить силы на убеждение компилятора в том, что точно знаю, что делаю
Я 95% времени пишу на статически типизированных языках.
Но когда пишу что-то на динамически меня постоянно мучает вопрос — а вдруг мне кто-то, передаст в функцию не то, что ожидается на входе, и всё пойдёт не так, как предпологалось.
Со статически типизированным мышлением ты чуствуешь себя спокойно хотя бы в этом вопросе — если я пишу что здесь у меня на входе данный класс/структура, то я могу быть уверен что публично описанные поля у него есть. А в языке с динамической типизацией меня каждый раз при обращение к полям входящего обьекта меня терзает вопрос — а не упадёт ли он при этом обращение, от того, что на входе будет не тот обьект, который я ожидаю?
Это не значит, что в том же питоне, я не думаю типами и обьектами, но там ощущение, что всё может быть совсем не так, как ты предпологаешь, является более навязчивым.
Статичиская типизация отодвигает момент такого беспокойства — тут ты уже не сомневаешься будет ли у тебя на входе нужный обьект, а задаёшься вопросами будет ли обьект в правильном состоянии, не поменяет ли его кто-то асинхроннно в процессе и т.п. Статическая типизация уменьшает количество причин переживать.
Есть языки которые и эти вопросы снимают по максимуму, но в моей сфере они не рапостранены.
Возможно, если долго писать на языках с динамической типизацией, то об этом не будешь задумываться так часто — просто привыкаешь к этому виду «опасности». Как в C++ особо не паришься, что любая подключённая к проекту библиотека могла затереть случайнными данными кусок памяти с твоим обьектом.
Но когда пишу что-то на динамически меня постоянно мучает вопрос — а вдруг мне кто-то, передаст в функцию не то, что ожидается на входе, и всё пойдёт не так, как предпологалось.
Даже хуже. Если разбираться в исходниках какого-нибудь tensorflow, то там у функции может быть 50 строчек проверок входных параметров (потому что там, например, в качестве входных данных может прийти и тензор, и список тензоров, и кортеж списков тензоров, а ещё есть куча параметры по-умолчанию, некоторые сочетания которых могут быть невалидными), и только потом будет вызываться функция с примерно таким же именем, в которой проверки уже не делаются.
Документация местами тоже божественная, например, "loss_func is loss function" — а с какими аргументами она будет вызвана, я сам должен угадать. При том что в нормальном языке просто в типах уже была бы информация о типах аргументов коллбека.
И самое противное — мой изначально простой код типа load_image() обрастает такой же кучей проверок (каждая была добавлена после очередного падения в рантайме и поиска бага), потому что часть png картинок грузятся как трёхмерные массивы numpy с тремя или четырьмя каналами, иногда чёрно-белые картинки вместо этого грузятся как двухмерные массивы, а ещё иногда тип данных в массиве оказывается не uint8 с интервалом 0..255, а float с интервалом 0..1.
При этом использование JS или TS в итоге гораздо слабее влияет на результат (результатом считаем, естественно, поддерживаемость кода, а не производительность etc.), чем привычки архитектора. Кроме TS в арсенале программиста-хипстера есть статическая типизация в ODM и даже пропсы (во фронт-компонентах) с фиксированным типом.
Основная идея в том, чтобы не пользоваться языком как динамическим и делать основной упор в коде на читаемость. А если всё же пользоваться, как динамическим — уже через неделю будь готов потратить n от дневного запаса когнитивного ресурса на ковыряния. Впрочем, бывают кейсы в которых это вполне нормик.
Но технология, которая стала для разрабов первой «боевой», определяет их настолько сильно, что два взрослых опытных человека просто не готовы друг друга слушать.Значит они недостаточно опытны.
Изначально JS, PHP решали небольшие сиюминутные задачи, где система сама по себе не усложнялась.
Крупные задачи решались на языках посерьезней.
Сейчас задачи другие, и соответственно меняется подход.
Можно прибраться в комнате сложив все игрушки под кровать, а можно разложить их по местам.
Вообще не вижу никакого непреодолимого противоречия: сам часто сначала прикидываю как все должно работать в общем (без типов) а потом описываю типы и прорабатываю детали. Имею одновременно и строгий порядок там где это нужно и необходимый уровень свободы там где важна скорость. По моему, виды типизации не стыкуются в головах только у людей с квадратно-гнездовым мышлением. Вообще вопрос типов очень близок к вопросу вариативности данных, и если вы впадаете в ступор от того, что окружающий мир не всегда вписывается в вашу систему строгих классификаций — проблема в вас.
Вброшу немного: единственная, на мой взгляд, веская причина почему JS вообще говоря используется в современном мире — это то, что он является единственным языком, работающим в браузерах. А дальше уже стокгольмский синдром.
То, о чём пишете вы, — не динамическая, а слабая типизация. Динамическая — это когда можно написать let x = 1; processNumber(x); x = 'string'; processString(x);
и не выворачивать себе мозг из-за того, что переменная x
должна иметь разный тип в разных местах кода.
То, что вы написали — это слабая типизация. А динамическая типизация — это когда, условно, можно все 4 приведенных вами значения записать в одну и ту же переменную без специальных приспособлений, просто по умолчанию.
После многих споров на тему статика vs динамика, у меня создалось устойчивое впечатление, что разработчики делятся на 2 лагеря:
- Те, кто предпочитают думать, прежде чем что-то делать, а именно построить математическую модель и потом ее имплементировать.
- Те, кому ближе философия "че тут думать, прыгать надо", а именно набросать что-то, а потом по ходу разбираться.
Я безнадежно далек от второго подхода, меня ужасно раздражает постоянное натягивание заплаток и расходищийся багфиксинг. Но миллион раз встречал людей, которые предпочитают с места в карьер нырять, им важнее дать супер быстрый старт, принося в жертву последующую скорость развития продукта.
Когда мы сталкиваемся с новой проблемой, мы некоторое время ее обдумываем, потом решаем. Но дефект нашего мышления в том, что мы думаем, что поняли, в чем именно проблема и как ее правильно решить. Это не так. По настоящему понять можно лишь погрузившись в проблему на практике, а не просто потыкав её теоретическими щупами. Потому что часть важной информации от нас обычно скрыта на старте.
Если вы с самого начала все обдумываете, строите полную архитектуру проекта, а потом ее реализовываете, то это не только замедлит вас в начале, но и не даст вам желаемого ускорения «в середине». Потому что через месяц-другой или вы увидите что-то, что упустили, или требования изменятся. В худшем случае потребуется переписывание крупной части проекта, если не всего целиком.
Гораздо быстрее будет осознанно в первый (а иногда и во второй, и в третий) раз писать быстро и некачественно (прототип), наткнуться на всевозможные стены лбом, переосмыслить чуть ли не полностью, а потом уже написать правильно и качественно.
PS Разумеется, это не относится к критически важным вещам, вроде системы управления полетом или там медицинского оборудования. Хотя даже в этих случаях есть этап исследования и быстрой проверки гипотез.
Кстати, именно так и происходит, когда за архитектуру берется мидл, даже сообразительный — просто опыта не хватает.
Что касается неряшливого прототипа — тысячу раз видел, как такой прототип внедряется, девелоперы заранее предупреждают, что он дерьмовый, и бизнес говорит «дадада, обязательно перепишем, и не раз». Спустя 2 года это дерьмо все еще в продакшне, обвешано миллионом костылей, и чем дальше, тем хуже и тем больше все ссут его переписывать, потому что уже никто не знает, как оно работает и что отвалится, если убрать.
В сухом остатке имеем: если проектом управляют сознательные люди, которые умеют в долгосрочную перспективу, то статическая типизация будет сильным преимуществом.
Если бизнесом управляют незрелые люди, то динамическая типизация на старте поможет этот факт сгладить, но агония неизбежна, и чем дальше, тем будет хуже.
Как тут уже не один раз заметили, типы в первую очередь рождаются в голове, и только потом переносятся в код. И если они родились убогими, то там уже ни динамика, ни статика не спасут.
Отдельно добавлю, что во многих мейнстримных языках (C#, Java, Go etc.) система типов слабая, не хватает банальных типов-сумм, я уже молчу про HKT. Тем не менее, это уже лучше, чем ничего.
Кстати, именно так и происходит, когда за архитектуру берется мидл, даже сообразительный — просто опыта не хватает.
Мне кажется так происходит вообще у всех, если задача не сильно очевидная/привычная. Сколько не проектируй, жизнь всё равно расставляет свои корректировки. Мы, человеки, достаточно ограничены в своих умственных способностях и удержать большую ещё несуществующую систему в голове не способны.
Ну ок, значит где-то живут небожители, с IQ > 200, бесконечной памятью, и которые не совершают ошибок.
Они не небожители, и совершают ошибки иногда. Они просто умеют проектировать, потому что в свое время потратили много сил на это, в то время как другие срались в коментах про плюсы и минусы динамической типизации)
Они понимают, какие правила в проекте являются ключевыми и меняться не будут, а потенциально изменчивые специально проектируют так, чтобы было легко расширять или вообще переписать при случае.
В общем, не рокет сайнс это никакой, думать надо головой и мат модели строить правильно, требования зарания выяснять, опять же. А не "там походу разберемся".
Ну у вас на одной стороне "там походу разберёмся", "не думать головой", "не выяснять заранее требования". А с другой те, кто набравшись опыта, и соответственно умея проектировать, пишут всё с первого раза на чистую. Ерунда какая-то. Чёрное и белое. Раздолбаи и гении.
Я веду речь о том, что, имхо, такое возможно только с хорошо знакомыми предметными областями. Либо с хорошо формализованными. А типовая ситуация как раз другая, и ключевые "неизменные" правила встают на уши, бизнес решает развернуться на 90 градусов посреди дороги, меняются приоритеты, неразумные сроки, и прочая чертовщина. Многие вещи становятся очевидными только уже в процессе реализации, когда все вынужденно погружаются в мельчайшие детали. И появляются все эти и многие другие проблемы. И гора legacy к тому же. Масштабный рефакторинг это вообще-то нормальное явление.
Т.е. так бывает не когда middle берётся за проектирование, это в целом рядовое явление. Чем опытнее архитектор и co, тем меньше будет граблей и серьёзных изменений\корректировок. Но они будут.
Только вот это скучно, а программисты в большинстве своем — народ, который не любит несколько раз делать одно и то же, а любит делать что-то новое. Поэтому, к примеру, и меняют места работы каждую пару лет — перерос существующее, пора искать более сложное. Вот и получается, что почти в каждом проекте есть неизвестный кусок — новая технология, новый подход, новые требования, и тд.
Другое ее применение — там, где поток требований к программе быстро и непредсказуемо меняется, и расходы на статические описания элементов программы (никто не будет спорить, что это расходы?) себя не оправдывают. Пример — веб-разработка.
Соответственно, самым удобным для универсальных приложений является язык, в котором типизация динамическая, но может быть ограничена для определенных полей, например, с целью оптимизации или дополнительных статических проверок.
Поэтому весь спор напоминает дискуссии о том, что лучше — пиво или мороженое. Смотря кому и для чего.
В общем предмет спора практически исчезает.
В любой программе есть непредсказуемая и предсказуемая части. Даже если вы обрабатываете "непредсказуемый" поток данных, эти данные, во-первых, имеют какие-то общие черты (иначе почему вы вообще их обрабатываете вместе?), а во-вторых, эта программа имеет другую часть — собственно часть, отвечающую за саму обработку, и эту вторую часть можно спроектировать в устойчивых абстракциях. Кстати, даже в статической типизации непредсказуемую структуру можно всегда описать хэштаблицей.
Другое ее применение — там, где поток требований к программе быстро и непредсказуемо меняется, и расходы на статические описания элементов программы (никто не будет спорить, что это расходы?) себя не оправдывают. Пример — веб-разработка.
Тут два варианта: либо требования меняются совершенно непредсказуемо (например, сегодня вы делаете интернет-магазин, а завтра вы его хотите проапргрейдить до социальной сети), либо все-таки есть общая канва продукта, и остальные требования на нее более менее гармонично можно наложить.
В первом варианте не поможет ничто, весь жизненный цикл проекта будет агонией, во втором варианте статическая типизация в умелых руках тащит будь здоров.
Соответственно, самым удобным для универсальных приложений является язык, в котором типизация динамическая, но может быть ограничена для определенных полей, например, с целью оптимизации или дополнительных статических проверок.
Что такое универсальные приложения? Это когда и мессенджер, и календарь, и ядро ОС немношк?
Статическая типизация — это инструмент, который помогает сделать приложение стабильным.
Если в замере скорости разработки учитывать стабилизацию фич, а не просто выкатывание хоть сколько-нибудь рабочего прототипа, то, соответственно, этот инструмент и скорость разработки увеличивает.
Конечно же, любой инструмент хорош только в умелых руках. Если разработчик умеет строить мат. модели и проектировать системы, а не просто набрасывать код на компилятор, то статическая типизация, отнимая немного времени на стадии написания кода, экономит огромное количество времени на всех последующтх этапах разработки.
Правильно — у нас есть много вариантов типов. И современные языки с алгебраическими типами данных не смотря на статическую типизацию отлично справляются. Более того, эти языки больно бьют по рукам того, кто забыл, что float — частично упорядочен, а в json'е может быть null.
расходы на статические описания элементов программы (никто не будет спорить, что это расходы?)
Это не расходы, а инвестиции.
С другой стороны, я понимаю ценителей динамической типизации. До момента появления типажей прошло много времени, а до этого нас много раз заставляли думать про наследование и другие ужасы ООПа, что превращает статическую типизацию в большее зло, чем её отсутствие.
Питон говорит, что это называется "утиная типизация". У объекта может быть вообще враппер над rpc, который все вызовы всех методов тупо передаёт дальше. Не разбираясь, "есть такое или нет". Это, кстати, яркий пример динамической типизации — код не знает, но делает.
> У объекта может быть вообще враппер над rpc, который все вызовы всех методов тупо передаёт дальше.
Внутри враппера он не зависит от конкретного типа. Чтобы пользоваться враппером, надо понимать, какие методы там могут быть, а какие нет. То есть вывести в мозгу тип.
Получается класс-прослойка, который готов выполнить и foo.len(), и foo.p_equal_np(), и foo.transpose(), абсолютно не зная про эти имена в момент написания кода.
Я и говорю что прослойка не знает. Но в том месте где вы пишете foo.len() вам надо знать что при выполнении туда попадет что-то у чего есть метод len.
… Но есть код, который даже не знает что вызывать. Например, многие фреймворки тестирования просто вызывают все функции по маске, не думая о том, что это за функция.
Указанный вами.
— Прочитать её как флоат.
— Прочитать её как инт.
— Перейти на неё как на код.
И всё это не делает происходящее сколь-либо типизируемым. Типы — это ограничения. Когда у нас нет ограничений, то нет и типов.
Когда вы используете mock.MagicMock для замены foo.Bar — ваш mock.MagicMock как бы имеет тип foo.Bar. Код, которому вы передали свой мок, ожидает именно foo.Bar.
mock.MagicMock максимальное приближение к нетипизированному объекту, которое можно придумать. Он не "имеет тип", он не "вызывает ошибок". Ровно так же, как jmp $RANDOM
не вызывает ошибок типизации в машинном коде (даже если это ахинея).
Давайте договоримся, что такое типы. Это ограничения на данные, которые накладывает программист для того, чтобы программа (компилятор) за него помнила и контролировала что можно а что нет. (На самом деле типы — это не ограничения, а разрешения, потому что полиси по умолчанию для языков с сильной типизацией — ничего нельзя).
у него тип mock.MagicMock :) или mock.MagicMock — я не знаю питона глубоко
Насколько я понял, он гарантировано предоставляет. Нет?
Вот это наверное и есть гарантия — какой-нибудь int упадет с ошибкой
Повторю, magicMock, это такой метод отключить типизацию в питоне.
Почему антригарантия. Для тех целей, для которых его создают, он гарантирует жрать и причмокивать. Другое дело, что мне данная практика не нравится.
Т.е. если его создают, то для того. чтобы результат обладал какими-то свойствами. Совокупность этих свойств про которые мы знаем и есть тип. Если мы знаем это в дизайн тайм, то тип статический. Концептуально.
А теперь у нас нечто, что не реализует смысл, а всего лишь выключает ошибки типизации. Внутри там ничего не происходит (я не про счётчики вызова, я про «смысл» — len и т.д.), и в этом отношении он эквивалентен просто коду.
Мы можем сделать jmp куда угодно — нас никто не остановит, и никакого соглашения об ограничениях нет.
Система типов — это соглашение об обозначении смыслов, плюс машинный энфорсинг этих соглашений. mock всё это отключает.
"Это не типизация, это… — «утиная типизация». Что угодно с ".len()" сойдёт в этом месте.
Утиная типизация это тоже типизация. Это просто не номинативная типизация а структурная.
не думая о том, что это за функция.
"функция" это уже тип.
Или проверить наличие этого метода в рантайме, Или проверить тип в рантайме, когда значение придёт. Разве можно назвать статической типизацию параметра функции если пишешь код типа if (param instanceof SomeClass) throw new TypeError(), пускай он даже спрятан под сахаром типа
function someFunction(SomeClass param)`?
1. К примеру, у меня есть функция, которая парсит массив объектов, например Employee, и что-то с ними делает. В JS я могу описать логику этой функции, и затем применять ее к любому массиву объектов, чья внутреняя структура одинакова. Мне не нужно заботится о типе объектов, которые содержатся в массиве. В Typescript, насколько я понимаю, мне пришлось бы внутри моей функции явно указать тип объектов, которые содержатся в массиве. Что-то вроде ...<Employee[]> (прошу прощения, если ошибся в синтаксисе, никогда промышленно не писал на TS). При попытке передать туда массив объектов другого типа компилятор будет ругаться. Мне надо идти в мою функцию и переписывать ее, добавляя новый тип, или делая переключение типов, или создавая свой тип. Точно не помню, но суть — в усложнении возможностей повторного использования. Можно вообще присвоить параметру значение по умолчанию и пускай приходит любой тип. Если он будет расценен, как false, функция возьмет мое значение по умолчанию.
Это же касается и возвращаемого функцией значения. В JS я могу вернуть в зависимости от логики функции, например, false, число или массив чисел. Мне не нужно прописывать 3 типа или выбирать только один их них. Причем возвращаемый результат я могу записывать с одну переменную. Например, присвоив ей при инициализации false, а затем число или массив чисел в зависимости, скажем, от количества возвращаемых данных. В других ЯП мне пришлось бы создавать 3 функции с одинаковыми именами, но с разной сигнатурой.
2. Возможность быстрого сравнения данных разных типов. В частности, приведение к логическому типу. Например, если в метод пришла пустая строка, число 0, значение null или undefined, то все они логически определяются как false. Я могу просто проверить if(!value) и не делать ничего с пришедшими данными. Это позволяет быстро определять значения, непригодные для работы: undefined (если значение не было установлено), null (если явно установлено пустое значение) и какое-то из пустых числовых и строковых значений, трактующихся как false, не прибегая к определению типа этого значение. Мне просто не нужен тип в данным случае. Это также облегчает использование примитивов в параметрах функции. Я ожидаю в пераметре функции массив или строку, а приходит null. Я могу сделать быстрый возврат при получении null. Мне не нужно описывать тип каждого аргумента.
3. Объединение значений разного типа. В JS я могу с легкостью сложить число со строкой. Это удобно при работе в WEB, например при получении данных их формы и произведения с ними каких-то вычислений. Например, я могу объединить имя (строка) и возраст (число) пользователя и вывести для него приветствие, например, «Здравствуйте, Петр. Сумма вашего заказа 30 руб.» Я как-то сталкивался с C# и искренне не мог понять, почему он не дает мне сложить число со строкой. Значения из полей форм в HTML приходят в string, и такая возможность бывает удобной. Это не камень в огород C#, просто я привык мыслить по-другому.
4. Отдельно отмечу возможность логического сравнения в операторах && и ||. Например, один из излюбленных приемов среди JS-программистов:
someFlagValue && anotherValue
Позволяет вернуть, например, в шаблон второе значение, только если первое истинно.
а
firstValue || secondValue
одно из истинных значений. Плюс в том, что мне не нужно думать, какой тип придет в них (undefined, null или пустая строка), для меня главное, как значение будет оцениваться логически.
5. Не уверен точно, но мне кажется, что для создания массива в ЯП со строгой статической типизацией нужно, чтобы данные в массиве были одного типа. Часто это неудобно, особенно если нужно потом эти данные в качестве параметров функции в виде массива. В JS можно запихнуть в массив данные разных типов.
6. Автоматическое приведение к числу. Это одна из моих любимых вещей. Посколько данные из HTML-форм приходят в программу в виде строк, даже числа, нет ничего странного, что в JS я могу проверить правильность
'2' > '3'
Например, когда у меня есть выпадающий список с перечнем годов и мне нужно сравнить 2 значения. В других ЯП часто нужно делать что-то вроде String.Compare(str1,str2)
или ParseInt
7. Устойчивость программы. Javascript позволяет гибко производить вычисления с данными разных типов. Да, эти вычисления могут быть ошибочными, но программа продолжит работу. Разумеется, это неприемлемо в ПО, предназначенном, скажем, для медицины и космоса, но вполне допустимо для показа на карте 10 ближайших баров в округе. Да, может быть, где-то будут ошибки с точными координатами или в названии одного из баров затесается "[object Object]", но приложение продолжит работу, что в данной ситуации, возможно, более важно.
Возможно, такое использование языка несколько усложняет жизнь и на нем бывает тяжелее писать, но мне нравится его гибкость. Кстати, я всегда считал и продолжаю считать, что программирование на JS не легче, как это принято считать, а наоборот тяжелее, чем на языках со строгой статической типизацией. Есть еще и другие вещи которые мне нравятся в JS, но я уже много написал, да и не все сразу вспоминается.
Ответил вам ниже: https://habr.com/post/431250/#comment_19426588
Мне не нужно заботится о типе объектов, которые содержатся в массиве. В Typescript, насколько я понимаю, мне пришлось бы внутри моей функции явно указать тип объектов, которые содержатся в массиве.
Нет, не пришлось бы. Есть генерики, то есть пишите что-то вроде: parse(arr: T[]) {...} и теперь оно работает для любого Т.
Мне не нужно прописывать 3 типа или выбирать только один их них. Причем возвращаемый результат я могу записывать с одну переменную. Например, присвоив ей при инициализации false, а затем число или массив чисел в зависимости, скажем, от количества возвращаемых данных. В других ЯП мне пришлось бы создавать 3 функции с одинаковыми именами, но с разной сигнатурой.
Прекрасно работает:
function yoba(x: number) {
if (x < 0) {
return true;
} else if (x === 0) {
return 0;
} else {
return [''];
}
}
let res = yoba(-10);
res = 0;
res = ['1', '2'];
тип res: number | boolean | string[]
Я ожидаю в пераметре функции массив или строку, а приходит null. Я могу сделать быстрый возврат при получении null. Мне не нужно описывать тип каждого аргумента.
function yoba(strs: string[] | null) {
if (strs) {
return strs.reduce((x, y) => x + y, '');
} else {
return '';
}
}
В JS я могу с легкостью сложить число со строкой.
Да не проблема:
function yoba(x: string, y: number) {
return x + y;
}
Плюс в том, что мне не нужно думать, какой тип придет в них (undefined, null или пустая строка), для меня главное, как значение будет оцениваться логически.
И не думайте!
function yoba(x: undefined | null | boolean | string) {
return x || 'yoba';
}
Не уверен точно, но мне кажется, что для создания массива в ЯП со строгой статической типизацией нужно, чтобы данные в массиве были одного типа.
const yoba = [1, true, 'yoba'];
yoba: (number | boolean | string)[]
Автоматическое приведение к числу. Это одна из моих любимых вещей. Посколько данные из HTML-форм приходят в программу в виде строк, даже числа, нет ничего странного, что в JS я могу проверить правильность
const yoba = '1' > '2';
yoba: boolean
Устойчивость программы. Javascript позволяет гибко производить вычисления с данными разных типов.
типы не меняют семантику рантайма
function yoba(x: undefined | null | boolean | string) {...
Не уверен, что приведенный вами пример обладает хорошей читаемостью
Все-таки
function yoba(x = 'Default') {...
воспринимается куда легчеconst yoba = [1, true, 'yoba'];
Это также код и на JS. Не думаю, что C# или Java позволят такое напрямую без создания объектных оболочек.
Не думаю, что C# или Java позволят такое напрямую без создания объектных оболочек.
В вашем ecmascript любое число уже изначально обладает своей объектной оболочкой! Так что неявное их создание компиляторов никак не может быть записано в недостатки C#.
В вашем ecmascript любое число уже изначально обладает своей объектной оболочкой! Так что неявное их создание компиляторов никак не может быть записано в недостатки C#.
Да, но для программиста при разработке тип примитива не будет является объектом с точки зрения языка. Это позволяет писать более лаконичный код. Разумеется, в JS можно написать код вида const myNum = new Number(2);
Но тогда «myNum » явно будет объектом, а можно использовать сразу тип «number».… и что? Вопрос-то был про массив из разнородных данных.
const yoba = [1, true, 'yoba'];
аналог кода на C# в виде массива из разнородных данных.
Не уверен, что приведенный вами пример обладает хорошей читаемостью
С-но из вашего описания было не совсем понятно, что требуется. Если речь просто о дефолтном значении, то можно точно так же и написать:
function yoba(x = 'Default') {
return x;
}
yoba: string? -> string
Это также код и на JS.
Это ts, он статически типизирован, все ок. Да, в c# или java так нельзя, потому что система типов в этих языках проектировалась несколько с другим прицелом.
Да, в c# или java так нельзя
Да все можно же: var yoba = new object[] { 1, true, "yoba" };
Да, в c# или java так нельзя
Ну да, с кастом до object можно, но это наверное все же несколько другой кейс. С-но, в тс можно типизировать и как const x: [number, boolean, string] = [1, true, '']; в c# будут таплы ближайшим аналогом (с тем исключением что таплы не являются подтипами массивов), есть ли что-то похожее в джаве — хз.
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
Ваш пример больше похож на создание именно объекта.
Простите, но вы несете чушь. Признак массива — []
(квадратные скобки), а вовсе не int
. Вместо int
можно написать любой тип данных. В том числе object
. В моем коде написано object[]
, что определяет массив объектов.
что определяет массив объектов.Но я просил привести пример на C# не массива объектов, а массива, в котором содержатся несколько значений с разными примитивными типами данных.
Ну так в массиве объектов как раз и содержатся "несколько значений с примитивных типом данных". Что вам не нравится-то?
В js вы при итерации точно так же получаете не-пойми-что, и чтобы обработать его как число — вам надо сделать проверку типа. Единственная операция, которую вы можете сделать без проверки типа — передать это не-пойми-что куда-то еще.
Также мне хотелось бы узнать, сможете ли вы передать созданный вами массив в функцию в качестве параметра?
А что, собственно, может этому помешать?
Тем, что внутри вашего массива лежит объект, который при итерации выдаст в качестве item именно объект. В моем примере на JS при итерации, скажем, с использованием «forEach» вы получаете доступ сразу к нужному значению массива.
В его примере точно так же будет доступ к нужному значению массива.
С-но из вашего описания было не совсем понятно, что требуется. Если речь просто о дефолтном значении, то можно точно так же и написать
Вам для того, что в TS это заработало, пришлось перечислить все типы для x, которые могут прийти в функцию:
function yoba(x: undefined | null | boolean | string) {...
Я лишь подчеркнул, что JS этого не требует.
Вам для того, что в TS это заработало, пришлось перечислить все типы для x, которые могут прийти в функцию:
Так это два разных кода. Если вы на js напишете function yoba(x = 'Default') { то обрабатывать дефолтным образом оно будет только undefined, в точности как вариант на TS, null, false и все остальное потребуется описать руками.
Ну а если вам просто лень писать | null | undefined… то можете просто сделать:
type Falsey<T> = T | null | undefined | false | '' | 0;
и использовать везде.
Ну а если вам просто лень писать | null | undefined… то можете просто сделать
А чем это тогда отличается от того, что есть в JS по умолчанию?
Неужели тем, что компилятор проверит корректность типов в вашем коде статически?
1) То есть функция принимает массив чего-то, делает что-то и возвращает то ли строку, то ли массив, то ли bool? Как вы вообще понимаете что у вас в коде происходит?
Да, например, если нужное значение не вычислено или данных не хватает, то можно вернуть «false». Если нужно вернуть одно число, то оно и возвращается, если чисел больше одного, что можно вернуть массив.
«Because I can»
Вообще тут вопрос в том, как следует обрабатывать данные. Если все кейзы обрабатываются однородно, то имеет смысл возвращать пустой массив вместо null и массив из одного элемента вместо числа. С другой стороны, если эти случаи на call site требуется обработать специфически — лучше как раз сделать тип number[] | null | number — тогда мы гарантированно не забудем обработать кейзы с пустым и одинарным значением, в то время как в случае возвращаемого типа number[] их можно прощелкать.
если эти случаи на call site требуется обработать специфически
Специфика использования лежит вне зоны ответственности библиотечной функции. Сейчас это так, завтра — иначе, послезавтра появляется другой «клиент» с иной спецификой. Абстракция же.
Специфика использования лежит вне зоны ответственности библиотечной функции.
Способ обработки зависит от данных. А тип — фиксирует соответствующие инварианты
Если данные логически предполагают определенный способ обработки — то они предполагают. Если перестают предполагать — это уже другие данные другого типа, что логично.
Очень сложно придумать кейс, когда действительно нужна такая вариативность и нет нарушения SRP.Ну не знаю, может быть, вас это удивит, но в своей работе я практически ежедневно сталкиваюсь с подобным кодом. Именно поэтому эта особенность вспомнилась мне одной из первых.
Вопрос, что она делает вместо того, чтобы упасть с ошибкой. И насколько очевидно то, что она делает. В некоторых случаях лучше уж упасть.
У меня не выходит найти иного оправдания этому, кроме экономии букв.
Гибкие функции — это нарушение SRP
Так SRP это подход объектно-ориентированного, но не функционального программирования.
Почему «божественные сущности»?
Потому что слишком много ответственности на себя берут. «Вокруг них все крутится».
Можно, например, в зависимости от переданных параметров
Функции могут быть с одинаковой сигнатурой (иначе как потом узнать способ их вызова?), вместо false возвращать null, например.
Функции могут быть с одинаковой сигнатурой (иначе как потом узнать способ их вызова?), вместо false возвращать null, например.Могут, но если вам достался код, где чья-то функция вызывается много где по коду, и вам нужно внести в нее изменение или дополнить функционал, то проще внести изменение в эту функцию, чем найти все места ее вызова, и с риском для других частей приложения вызывать в некоторых местах другую функцию, которую создали уже вы.
Так то в готовых проектах. А я говорю про ситуацию, когда хочется заменить одну функцию на другую, но страшно, потому что фиг знает что в нее на самом деле передается и что из нее ожидается. Вот именно в этой ситуации статическая типизация и помогает.
Но есть в жизни и хорошие вещи:
- тесты смогут подтвердить, что новые функции продолжают выполнять старые контракты (или укажут на то, как нужно изменить клиентский код для соответствия новым контрактам);
- аффект будет меньше: при плохом сценарии рискуют пострадать только некоторые (непосредственно замененные) точки вызовов, а не все сразу.
вы реально хотите с десяток вариаций console.log?
для каждого примитивного типа данных. для number, для string и т. п.
console.log
остается одним и тем же для разных типов данных. Но если он захочет для number варить кофе и возвращать промис готовности — вот это мы оттуда должны убрать.Не обязательно, можно будет просто сделать тип-сумму.
Хотя запашок у подобной потребности определнный есть.
Я верю, что так делать никогда не надо. Вопрос про "можем"/"не можем"))
Во-первых можем.
Во-вторых хотя можем, лучше никогда так не делать)
Не, алгоритм разный. Разные алгоритмы преобразования к строке у строк, чисел, объектов и т. п.
динамическая диспетчеризация в помощь.
То есть проверка типов в рантайме?
Ну а как реализуются разные ветви в зависимости от типа аргумента функции?
А как определить 10 или 0, если мы не знаем какого типа будет аргумент?
Виртуальные таблицы не являются частью "как работает ООП" — это деталь конкретной реализации. И, если вы вдруг о параметрическом полиморфизме, то ООП тоже о нём не упоминает.
null
на месте ссылки.*.prototype.toString
, а console.log лишь отправляет результат на консоль.А потом сидишь с коллстеком в 50 "супер-гибких функций", и пытаешься понять, кто данные попортил, а возможно и не один раз. Каждая ведь решила "гибко" адаптироваться. И дебажишь 4 часа, чтобы понять, что в от в этом параметре число нужно в строковом представлении передавать, а не в числовом.
Аналог стандартного console.log
Вопрос то был в том как вообще понять что оно принимает и что возвращает? Ну, потом, когда протрезвеешь.
Для того, чтобы сделать это ответственно, надо проанализировать все места где она используется и понять, что они расчитаны на новый тип результата.
При статической типизации вам это поможет сделать анализ типов и рефакторинги.
> и не уронить всё приложение.
Вы просто не заметите, ибо п.7
Или же вы функцией берете из нереляционной базы какие-то данные — так уж получилось, что не вы её заполняете и в ответ может прийти и число, и масив, и даже фотография. Потом, в зависимости от типа, передаете эти данные нужному обработчику.
Если вы получает из «нереляционной базы какие-то данные» — причём то фотографию, то вообще песенку, то какой у всего этого действия физический смысл? Что пользователь-то должен увидеть?
Вам нужно сделать фронт-админку для этого. Вы отправляете на бек запрос с айдишником опроса, в ответ от присылает вам всё что заполнил юзер — массив из данных разных типов. Вы каждый раз получаете следующий элемент этого массива — цикл по сути, в js это можно сделать функцией forEach() — и оно возвращает каждый раз данные разного типа. В зависимости от типа данных вы рендерите по-разному: где-то добавляете тег img, где-то checkbox, где-то просто вставляете строку.
Вот и получается что-то такое странное:
let arr = returnedDataFromBackend;
arr.forEach((ele) => {
// функция каждый раз возвращает переменную ele разных типов
if (typeof ele === 'string') return renderString(ele);
if (ele.type == 'image') return renderImage(ele);
...
});
Разумеется, этот код можно улучшить. К примеру, подойти к беку и сказать, чтобы он отдавал мне все данные в виде массива хешей с двумя полями: type и value. Либо самому написать прослойку которая нормализует полученные данные, а потом передает их дальше. Но это уже совсем другая история :)
При этом либо среда, либо даже компилятор за вас проконтролируют, чтобы вы все варианты правильно обработали (а сколько раз я видел, что кто-то один из вараинтов пропускает в языке с динамической типизацией — не в сказке сказать, ни пером описать).
Так что этот случай ничуть статической типизации не противоречит и, более того, является классическим случаем для демонстрации достоинств статической типизации.
А вот если мы имеем заранее неизвестный набор типов и понятия ни имеем — что с ними делать… зачем это? Куда это? Как это?
В Rust для этого есть impl Trait
и Box<dyn Trait>
. Как раз для примера из коммента от 0xd34df00d, когда мы не знаем, что за сущность нам пришлют, но знаем, что она будет уметь некоторую нужную нам функциональность.
Так что этот случай ничуть статической типизации не противоречит и, более того, является классическим случаем для демонстрации достоинств статической типизации.
Да, только на деле потом оказывается, что программист в качестве типа ответа прописал String, в базе оно лежит как VARCHAR(255), и для того, чтобы добавить в варианты картинку, придется переписать десять классов и три шаблона. А сделать это надо буквально вчера.
Так может эти баги никто и не заметит, чего вы волнуетесь сразу! :)
В итоге силы — сэкономлены, заказчик — доволен. Пока… :)
С тех пор прошло уже 2 года. До сих пор не работает, уже 4 года в сумме. Возможно, это и правда кому-то кажется нормальным…
Дело в подходе. Нормально ли оставлять недоделки на потом. Если нормально — то динамическая типизация и правда может помочь сделать что-то быстрее и уложиться в сроки (первые несколько раз).
Если ненормально — то вам все равно придется менять весь код, который использует эту самую строку. И статическая типизация просто поможет его найти.
А за счет какого волшебства у вас в динамическом ЯП количество классов уменьшится?
Для начала введем базовый класс, AbstractAnswer, и от него сделаем двух наследников: AnswerString и AnswerImage. Потом добавим к этому фабрику AnswerFactory — она будет брать сериализованный JSON, пришедший от API, и порождать нужный класс. Конечно же, у каждого из классов будет свой метод render, каждому нужен свой шаблон…
В динамическом языке все будет попроще, менять будем только рендер:
if (typeof(answer) == "object" && answer.type == "image") {
return renderImage(answer.image);
} else {
return renderText(answer);
}
Это только фронтэнд, про бэкэнд я молчу.
Речь на самом деле не про статическую/динамическую типизацию, а про номинальную/структурную. Просто с динамическим рантаймом структурная типизация дается проще.
Ну а как иначе? Вот был у нас ответ с типом String, а теперь должен быть и String, и Image. Начинаем переписывать!
Ничего не начинаем, просто пишем в типе возврата string | image, а потом тот самый ваш код выше.
Нет. Для того чтобы передавать string | image
, совершенно необязательно иметь в рантайме полную информацию о типах. Достаточно чтобы разные типы были различимыми.
И, кстати, у рантайма не может быть динамической типизации. Динамическая типизация может быть характеристикой только языка, но не рантайма.
Рантайм динамического языка должен обеспечивать гарантии, даваемые системой типов языка. Если я написал function a(int b) на динамическом языке, то рантайм должен дать TypeError при попытке объект, например, передать.
Да, а рантайм статического языка такую гарантию обеспечивать не обязан. Но какое отношение это имеет к типам-объединениям или типам-суммам?
В том же С++, например, рантайм в общем случае ничего не знает про типы передаваемых параметров. Но это ничуть не мешает существованию std::variant
или даже std::any
А какое отношение эти типы имеют к, например, PHP? )
И правда, какое же? И при чем тут PHP?
Язык с динамической типизацией, дающий в рантайме гарантии своей системы типов. В последнее время всё более строгие гарантии.
Во-первых, вы писали про "переписать 10 классов", а не про "написать 10 классов".
Во-вторых, не выдавайте свои фантазии на тему ООП за недостатки статической типизации. Нахрена тут фабрика, да еще и абстрактная?
В-третьих, исходно предлагалось, вообще-то, использование готовых языковых либо библиотечных механизмов вместо наследования. Вот я пишу: std::variant<std::string, Image>
— где тут наследование и лишние классы?
Допустим, вам надо отправить сообщение юзеру, для этого нужно получить канал отправки по id юзера, а потом отправить это сообщение по каналу. У вас есть функция, которая принимает на вход user_id, на выходе отдает один из нескольких типов данных: или экземпляр класса SMS, или экземпляр класса Email, или экземпляр класса Websocket, или вообще null.
То есть вы думаете что результат является каналом отправки, и SMS, Email и Websocket являются каналами. Это похоже на описание типов
UPDATE Мне в этом смысле golang нравится — есть структуры (типы), а есть интерфейсы (поведение). В итоге разные структуры (SMS, Email и Websocket) реализовывают одно и то же поведение — отправку сообщения.
Все это объекты. Да, есть типы есть подтипы.
> олько зачем тогда вообще типы нужны, если мы их все приводим к одному супертипу?
Там где мы приводим, мы не используем их особенности, а там где не приводим используем. В результате мы знаем какой код полагается на какие фичи и это контроллируется автоматически.
В целом вы описываете преимущества слабой типизации перед сильной, а не динамической перед статической.
При попытке передать туда массив объектов другого типа компилятор будет ругаться.
Typescript использует структурную типизацию. Это, в частности, означает что компилятор не будет ругаться если у объектов другого типа есть все нужные поля.
Возможность быстрого сравнения данных разных типов. В частности, приведение к логическому типу.
Не является особенностью динамической типизации.
Я как-то сталкивался с C# и искренне не мог понять, почему он не дает мне сложить число со строкой.
Вранье, C# это разрешает делать.
Не уверен точно, но мне кажется, что для создания массива в ЯП со строгой статической типизацией нужно, чтобы данные в массиве были одного типа.
В том же C# любое значение может быть приведено к object
и положено в object[]
.
Автоматическое приведение к числу. Это одна из моих любимых вещей.
Это одна из ужаснейших вещей. Простота перевода между строками и числами заставляет программистов забывать об отличиях между ними, равно как и о региональных особенностях.
Вам, как русскоязычному программисту, должно быть известно что иногда в качестве десятичного разделителя выступает запятая, а не точка, и про связанные с этим проблемы.
Устойчивость программы. Javascript позволяет гибко производить вычисления с данными разных типов. Да, эти вычисления могут быть ошибочными, но программа продолжит работу.
Чтобы программа продолжала работу при ошибках — нужно не забывать ставить try/catch. А продолжать вычисления после ошибки типизации — бессмысленно.
Typescript использует структурную типизацию. Это, в частности, означает что компилятор не будет ругаться если у объектов другого типа есть все нужные поля.
А нужных полей может и не быть. Объекты могут незначительно различаться, но при этом все равно быть успешно распарсены одной JS функцией — утилитой.В том же C# любое значение может быть приведено к object и положено в object[].В JS для этого не нужно создавать объект. Можно положить в массив как примитивы, так и объекты:
const customArr = [1, 'two', {three: 'three'}];
Чтобы программа продолжала работу при ошибках — нужно не забывать ставить try/catchДля этого придется обертывать в try/catch практически весь код, а не предположительно наиболее уязвимые участки кода, как обычно делается.
Вам, как русскоязычному программисту, должно быть известно что иногда в качестве десятичного разделителя выступает запятая, а не точка, и про связанные с этим проблемыДля условно «целых» чисел, не содержащих разделители, эта проблема неактуальна.
Это одна из ужаснейших вещей. Простота перевода между строками и числами заставляет программистов забывать об отличиях между ними, равно как и о региональных особенностяхЭта особенность наоборот заставляет не забывать, а помнить об этих отличиях. Насчет «ужаснейшей», как говорится, это дело вкуса.
А нужных полей может и не быть. Объекты могут незначительно различаться, но при этом все равно быть успешно распарсены одной JS функцией — утилитой.
Так заведите интерфейс, где будут перечислены именно те поля, которые требуются этой функцией. Вам же все равно придется где-то эти поля перечислить если вы собираетесь использовать функцию с произвольными объектами через два года.
В JS для этого не нужно создавать объект.
Почему приведение типа к object вы называете "созданием" объекта?
Так заведите интерфейс, где будут перечислены именно те поля, которые требуются этой функцией. Вам же все равно придется где-то эти поля перечислить если вы собираетесь использовать функцию с произвольными объектами через два года.Ну к JS понятие «требуется функцией» не вполне применимо. Переданных параметров может быть больше или меньше, чем описано аргументов в сигнатуре функции.
Почему приведение типа к object вы называете «созданием» объекта?Насколько, я знаю, в C# массивы не могут иметь разные типы данных. Это принцип проектирования массивов в этом языке. Поэтому я предполагаю, что вы имели ввиду создание объектной оболочки над примитивом. В отличие от моего примера, где используется сам примитив.
Ну к JS понятие «требуется функцией» не вполне применимо. Переданных параметров может быть больше или меньше, чем описано аргументов в сигнатуре функции.
Вполне себе применимо. Программист, когда писал функцию, рассчитывал что у переданных ему объектов есть вполне конкретные поля. Без этих полей функция будет работать не так, как задумывалось ее автором. Это и есть "требуется".
Насколько, я знаю, в C# массивы не могут иметь разные типы данных. Это принцип проектирования массивов в этом языке. Поэтому я предполагаю, что вы имели ввиду создание объектной оболочки над примитивом. В отличие от моего примера, где используется сам примитив.
Вас что, вручную заставляют создавать эту самую объектную оболочку? Так нет же, компилятор автоматически приводит любое значение к object
! Что же до эффективности такой операции — не js-нику жаловаться на производительность упакованных объектов в c#...
Программист, когда писал функцию, рассчитывал что у переданных ему объектов есть вполне конкретные поля. Без этих полей функция будет работать не так, как задумывалось ее авторомНет, поведение функции может подстраиваться под переданные аргументы и их количество или даже их отсутствие. А способ подстраивания обычно и задумывается автором.
Объекты могут незначительно различаться, но при этом все равно быть успешно распарсены одной JS функцией
придется обертывать в try/catch практически весь код, а не предположительно наиболее уязвимые участки кода
Для условно «целых» чисел, не содержащих разделители, эта проблема неактуальна.
Точность, достойная тру-разработчика. Просто восхитительно. Надеюсь, вас в жизни не допустят до написания кода систем, имеющих хоть какое-то отношение к человеческому здоровью/жизни.
"Наши самолеты в целом более менее скорее летают, чем падают. Предположительно большинство из них долетит до места назначения, наиболее похожие на критически уязвимые места в коде мы с большой вероятностью обернули в try/catch
".
Точность, достойная тру-разработчика. Просто восхитительно. Надеюсь, вас в жизни не допустят до написания кода систем, имеющих хоть какое-то отношение к человеческому здоровью/жизни.Спасибо за характеристику меня, как разработчика)) Но я в своем развернутом мнении выше как раз и описал, что это
… неприемлемо в ПО, предназначенном, скажем, для медицины и космоса, но вполне допустимо для показа на карте 10 ближайших баров в округеОсобенно, если приложение для показа баров должно было быть готово «вчера», но узнали об этом, как обычно, сегодня.
TypeScript:
// Динамика это круто
function f(a, b) {
// Будь что будет
return a * b;
}
f(1, "abc") // Ну и что, что получаем NaN, зато не надо писать типы. Красота !
// Укажу динамический тип. Динамика это круто
function g(a: any, b: any): any {
// Будь что будет
return a * b;
}
// Статика это тоже круто
function h(a: number, b: number): number {
// Умножаем числа
return a * b;
}
h(1, "abc") // Плохой компилятор, не хочу ошибку компиляции
C#:
public class Program
{
// Динамика это круто
public static dynamic g(dynamic a, dynamic b) {
// Будь что будет
return a * b;
}
// Статика это тоже круто
public static int h(int a, int b) {
// Умножаем числа
return a * b;
}
public static void Main()
{
g(1, "abc"); // Кидает исключение, ерунда. Зато принимает любые типы
h(1, "abc") // Плохой компилятор, не хочу ошибку компиляции
}
}
И да. Очень хотелось бы чтобы подобную статью написал кто-то из сильных js разработчиков со своей колокольни. Надеюсь эта дискуссия кого-нибудь из них сподвигнет. Буду каждый день просматривать хаб по джаваскрипту :))
Да ладно.
Метапол против полипропилена.
А если наберёте Festool, то откроете ворота в ад.
Один показал свой набор инструмента, состоявший из кажется 4 плотно упакованных тех самых систейнеров Festool, а второй его тут же раскритиковал, и заявил, что ничего из этого инструмента на монтаже мебели не нужно. И в следующем видео продемонстрировал свой набор инструмента из Китая (на самом деле кажется из Польши, но такого же примерно уровня). Это было весьма эпично.
Метапол против полипропилена.
Да проще. Лён против фума =)
Давайте я попробую. У меня есть интересный опыт вида Scala -> JavaScript (и сервер и фронт) -> Typescript -> Scala.
- всем кто пишет на строготипизированных языках нужно обязательно попробовать языки со слабой типизацией и наоборот. Например, на домашних поделках внезапно оказывается, что можно не писать тонны бойлерплейта (привет Java), и не воевать с компилятором. Такое вот чистое творчество.
- как правильно заметили выше — в слаботипизированных языках типы никуда не исчезают. Они просто становятся сильно менее строгими и позволяют больше фривольностей.
- судя по всему существует определённый предел для проекта с со слабой типизацией. Это и количество задействованных одновременно людей (интерфейсов нет), и объем проекта (типы — документация). Выше некоторого объема начинается настоящая боль.
- насколько я вижу сейчас, в мейнстримовых языках типы — не панацея, но здорово помогает. Системы типов либо не достаточно мощные (Java, например), чтобы выражать всё задуманное. Либо чересчур сложные (Haskell, Agda, Idris), чтобы на этом можно было писать бизнес-код средними бизнес-программистами. Плюс частенько порождает некоторые теоретические тупики, которые императивно решаются в десяток строк кода, а функционально заставляют весь мир намотать на себя. Есть Scala, которая пытается быть и тем и другим, но у неё свои проблемы есть.
- как минимум в мейнстримовых языках со строгой типизацией от написания тестов никуда не сбежать — система типов не достаточно мощная, чтобы выразить все операции с данными, плюс в игру вступают сайд-эффекты, внешний мир, базы данных, итд. Со стороны приёмочного тестирования, едва ли тестов меньше. В юнит-тестах — возможно.
Вообще, безотносительно самого JS мне очень нравится связка JS + TS из-за gradual typing. Хочется писать быстро — пишешь быстро на JS и надеешься на лучшее. Хочется писать надежнее — развешиваешь типы и спишь по ночам крепче.
Если кому-то интересно, я с радостью могу поотвечать на волнующие вопросы :)
Немного не в тему, но как вообще со скалой, с вакансиями и т.п.? Мне многие знакомые советуют с C# свичнутся, для большей продуктивности, но я не вижу ни вакансий, ни потребностей рынка… С удовольствием почитал бы статью, но сойдет и комментарий.
Они есть, но если сравнивать с C#, Java и тем более JS — значительно меньше.
Вообще на мой взгляд Scala сейчас переживает не лучшие времена. Волна хайпа прошла, scala-only фичи потихоньку подвозят в мейнстримовые языке (те же лямбды в Java заставили задуматься очень многих о том действительно ли им всё это надо или и джавы хватит). Kotlin, опять же появился.
Вообще по своему Scala-опыту могу сказать, что использовать Scala как Better Java не очень хорошо выйдет. Оно хоть и достаточно интероперабельно, но не так всё гладко. Плюс многие Scala-библиотеки делают вещи scala-way (чем и хороши). В этом плане Kotlin выглядит сильно лучше.
Но и нельзя сказать что рынок совсем глух. Даже на неактивный в данный момент профиль на upwork раз в пару недель уж точно инвайт на Scala-интервью прилетает.
Ну и еще такое дело… чтобы понимать хайповые Scala-штуки, нужно хоть немного уметь Haskell. Не то, чтобы обязательно, но это сильно упрощает жизнь и даёт понимание того, что коллектив авторов там не совсем упоролся, а просто решает функциональным образом некоторые проблемы.
Просто хочется нормальный паттерн-матчинг, нормальные атд (и гадт при должной фантазии), типы высших порядков, literal типы, path-dependent типы и т.п., человеческие трейты, импликиты, макросы, развитая и популярная ФП экосистема… Беттер джава не сильно инетерсует, скорее интересует вариация "раста без борроу чекера, на гц, но с хкт". Просто у меня выбор как раз между ними. С одной стороны не очень популярная скала. С другой — раст, которым не очень легко заниматься в мейнстрим разработке, даже коллеги-растовчане на нетривиальные вопросы предпочитают отвечать в духе "а нафига ты пишешь это на расте?". Хаскель сильно академичный + мне не нравятся многие вещи (вроде специального синтаксиса для filter
, странные операторы вроде ++
и т.п.). А больше и нет альтернатив, где можно писать типичные-задачи более удобно и красиво.
filter (>5) [1,2,3,4,5,6,7,8]
IMHO, это само совершенство!
Я про [x | x <- [50..100], x 'mod' 7 == 3]
. Как по мне, странноватое решение.
Ну вот часть x 'mod' 7 == 3
это чисто filter
, зачем её выносить в синтаксис если можно было бы просто вызвать как функцию как в вашем примере не сильно понятно.
Я ж надеюсь, Вы не про этот Zombie?..
Idris это вообще уже удел оторванных чистых математиков, которые еще и ХМ без ограничений юзают :)
Хотя я, конечно же, могу ошибаться. Для меня это 'Known fact', без какого-то конкретного достоверного источника.
Хороший оффер это сколько?
Хороший я больше считаю по вакансии, нежели по деньгам. А то последний год я проработал в подвале, вот там было не очень :D
Если по деньгам, мне кажеся от 3к белыми на сениор позицию это хорошие деньги.
P.S. Да, на всякий случай ко всем. Если хотите изучить функциональщину, играться со scala будет далеко не лучшей идеей. Слишком многое scala позволяет и Вы ничему не научитесь. Начните с Haskell. Он просто не позволит Вам писать не идеоматично. Сам сейчас именно его потихоньку изучаю.
Со scala.js не работал сам, но на одном из проектов работал с фронтендерами, которые как раз выкидывали всё написанное ими же на scala.js в пользу более джаваскриптовых фреймворков.
Тогда меня фронтенд не интересовал совсем, но эти ребята говорили, что:
а) реальной гетерогенности нет
б) тулинг ужасный
в) готовые фронтендовые либы очень грустно интегрируются
г) и если всё правильно помню, то на выходе у них получался огромный бандл
И, честно говоря, ощутив на себе проблемы на стыке JS и TS, при том что TS надмножество JS, могу сказать, что я бы подумал несколько раз крепко, прежде чем использовать что-то такое развесистое поверх JS.
>вообще самое лучшее что сейчас существует для фронтэнда.
Не соглашусь. Безобразной поддержки IDE уже было бы достаточно. Впрочем, расскажите, почему вы так думаете?
Мне кажется, что автор неоправданно сравнивает C# с JS, противопоставляя статическую типизацию динамической. JS слабо типизирован, но не все языки с динамической типизацией слабо типизированы. Далеко не во всех языках с динамической типизацией можно к строке прибавить число.
Кроме того, не с проста во многих языках со статической типизацией есть тип any. Даже в C# есть утиная типизация.
Это я к тому, что вероятно, неверно относить язык к тому или иному виду типизации, не говоря уже о том, чтобы выбирать язык только по этому параметру. Да, есть языки которые сильно тяготеют к какой-то одной стороне. Но есть и такие, которые "плавают" где-то близко к середине.
Как бы вы ответили на вопрос: какой язык лучше, со слабой статической типизацией, или сильной динамической?
Это я к тому, что вероятно, неверно относить язык к тому или иному виду типизации
Язык либо динамически типизирован, либо статически. Он не может "плавать" где-то посередине. В языке могут быть различные средства для того, чтобы что-то почерпнуть из другого мира, например dynamic
в C#. Но от того, что в сишарпе есть dynamic
он не стал динамически типизированным.
Еще, например, в С# есть неявное приведение типов. Но оно работает, только если заранее описать, как оно должно работать, оно не работает для всего и всегда. Да и делается обычно это для каких-то совсем очевидных случаев, вроде int -> long
.
Между тем, утиная статическая типизация это все равно жесткая типизация, абсолютно не важно, что она не номинальная. Она все равно жестко следит за соблюдением объявленного контракта, и если этот контракт нарушить, то ошибка проявит себя еще до запуска приложения: код не скомпилируется.
Как бы вы ответили на вопрос: какой язык лучше, со слабой статической типизацией, или сильной динамической?
Я не знаю ни одного языка со слабой статической типизацией, и не представляю, кому это могло бы понадобиться. Если уж взялся делать в языке статику — нахера делать ее слабой, в чем тогда смысл?
Я не знаю ни одного языка со слабой статической типизацией, и не представляю, кому это могло бы понадобиться.
Язык: Си, зачем: fast inverse square root =)
Да и делается обычно это для каких-то совсем очевидных случаев, вроде int -> long.
Корректней сказать для примитивных типов и при отсутствии потери информации int (4 байта) -> long (8 байт). Обратное преобразование породит ошибку компиляции.
… от того, что в сишарпе есть dynamic он не стал динамически типизированным
Возможно, я что-то упускаю, т.к. не знаком с C#, но разве dynamic не использует DLR? Это же не библиотека, а часть языка.
Язык либо динамически типизирован, либо статически.
Здесь соглашусь. Я имел в виду, что те вещи, которые ассоциируют в основном со статической типизацией, характерны и для многих языков с динамической типизацией. И наоборот. Те же object/any, например.
По поводу статьи:
Статическая типизация не панацея. Например, статическая типизация в Java не помогает отловить NullPointerException.
Вот, чуваки утверждают:
A statically typed language can catch some errors in your program before it even runs, by analyzing the types of your variables and the ways they're being used. A dynamically language can't necessarily do this, but generally you'll be writing unit tests for your code either way (since type errors are a small fraction of all the things that might go wrong in a program); as a result, programmers in dynamic languages rely on their test suites to catch these and all other errors, rather than using a dedicated type-checking compiler.
И действительно, как часто, по вашему, у программистов на языках с сильной динамической типизацией возникает несогласование типов (none/null и т.п. не в счёт)? А в статически типизированных?
И каким образом компилятор статически типизированного языка поможет мне защититься от внешних данных с неверным типом?
Нашел на просторах интернета текст, с которым по большей части солидарен, и дабы не пересказывать, оставлю цитату:
Благодаря определению типов во время исполнения программы сильно облегчается метапрограммирование. Очень сильно облегчается. Оно, в свою очередь, упрощает всю остальную работу.
Благодаря гибкости кода в рантайме (см. тот же duck typing) и интроспекции (анализ свойств объектов и кода) получается на порядок проще и быстрее писать универсальные алгоритмы и конструкции вроде декораторов, всяческих ORM и подобных вещей. Это сильно упрощает интерфейсы библиотек, что в совокупности ведёт к более простому коду и к плавной кривой обучения новичков.
Из моей практики (5 лет писал на C++, потом столько же на Python, эти сроки немного пересекались) могу сказать, что с точки зрения ошибок типизации (а собственно их и ставят в недостаток динамически типизированным языкам) разница минимальна — они очень редки и все отлавливаются автоматическими тестами. Конечно, если у вас руки откуда надо растут, а если не откуда надо, то эти ошибки будут далеко не самой большой проблемой. Поэтому в области разработки софта, не требовательного к производительности, такие языки рулят.
Преимущество же статически типизированных языков в том, что, налагая дополнительные ограничения на программиста и на гибкость кода, они позволяют компилятору получить дополнительную информацию о программе и сделать более специфичные операции. Однако нынче почти везде используют JIT и это частично нивелирует разницу в производительности.
В итоге мы получаем классическую дилемму: повышение уровня абстракции против повышения уровня специализации. У каждого пути есть свои плюсы и минусы.
И ещё от туда же:
Тип переменных (примитивов) можно и указать, это не сложно. Гораздо сложнее указывать тип объектов. Не всегда известно, с каким объектом придётся работать. В питоне мне достаточно знать, что у объекта имеется некоторый метод или поле (а можно и того не знать). В статическом языке от меня сам компилятор бы не отстал до тех пор, пока я бы не указал тип объекта. Для этого мне пришлось бы следить, чтобы все объекты наследовались от одного базового класса (что не всегда возможно), или мне пришлось бы выделять интерфейс, имплементируя у всех возможных объектов данный интерфейс. Появлялись бы ситуации, где мне пришлось бы выдумывать, как бы сделать так, чтобы явно не указывать тип, но и чтобы компилятор пропустил.
Ну и где мои коллеги на С# пишут всякие dependency injection, visitor, перегружают методы, наворачивают какие-то абстракции, вводят интерфейсы, я со своим питоном просто проверяю, есть ли свойство через hasattr(), или узнаю тип через type(). Так что, сила питона в интроспекции и рефлексии.
Статическая типизация не панацея. Например, статическая типизация в Java не помогает отловить NullPointerException.
Потому что в жабе достаточно слабая типизация. В любом современном языке у вас есть Maybe T
/Option<T>
/..., где вы явно говорите, что тут может быть нулл, и вы не забудете его проверить, и не будете проверять там, где не надо. Так что компилятор именно что помогает отловить нулл. Более того, в C# 8.0 это ключевая фича новой версии языка.
since type errors are a small fraction of all the things that might go wrong in a program
Практически любую ошибку можно выразить на уровне типов. Это не всегда полезно, и можно упороться тем, чтобы сделать классы IntOne, IntTwo и т.п., описывая все их свойства (такие завтипы для бедных), но в моей практике скорее все наоборот: типы позволяют найти почти все ошибки, кроме совсем дурацких, вроде написали 1 вместо 10. Которые легко чинятся, потому что совершенно попиюще кричат из кода "Я НЕВЕРНОЕ ЗНАЧЕНИЕ" :)
По-поводу первого комментария: человек пришел из С++ в питон и там все было зашибись. Окей, только типы никакого отношения к JIT и дополнительным оптимизациям не имеют отношения, типы это про статический анализ на предмет ошибок. Типы могут эффективно полность заменить все тесты в программе, при должном старании. Хотя выгоднее оставить 10% тестов, чтобы упростить типы, но все равно это позволяет в 10 раз сократить их количество, с одновременным увеличением качества и упрощением дальнейших доработок.
Посмотрите на ORM в каких-нибудь шарпах. Как по мне, во-первых сильно проще их писать (потому что ОРМ сама по себе сложная штука, а все сложные штуки, как я уже выше писал, проще писать с поддержкой от компилятора), во-вторых их проще использовать, потому что компилятор помогает понять, что можно делатЬ, что нет, и т.п.
Попробуйте на любом динамическом языке попробовать разобраться в любой неизвестной библиотеке с отключенным интернетом, сразу станет понятно, о чем я говорю.
Тип переменных (примитивов) можно и указать, это не сложно. Гораздо сложнее указывать тип объектов. Не всегда известно, с каким объектом придётся работать. В питоне мне достаточно знать, что у объекта имеется некоторый метод или поле (а можно и того не знать).
Интерфейсы и экзистенциальные типы в помощь.
Зато в динамике легко забыть, какой именно набор полей может быть у сущности, и начать передавать еще один, но не во всех местах, а потом радоваться взлетающим undefined is not a function
.
Для этого мне пришлось бы следить, чтобы все объекты наследовались от одного базового класса (что не всегда возможно), или мне пришлось бы выделять интерфейс, имплементируя у всех возможных объектов данный интерфейс.
Надо имплементировать только тот интерфейс, которым пользуется вызывающий код. То есть тот, которым вы и так неявно пользуетесь, когда надеетесь, что у объекта есть метод .foo()
. Только в другом случае вы не надеетесь, а точно знаете, что он будет.
Ну и где мои коллеги на С# пишут всякие dependency injection, visitor, перегружают методы, наворачивают какие-то абстракции, вводят интерфейсы, я со своим питоном просто проверяю, есть ли свойство через hasattr(), или узнаю тип через type(). Так что, сила питона в интроспекции и рефлексии.
В C# тоже так можно, только так не делают. И не потому, что медленно, а потому что ненадежно. Не говоря про то, что визитор ортогонален hasattr'у.
Статическая типизация не панацея.
Никто не говорит, что это панацея. Но целый пласт ошибок убирает.
как часто, по вашему, у программистов на языках с сильной динамической типизацией возникает несогласование типов (none/null и т.п. не в счёт)?
При поддержке крупных кодовых баз, рефакторинге — это очень частое явление. Причем, изменчивость софта на динамических языках при этом играет не в те ворота.
Разница в том, что со статической типизацией большинство ошибок отлавливается на этапе компиляции, а с динамической — в рантайме, причем лишь тогда, когда кусок плохого кода сработает.
Да люди не знают. что в нормальном проекте даже 500 файлов перелопатить (например, у меня такое было с миграции с монго драйвера версии 1.0 на 2.0) не вызывает проблем в статическом языке, а в динамике обычно дернуться боишьсЯ, чтобы где-то не взорвалось, файлики меняются аккуратно, по одному, и желательно с фоллбеком. Поменять треть проекта так, чтобы сохранить работоспособность, это для людей совершенно непонятно :)
В этом случае статическая типизация вам только ошибки компиляции покажет
Это и есть баги типизации, которые без этого этапа останутся незамеченными и начнут всплывать гораздо позже (+ к временным затратам на стабилизацию), а то еще и «дрейфовать» в случайные стороны, если типизация будет не только динамической, но и слабой.
Это и погрепать можно
Нельзя. Греп не видит контекста. Да и вы это в 2к18 руками будете делать?!
Но, хорошее тестовое покрытие считаю более важным, чем наличие/отсутствие типизации.
Более/не более — это софизм. Зачем теплое с мягким сравнивать? Если будет и то, и другое — это только лучше.
а одних тестов — достаточно.
Ага, есть время покрывать тестами, но нет писать типы?) Не укладывается это в парадигму динамичной разработки в условиях изменяющихся требований, которая и дает смысл скриптовым языкам.
Не буду спорить, до определенного размера кодовой базы это имеет смысл. Но вокруг нас приложения все больше, приходит время что-то менять в подходах.
Статическая типизация — это тесты, которые уже написаны за вас и запускаются при каждом изменении. Это взаимодополняющая штука с юнит-тестами.
Смысл скриптовым языкам даёт прежде всего отсутствие этапа компиляции. Скриптовый язык может иметь статическую строгую типизацию и, наверноё, смысла в нём будет больше чем в любом другом. Особенно, если в нём будет JIT.
Смысл скриптовым языкам даёт прежде всего отсутствие этапа компиляции.Но ведь он там всё равно есть! Пропустите где-нибудь в питоне двоеточие (в функции, которая не используется) и увидите, как он прекрасно откажется запускать ваш скрипт.
Просто пользы от этого этапа у них куда меньше, чем у языков со статической типизацией…
Особенно, если в нём будет JIT.А JIT тут причём?
С JIT в теории скриптовые языки должны показывать то же быстродействие, что и компилируемые
А вот люди, неготовые работать с языками, в которых для каждого случая припасён новый, свежий, крупнокалиберный ногострел — на Go переходят с удовольствием.
На каких именно задачах и за счёт чего конкретно?
JIT дает задержку при, собственно, компиляции (и этого никто не скрывает). А в скомпилированном виде за счет чего проигрыш? Нет поддержки каких-то SSE или что?
Обычно в тестах любят включить оптимизации для плюсов и написать «в лоб» что-нибудь на C#. Причем, взяв задачку, где GC должен сдохнуть. И закончить сравнение «вот так пила сказала хр-р-р, как и предполагали суровые сибирские мужики».
А вот с тестами без издевательств над JIT/GC как-то сложнее (мне таких не попадалось).
Но наличие GC компилирумости языков то ортоганально.
Почему оно так получается, я думаю это задачка не из простых. На ум приходят всякие мысли о райнтайм проверках, GC, который в фоне постоянно работает параллельно, всяческие другие вставки JIT компилятора, которые делают пользовательский код более контролируемым. Ведь тому же GC надо как-то останавливать все потоки безопасно, нужные safe point'ы. При любом изменении ссылки нужно вставить write barrier, чтобы GC мог свои структуры обновить. И т.д. и т.п.
Ну и всегда остается тот факт, что JIT может быть просто хуже год выдает. Ведь ему надо работать быстро, он не может себе позволить тратить столько времени, сколько может какой-нить clang.
Мне показалось у вас спор «С JIT в теории скриптовые языки должны показывать то же быстродействие, что и компилируемые», а вы говорите «GC дает оверхед».
Да, дает, но это же разные тезисы. Можно, например, заняться ручным выделением памяти и выкинуть GC за скобки (что тот же Computer Language Benchmarks Game запрещает прямым текстом).
Да и вообще, GC — это про создание миллионов объектов, а не про вычислительные задачи. И относительный оверхед на компиляцию падает со сложностью задачи.
Так что ставить крест на JIT в вычислительных задачах потому, что в популярных языках принято полагаться на GC — такое себе. Компилируемые между собой тоже заметно по скорости отличаются, даст ли на фоне этого именно JIT какое-то значимое отличие — вопрос.
Еще можно взглянуть конкретно на n-body бенчмарк в рамках java и C++. Код практически идентичный, но разница в скорости огромная:
1. GC вроде мешать сильно не должен, выделений памяти нет.
2. Сразу бросается в глаза наличие методов. У С++ это будет обычный джамп, а для Java еще один indirection уровень из-за vtable. На 50000000 итерациях это может вполне сказаться.
3. Тоже самое с полями. У Java это ссылки на объекты. У С++ это std::array, что тоже исключается уровень indirection
4. Ну и самое явное это явное использование векторных инструкций. Догадалась ли Java их использовать большой вопрос.
В общем, наверняка можно попробовать сделать такой пример, который полностью исключит и GC, и специфику управляемых языков, где добавляются дополнительные уровни indirection, чтобы все зависело только от компиляторов. Но на сколько-нибудь реальной задаче это все равно представляет лишь академический интерес.
А в питоне не компилятор заругается, насколько я знаю, а синтаксический анализатор.Синтаксический анализатор, который читает текстовый файл и превращает его в бинарный байткод? Который потом даже можно сложить в файл?
Извините, но это компилятор. И очень многие скриптовые языки так устроены: emacs lisp, perl, python и т.д.
Но с тем же успехом можно научить Linux использовать Go как скриптовый язык. Тут нет никакой магии и никакой разницы: скриптовые языки отличаются от не скриптовых тем, что у них есть «однопроходный запускач», которым пользуются люди. Всё.
С JIT в теории скриптовые языки должны показывать то же быстродействие, что и компилируемыеЭто дурацкая теория, которая не подтверждается практикой. И никакого отношения к скриптовым языкам не имеет.
.pyc
файле отличатеся от «компилятора Java», порождающего байткод в .class
файлах?Только тем, что питон вызывает свой компилятор (а это — полноценный компилятор, без каких-либо оговорок) неясно, а JavaC обычно (но не всегда) вызывается явно.
Но он может и неявно вызываться «в фоновом режиме». JSP, к примеру.
Статическая типизация ценна не из-за «ошибок», а совсем по другим причинам.
почему typescript был вообще создан это возможность комфортно рефакторить большие проектыВот, это и есть одна из основных причин.
Но рефакторить вам помогает «тулинг»/IDE и «тайп-хинтинг». Является ли «тайп-хинтинг» статической типизацией? Явяется ли TypeScript статический типизированныйм? На мой взгляд он примерно такой же «типизированный» как и Python.
А вот другой комментарий с близким количеством плюсов.
Если только начинаете изучать разработку, начинайте со статической типизации
И тогда, как и автор, через несколько лет сможете про себя сказать —
Сейчас я так крепко прибит к своему мышлению, что не вижу и не хочу видеть плюсов динамической типизации. Они есть, но вне поля моего зрения, и все что мне остается — закрывать на них глаза
Пишу на JS давно и много, по опыту моему и моих коллег, собственно на ошибки типов приходится мизер от всех ошибок, которые бывают на фронтенде.
Причина — в психике и, вероятно, нейрофизиологии.
Если послушать аргументы сторонников строгой типизации, в них звучит одна эмоция — страх. Страх допустить ошибку. Страх, что код станет неуправляемым — со временем или при использовании другими людьми. У автора:
«Компилятор меня проверил, я все сделал правильно», «IDE… проверяет на ошибки». «JS-ник, берет этот мой тип… и начинает использовать его неправильно»
Тем, кто в разработке давно, очевидно — компилятор не спасает от ошибок, которые встречаются в реальном мире, и не делает код делает код лучше структурированным.
Но страх — очень сильная эмоция. Кто-то с ним (и заодно со сложностью) справляется лучше, а у кого-то мозг просто устроен по-другому. И в дело вступает любимая психологическая защита людей с высоким интеллектом — рационализация.
Если послушать аргументы сторонников строгой типизации, в них звучит одна эмоция — страх. Страх допустить ошибку. Страх, что код станет неуправляемым — со временем или при использовании другими людьми.
Скорее нежелание. Да, я не хочу допустить ошибку. Я хочу, чтобы код был управляемым при использовании другими людьми. Я хочу, чтобы если у них нет интернета, они все равно могли понять, Какие данные в какие функции они могут передать, и на что могут рассчитывать в ответ. Это плохо?
Тем, кто в разработке давно, очевидно — компилятор не спасает от ошибок, которые встречаются в реальном мире, и не делает код делает код лучше структурированным.
Компилятор, очевидно, спасает от ошибок, которые встречаются в реальном мире, и делает код лучше структурированным.
Если послушать аргументы сторонников строгой типизации, в них звучит одна эмоция — страх. Страх допустить ошибку.
Этот демагогический прием называется "чтение в сердцах". Если вы откроете поисковик и напишете "why static typing" вы легко найдете другие доводы за. И почему это может быть не эмоция, а расчет?
«Компилятор меня проверил, я все сделал правильно»
А так же "IDE мне подсказывает" — уже не только про ошибки.
Тем, кто в разработке давно, очевидно — компилятор не спасает от ошибок, которые встречаются в реальном мире, и не делает код делает код лучше структурированным.
"Аргументация демагога начинается с выражений типа «каждому известно, что…», «очевидно…», «наука давно доказала…», «все успешные бизнесмены знают, что…» и так далее. При этом эксплуатируется ложная гордость слушателя (если что-то подаётся как «общеизвестное», то ему бывает сложно признаться даже себе, что он об этом никогда не слышал) либо приводятся мнимые авторитеты, согласные с высказыванием, что действует на людей, склонных доверять авторитетам."
Когда вы говорите "не спасает от ошибок" вы имеете ввиду не спасает от всех ошибок или нет вообще никаких ошибок, от которых он спасает?
И в дело вступает любимая психологическая защита
И опять "чтение в сердцах"
Если вы откроете поисковик и напишете «why static typing» вы легко найдете другие доводы за.Именно так работает когнитивное искажение предвзятость подтверждения.
Когда вы говорите «не спасает от ошибок» вы имеете ввиду не спасает от всех ошибок или нет вообще никаких ошибок, от которых он спасает?В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами.
— компилятор не спасает от всех ошибок или нет вообще никаких ошибок, от которых он спасает?
— В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами.
Простите, но это не похоже на ответ на поставленный вопрос, а скорее псевдофилософское словоблудие.
компилятор не спасает от ошибок, которые встречаются в реальном мире
В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами
Привести примеры взаимодействия?
Отвалившееся соединение с базой данных. Ответ от API с некорректными данными в неожиданном формате. Пользователь сделал что-то не то.
Надеюсь, так понятней.
Если вопрос был в том, спасает ли компилятор вообще от каких-то ошибок, то… Риторические вопросы ж не требуют ответа?
Именно так работает когнитивное искажение предвзятость подтверждения.
Я процитировал ту фразу на которую отвечал. Она относится не к самой статической типизации, а к тому, что именно говорят ее сторонники. Вы не полностью выразили их доводы вот и все.
В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами.
Тогда у меня большинство ошибок происходит не в "реальном мире", а в коде, который неправильно взаимодействует с другим кодом (библиотек, чужих и собственных, операционной системы и т.д.).
Типы если и несут какие-то знания, которыми программист не обладает, то лишь случайно. А вероятность того, что программист решил поставить где-то any чтобы не выдумывать сложный граф типов, я оцениваю, субъективно, куда выше, чем вероятность того, что ему будет лень написать три строчки теста.
а на вопрос «как типы помогают не допустить лишних ошибок в логике».
Если у меня UnityType { Pawn, King,… etc } это ТипФигуры в шахматах (или другой игре не суть) и я передаю его в нужный метод, как аргумент или возвращаю, то я уверен что тут ожидается оно, и есть смысл в этом. А если я обозначу int (или вобще без типа не суть) я буду лишнее время тратить чтобы найти где оно передается, что туда передается, и почему, и как обрабатывать, если я пишу новое, или ВАЖНЕЙ — РЕДАКТИРУЮ, я ЧИТАЮ код, и вижу ограничения. Они придуманы как раз чтобы в голове держать ТипыФигуры, а не «незнамо что». А когда enum таких становится десятки, то перепутать чтото компилятор уже поможет. И даже без компилятора, find usages.
Тоже самое касается классов, в которых есть семантика, я знаю что ожидается класс который будет делать тото, да хоть делегат / интерфейс с функцией, у которой строгое число и смысл аргументов, а не function (a, b) с неясным смыслом аргументов. Это даже к вопросу косвенному недавно вон написали «уже не нужны паттерны, если можно передать указатель на функцию» — да можно было еще с Си передать указатели, смысл в семантике. И чтении и поиске при разработке.
А НЕ write only Код потом write only Тесты.
**
«статическая типизация это 10%, а тесты это 90%.»
по этому — т.е вы предлагаете написать огромное кол-во тестов, которые надо будет тысячу раз перечитывать (а разработка требует такого, что функционал меняется часто, и во многих местах, я уже не помню как оно работает), вместо нормального использования типов? Вот мне тесты читать не надо чтоб понять что тут ожидается UnitType или MaterialType и т.п., и как оно себя поведет и какие в нем ограничения.
Может даже тестов еще и нет, их над написать. а функционал в разработке, и меняется, толку тесты писать еще не было, не дошло, бывает? Часто. Тут только типы и помогут. Вот потом вы тесты и напишете. Или вобще выкините функционал, т.к. над по-другому реализовать.
И да у меня даже страха нет, когда тестов нет, или вообще нет, или еще нет, не суть, а вот типы уже есть, и уже видны семантические ограничения.
Это большое заблуждение, что если пишут на динамическом языке, то проверяют типы в тестах. У нас тысячи тестов и ни один не проверяет тип.
Так это ж еще хуже.
а как проверить, что оно работает? Я имею ввиду бизнес логику?
И как же с тестами вы проверяете? Сначала надо спроектировать «как оно должно работать» — это вопрос подумать головой, и в этом как раз статическая типизация поможет, потому что явно видно что куда передается, где find usages, где-то срефакторить где было не-красиво и багоопасно из-за запутанных конструкций и только что введенного нового функционала.
Ну и как вы сможете тесты написать на код которого нет — тот еще вопрос… особенно если это очень исследовательская правка, которую можно реализовать кучей способов, а вы пишете сразу тест, который фиксирует API и возможно реализацию связанную с ним, иначе вы не сможете определить даже типы параметров и что с чем взаимодействует, вы всеравно сначала спроектируете, хоть что-то типизированное, в духе «есть объект Factory у него параметр UnitType и он выдает Unit с заданным UnitType» это уже интерфейс и вон сколько в нем типов, и это прсотейший пример, как вы тесты напишете не спроектировав — не представляю. Тем более не представляю если у вас типов нет в синтаксисе, как вы будете писать тесты как доменную область, не зная что в этой доменной области (бизнес-логике) за объекты. И как вы будете рефакторить, если у вас доменные объекты не ищутся по find usages. А если ищутся — так это уже нормальная статическая типизация.
А да — если воппрос только «как проверить когда спроектировали» — я не говорил ни разу что проверять не надо. Я говорю только о том что в языке со статической типизацией, явно определнные типы — помогут вам спроектировать. И помогут написать тесты (которые — не обязаны быть написаны до пректирования).
Это большое заблуждение, что если пишут на динамическом языке, то проверяют типы в тестах. У нас тысячи тестов и ни один не проверяет тип.
И где же в моем утверждении — хоть намек на проверку типу в тестах?
Вот цитата от моего же сообщения
Вот мне тесты читать не надо чтоб понять что тут ожидается UnitType или MaterialType и т.п., и как оно себя поведет и какие в нем ограничения.
Я сообщаю о том что у меня есть UnitType и MaterialType — это типы, они определны явно, да пусть и enum в простом случае (вместо констант 1-2-3 в разных множествах что еще перепутать можно, но я не говорю однозначно что вы используете константы, мало ли способов). И если они явно опеределены как enum, то я могу посмотреть на код и без тестов увидеть что эти аргументы являются «типом юнита» а в случае int в кривой реализации без типов или «нетипизированного unitType аргумента в function getA(unitType) в JS» что тоже самое (! абсолютно) я не могу быть уверен что передается UnitType и что он может быть только UnitType.Pawn или UnitType.King но не 0 и не строка и не фиг-знает-что-еще. Ну а в случае JS с не явной типизацией которая в аргументе не читается, еще и тесты надо писать, но как вы выразились — вы их даже и не пишете (я про проверку типов), ну значит еще больше не можете быть уверены что ваш код работает, если только вы не провели тесты на абсолютно все комбинации (декартово множество — ререал)
У меня такого не бывает. Сначала тесты, потом код. Ну запроектировали вы типы, все у вас стыкуется, а как проверить, что оно работает? Я имею ввиду бизнес логику?
Если компилируется, то есть очень неплохой шанс, что в логике проблем нет. Точнее он прямо пропорционален тому, насколько полно вы используете типы. В пределе скомпилировалось == вся бизнес-логика корректна.
2. TDD позволяет ровно то же
3. Ведёт к тому же
4. Аналогично в какой-то мере.
Иной раз залезаешь в биндинги TS и видишь на поэкрана только описание тип аодного аргумента какой-нить стандартной функции.
Но ведь это точно не хуже, чем если бы его не было. Т.к. вы можете просто, например, не смотреть. А вот я зачастую пропускаю документацию в комментах и вместо этого сразу смотрю на тип.
Ведёт к тому же
Наоборот. Часто требование простоты и удобства тестирования строго противоречит качеству архитектуры.
Можно пример? простота и удобство тестирования подразумевают низкую связанность, чтобы не инициализировать ненужные в кейсе применения логически, но имеющиеся технически зависимости, и высокую связность, чтобы не инициализировать по отдельности сущности, которые логически друг от друга не отделеяются, но технически разделены. Как это строго противоречит качеству архитектуры?
В некоторых ситуация хуже. Когда такие типы заменяют документацию в комментах или полноценную.
Так они не заменяют. Если проект без документации но с типами, то без типов он был бы и без документации и без типов.
Можно пример?
Ну вот вы сами снизу сказали про низкое зацепление и высокую связность. Зачастую для достижения этих характеристик приходится жертвовать другими составляющими качественной архитектуры, что в итоге перевешивает все плюсы.
Если жертвовать этими составляющими качественной архитектуры в угоду чему-то другому, то всё равно качественную не получим. Не в тестах тут дело будет.
Заменяют. Даже по истории коммитов можно отследить момент замены.
А примеры можно?
Если жертвовать этими составляющими качественной архитектуры в угоду чему-то другому, то всё равно качественную не получим.
Конечно, получим. Качественная архитектура — это баланс. Для нее не нужно низкое зацепление — достаточно чтобы зацепление не было слишком высоким и т.д.
Условно говоря, качество архитектуры — выпуклая функция от набора переменных, и максимизируя одну или две переменные из набора — мы не получаем максимума.
Примерно так:
const two = new VeryTestableNumber( 2 , new VeryTestableNumberTextSerializer , new VeryTestableNumbeJSONSerializer )
const algebra = new VeryTestableAlgebra( VeryTestableNumber , new VeryTestableMultiplicator( VeryTestableNumber ) , new VeryTestableDivider( VeryTestableNumber ) , new VeryTestableSummator( VeryTestableNumber ) , new VeryTestableSubtractor( VeryTestableNumber ) )
const calc = new VeryTestableCalculation( ()=> algebra.multiply( four , four ) , new VeryTestableErrorStrategy( new VeryTestableLogger ) )
const four = calc.execute().result
И это я ещё упростил..
1. Очень-очень спорно. Иной раз залезаешь в биндинги TS и видишь на поэкрана только описание тип аодного аргумента какой-нить стандартной функции.
Думаю, что это для документации, которая отображается при альт-ентер в IDE
«Компилируется — значит работает правильно» звучит в 2018 году смешно
Как по мне, все наоборот. Сейчас именно то время, когда я все больше замечаю — скомпилировалось и работает нормально с первого раза. Это раньше нужно было дебаггером десять раз пройтись, чтобы выловить какое-то хитрое непонятное поведение.
Требования всегда не полные, в конце проекта всегда нужно внести изменения, 3rd party api за день до нашего релиза выпускают новые версии несовместимые с тем на чем мы разрабатывали, ни и вы сами все про это знаете.
Так вы как раз описали ситуацию, когда Type Driven Developmen раскрывается во всей красе. Когда требования меняются, надо исправлять, рефакторить, фиксить под новый апи и т.д.
Вот когда вы раз написали — и все, тогда, да, можно и динамикой обойтись.
Очень неплохой шанс отхватит NPE, ошибку ±1, неправильное направление сравнения, ошибку в логичеком условии и т.д.
Типы никак с этим не помогают.
Очень даже помогают
Но конечно от C# разработчика слышать, что «если билдится значит работает» очень странно.
Я не C# разработчик, я просто программист :) На текущем проекте у меня питон/C#/Solidity/Rust в пропорциях примерно 0.1/0.4/0.4/0.1, а в любом другом может быть любая другая комбинация. Учитывая, что язык хуже солидити придумать трудно, я без труда пишу на практически любом распространенном.
В C# никаких подпорок под это нету (я знаю что в 8ой версии появились not null типы, но это все-таки будущее, а не настоящее).
C# не вершина эволюции. Он примерно в середине множества типов. Не такой классный, как rust/haskell, но сильно мощнее динамики. Да блин, даже скриптовать в linqpad сильно удобнее, чем в webstorm под ноду.
Моя философия проста, я пишу тест, чтобы запускать кусок функциональности который я разрабатываю в изоляции, потом можно укрупнить тесты для проверки интеграции с другими частями, затем укрупняем до end to end теста. И деплоим. Мне это позволяет даже приложения не запускать, т.е. если мои тесты прошли я уверен, что все ок.
Когда люди поймут, что тесты не заменяют типы? Они могут их в лучшем случае дополнить. Вы никак не можете проверить ВСЕ входные данные. Как-то с этим бороться пытаются тулзы вроде QuickCheck, но рандомные входные данные сильно хуже, чем математически доказанная корректность для любых ситуаций.
Мне это позволяет даже приложения не запускать, т.е. если мои тесты прошли я уверен, что все ок.
Если мои тесты прошли, я уверен только в том, что в ожидаемых случаях поведение совпадает со спекой :) И то если покрытие достаточно хорошее.
Это нормально, что такой подход не всем нравится, и даже возможно я не прав что деалю именно так. Но иного варианта разработки я пока не вижу.
Ну вот например, как можно типами избавиться от тестов в стиле «бросить эксепшн если попытались выйти из программы во время обработки сообщения».
Типы никак с этим не помогают.
Типы помогают в фиксации тех инвариантов, которые вы хотите зафиксировать.
int sum(int a, int b) { return a-b; }
Скомпилировалось, но делает не то что нужно. Как вам типы тут помогут?Благо, для выражения семантики суммирования есть специальное синтаксическое средство — оператор сложения ("+"). При его использовании компилятор обязательно проверит, что эта операция определена для типов аргументов.
Благо, для выражения семантики суммирования есть специальное синтаксическое средство — оператор сложения ("+")Ну так и «минус» там тоже есть, в итоге ошибка не куда не исчезает.
Вы формализовали свое требование только названием функции. Компилятору невдомёк, что ожидалось что-то другое.Приведите пример как должно быть, чтобы компиллятору было «вдомёк».
и в типах, скажем, написано `∀x sub(x, x) = 0`Это же просто тест, вы его прямо с кодом смешиваете? И при чем тут типы? точно так же можно тестировать и в динамической типизации.
Это не страх, это Уверенность. Когда у вас статическая типизация вы просто увереныХорошо подметили. Похоже, сторонникам статической типизации действительно для уверенности требуется статическая типизация.
Плюс получаем поддержку IDE для рефакторинга.Рефакторинг в IDE есть и для динамических языков. И даже без IDE есть
В современных языках уже понятно, что это оверкилл, но опционально можно писать и их.
БТВ рефакторингов в лиспе никаких не было, потому что первой IDE был борландовский паскаль.
Википедия: «рефакторинг означает изменение исходного кода программы без изменения его внешнего поведения». При чем тут IDE?
Википедия: «Средства автоматизации рефакторинга. Технические критерии для инструментов рефакторинга: деревья синтаксического разбора».
Обьяснить, почему Лисп, или этого достаточно?
Особенность Лиспа (и некоторых других языков) в том, что в нем структура AST полностью идентична структуре кода и строится из примитивных типов языка, что позволяет анализировать и модифицировать ее средствами самого языка. Звучит наверное сложновато, но тут есть наглядные примеры
первой IDE был борландовский паскальИ опять вы заблуждаетесь
Но он почему-то не вызывает желания поделиться этой радостью в комментариях.
То, что я видел, хотелось «обнять и плакать»
www.infoq.com/presentations/Simple-Made-Easy
www.youtube.com/watch?v=rI8tNMsozo0
gist.github.com/oakes/c82cd08821ce444be6bf
До сих пор интересует вопрос, какому гению пришла в голову идея в качестве базы для QML выбрать JS? И это во фреймворке на C++, где типизацией пронизано буквально всё, а в QML её особо нет, даже если захочется. Порог вхождения во фреймворк по этому получился просто астрономическим. Нужно не только уметь мыслить «по ООП-шному», но еще и по JS-ному. При чем и то и другое нужно уметь делать одинаково хорошо…
Огромная часть frontend'а — это landing pages и небольшие сайты, например, портфолио или промо. Можно, конечно, спорить frontend-программист ли вообще человек, встраивающий slider-widget на сайт, но так называются вакансии и позиции, на которых они работают. Годами. Суть же в том, что для выполнения этой работы, которой много, типизация просто-напросто избыточна, читай не окупает себя.
Как только задача перестает быть тривиальной, она начинает требовать декомпозиции. И как следствие для ее решения выстраивается архитектура и подбираются паттерны. Да, трудно спорить о том, что ООП может поспособствовать развитию системного мышления, это то, что Вы описывали: системные объекты и их взаимодействия, но это вовсе не обязательно. Более того, не всегда опыт и владение языком со статической типизацией ведут к тому, что у человека появляются способности к системному мышлению и проектированию. Ровно как и то, что человек пишет, скажем, на JavaScript, не лишает его этих способностей.
Кстати, качественное тестирование помогает решить не проблему TypeError, а logical errors.
В любом случае, судя по Вами же описанному случаю, у Вас проблема заключалась не в разных языковых предпочтениях, а в том, что Ваш друг и коллега отказывался от этапа проектирования. Совсем ли он отказывался или настаивал на поэтапном проектировании, призывая выбрать agile-подход, не понятно, а может быть он вообще хотел сначала сделать прототип (как частенько бывает во frontend), но суть вашей проблемы именно в этом.
А к написанию этой статьи Вас подвигло то, что вы распространили отдельно взятый частный случай, когда, пишущий на динамически типизированном языке, отказался от проектирования, на всех программистов использующих такие языки.
Не все такие.
staticTypes !== projectDisign && dynamicTypes !== noProjectDisign
Вы знаете, кажется, что Вы раздули из мухи слона. К сожалению, проблемы проектирования и взаимопонимания (а может и лидерства, кто вас знает, может подоплекой разногласий было выяснение того, кто из вас у руля и принимает окончательное решение) решаются не через изучение статически типизированных языков, а только через улучшение владением естественными языками, те самые пресловутые soft skills.
Но это не все типизация в языке может вообще отсутствовать.
<Статическая хороша для больших проектов, которые годами делают сотни разрабов, а динамическая — для маленьких команд, для задач, где часто нужен write-only код. С динамической ты экономишь время и силы в начале разработки, а со статической — в конце.>
Сомнительное утверждение. Связь не просматривается.
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю. Увидев любую задачу, я пытаюсь представить ее решение как набор типов и принципов их взаимодействия. Когда я разрабатываю модуль, первым делом определяю, какими типами он оперирует и какими взаимодействует со своим окружением. Я даже не помню, как я подходил к решению задач раньше. >
В этом вся соль. Проектирование приложения отталкиваясь от данных. Абсолютно с вами согласен в этом. Только типизация тут вообще не причем. Данные могут быть спроектированы любыми способами способами, хоть в комментариях. Типы только один из способов их спроектировать, и возможно не самый лучший. То что вы к этому пришли через типы, ну прекрасно. Утверждать что проектирование данных вытекает только из статической типизации я бы не стал. Это все таки разные категории.
Но проблема бывает когда не только типы не используются, и комментариев и других ограничителей тоже нет, и попробуйте догадаться что там за единица измерения (называть всегда millis суффикс если это милисекунды — не факт что удобно, а еще бывает и сложней)
А еще лучше вообще забудьте о типизации как о дурном сне.
Но как раз в том же JS (без keyword class из нового стандарта и без TypeScript) там черт ногу сломит даже с пользовательскими типами
Особенно если сравнивающие под «статической типизацией» имеют в виду «строгая статическая типизация с гибкой и мощной системой типов», а не «слабая статическая с примитивной системой типов». А под динамической «слабая динамическая типизация с примитивной системой типов», а не «строгая динамическая типизация с гибкой и мощной системой типов».
Если сравнивать динамическую со статической, то сравнивать надо эквивалентные во всё остальном. А то я могу сравнить C с PHP и показать, что статическая типизация отстой, ничего не гарантирующая в плане типов.
Сторонники статической типизации возлагают на нее надежды по борьбе с багами. Проблема багов значительно шире и эффект от применения статической типизации явно преувеличен
Я как сторонник возлагаю надежды, и даже сообщаю свой опыт, и я писал и на JS и на PHP (уж не говорю это «правильная» там динамика или нет), и вижу что проще не допускать баги в Java или C# и даже в C++, с его управлением памятью, и даже в Си с его старой древней библиотекой, я на всем этом попробовал писать проекты с использованием нормальных доменных моделей, пусть и не из миллиона строк, но опыт у меня есть.
Для борьбы с ней наиболее эффективно лишить программиста инструментов управления памятью. Управлять памятью должен полностью компилятор. Динамические языки ближе к решению данной проблемы и поэтому более эффективны.
Зависит от проекта. Для подавляющего большинства простых проектов, и для большинства средних/сложных — наверное да, управление памятью в духе сборки мусора — вне ведома программиста, это хорошее решение, в сложных проектах где к тому же нужна оптимизация — лучше позволять умные ссылки (как в С++) и какие-то новые разработанные языки в духе Rust/go и что-там-еще. Но управление памятью вводит баги толькоо пределенных классов вроде «утечка (не удаление)» или «двойное удаление (краш)» или «переполнение массива/буфера (уязвимость без проверки лимитов)», но они и направлены чтобы определенные вещи оптимизировать (те же аллокации не делать), так что не все баги они вводят, я бы сказал что доменные баги в духе «в эту функцию(метод класса) передал аргумент и забыл инвариант, а аргумент оказался null (простой случай) или невалидный (меньше 0)» и тут динамика никак не поможет а навредит т.к. инварианты видны хуже (и типы, и без комментариев особенно если, а полагаться на «да итак сойдет» как многие пишут на динамике, фиг разберешь что творится в коде, без тестов (всмысле без тестов не разбрешь, но они могут быть), которые еще и читать замучаешься тоже, когда надо в доменной области разобраться)
Ваш подход позволяет реализовать более продуманную в техническом плане задачу, но требует держать в голове весь проект сразу. Подход вашего друга позволяет десятку джуниоров работать над проектом, вообще не представляя, зачем это все пишется. Как результат — получается, что функция на верхнем уровне выполняется год с небольшим и теряет память, потому что один из джуниоров написал рекурсивный вызов своей функции, которая использует функция другого джуниора с временем выполнения O(n^3)/
То есть пишем функцию «MakeBest()», в ней пишем вызов «MakeGood()», и только потом создаем эту самую MakeGood(), прописывая что именно она должна сделать.
В современных IDE так можно вполне себе и со статической типизацией. Они умеют создавать методы по примеру использования.
![image](https://habrastorage.org/getpro/habr/comment_images/161/0af/3b6/1610af3b6c6dac1aacfc613c8d5119ae.jpg)
«Приверженцы» таки никогда не поймут друг друга и топик явно тянет на холиварный.
Понять оппонента не сложно. Сложно убедить его, что он не прав, или убедить себя, что ты не прав.
Замечу еще, что строгая типизация появилась задолго до ООП, а у автора эи понятия смешиваются. Строгая типизация появилась еще в Фортране.Язык для ООП появился на десять лет позже — Симула-67. И мейнстримом не стал т.к. считалось что объекты нужны только для имитационного моделирования. Еще 20 лет потребовалось, чтобы осознать, что ООП — универсальный инструмент борьбы со сложностью.
Уровень языка позволяет большому количеству производителей и потребителей инструментов договориться об описании типов, наверное.
в статических все больше вывода типов — уже не надо их декларировать
Вывод типов не освобождает от необходимости эти типы определять.
Рантайм — это слишком поздно, чтобы ловить ошибки, это не надежно, это оверхед. Чем больше гарантий можно вынести на этап компиляции, тем лучше.
Слушайте, ну хватит уже про вашу "фазу статанализа". Ну не работает статанализ на чисто динамических языках. Вот прямо сейчас у меня на поддержке проект на реакте, там ни флоу ни тс нету, и это адовый ад. Половина ф-й ИДЕ — просто не работают, протоколы обмена данными все время приходится уточнять в сторонних местах, банальный рефакторинг с добавлением параметра в ф-ю превращается в эпопею и грепы по проекту, вещи с внедрением зависимостей, HOC и прочее — убивают типы сразу и на входе идет гордое any, и чтобы с этим работать приходится держать по 5 вкладок открытых на экране и просто ВЕРИТЬ, что сам не зевнешь и не забудешь ничего. И не помогает ничерта встроенный тс-анализатор с типами стандартной библиотеки и зависимостей.
![image](https://habrastorage.org/getpro/habr/comment_images/e9a/123/8b3/e9a1238b37c130d2f49fff9d59b2847a.gif)
Что? Нет, не так. ЧТООООО????!!! Автор хоть википедию не мог почитать перед выкаткой? Smalltalk, CLOS, Obkective-C? Это типа половина ОО-языков к концу 80х.
Именно поэтому есть фронтендеры, которые покрывают свой код невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors.
Ну они точно в меньшинстве. Большинство просто не пишет юнит тестов, потому что who cares if doesn't work sometimes.
Нельзя не согласиться. Это способствует формализации задачи, пониманию аппаратных и алгоритмических основ вычислительного процесса. Это фундамент программиста.
На аппаратном уровне популярных архитектур типов нет, типизации нет.
Операции для целых чисел и с плавающей точкой не отличаются? Откуда понятия word, dword и прочие?
В алгоритмах хоть типа пресловутой сортировки… тоже типов нет.
Это обобщенные высокоуровневые алгоритмы. Если копнуть чуть глубже в реализации: для сортировки нужно выбрать некоторый критерий сравнения, который наверняка будет отличаться для разных типов.
Операции отличаются, конечно, по содержанию — разные манипуляции с битами в памяти проводят. Но они не контролируют, что записанные в память аргументы имеют нужный тип. Вы вправе записать в память строку в кодировке koi-8 из пользовательского ввода и вызвать над ней операцию извлечения квадратного корня — ошибки быть не должно. Операции процессора ожидают, что вы сами проконтролировали какой тип операнда им передали, если ожидаете разумного результата. Контролировали вы в уме при написании ассемблерного кода или умный компилятор высокоуровневого языка не дал вам скомпилировать использование значения неожидаемого типа в операции — операции всё равно. Даже если вы вообще не контролировали. word, dword — это не про типы данных, а про их размер прежде всего.
> Если копнуть чуть глубже в реализации
Мы об алгоритмах или их реализации? Ну и реализации тоже может быть всё равно на тип. Критерий выбран будет по ожидаемомум типу без контроля. Как в математике (грубо) «Пусть N — множество чисел, с операцией сравнения определенной как...»
И обычно низкоуровневые реализации не проверяют типы, да и не имеют такой возможности, а исходят из «пусть адрес который пришёл к нам на вход в регистре указывает на начало последовательности 32-битных целых чисел в дополнительном коде с little endian порядком байт»
Но они не контролируют
Но это не означает, что типов нет. Инструкции ожидают аргументы вполне определенных типов.
word, dword — это не про типы данных, а про их размер прежде всего.
В сях
short
и long
тоже могут отличаться только размером. Но это ведь разные типы.Как в математике (грубо) «Пусть N — множество чисел, с операцией сравнения определенной как...»
Набор данных, связанный с некоторым множеством допустимых над ним операций — это и есть тип.
В сях типы, да, пускай и слабенькие. Но в ассемблере (классическом) типов нет, просто подсказка программисту сколько байт инструкция будет обрабатывать и, в лучшем случае как интерпретировать.
Вот только в алгоритмах такие типы в лучше случае определяются как условия для применения алгоритмов, а часто вообще не определяются, подразумевая что если над входными параметрами какая операция в алгоритме применяется, то ра для них определена.
Вот только в алгоритмах такие типы в лучше случае определяются как условия для применения алгоритмов
Эти условия — это и есть типы.
Например
std::sort
, который подразумевает, что в для типа должна быть определена операция меньше ("<"). Хотя это вполне себе обобщенный алгоритм.Но они не контролируют, что записанные в память аргументы имеют нужный тип.На некоторых процессорах — контролируют. Эльбрус пресловутый. А на «обычных» процессорах… да, после громкого провала iAPX 432 про это направление на много лет забыли… но сейчас потихоньку начинают-таки двигаться в эту сторону
$var = 123;
$var = $var + "123";
Как представлена переменная var в памяти и по какому адресу она лежит?
Что выполняет процессор на второй строчке?
Возвращаясь к прошлому тезису: стоит ли с подобного объяснения начинать обучение человека с условно нулевыми знаниями?
Все очень мега-круто и гвоздями к затылку, но кнопка только слева и только квадратная и так у всех и навсегда или все взорвется!
Статика это дело машины а не человека, статика допустима в разработке инструментария но не конечного продукта!
Да, я беру тайпскрипт фреймворк и вижу сигнатуры методов и возвращаемые типы:
String, Boolean или Number
Но если я сам начинаю городить свою объектную модель да ещё строгую, то я просто связываю себя по рукам и ногам и чем больше типов я пложу тем больше я в них увязаю и в итоге получается гора типов которая умеет меганадежно нажать кнопку.
JS за пять лет наплодил кучу ошизительного софта от системного до прикладного какую все ЯП до него не смогли даже на сотую часть.
Потому, что все дело «разработки» в статике начинается и заканчивается «объектным анализом» «разработкой классов» а стадия «использования классов» не наступает никогда!
Представить себе ситуацию океана статических либ типа js-ных на гитхабе — невозможно именно потому что у каждого свой царь в голове и своя «объектная модель» и врубаться каждый раз в новую ОМ просто чтобы только узнать чего и как там делает эта либа это нереальный мрак потому что каждый тут же начнет переколбашивать ОМ «как правильно» и до дела опять дело не дойдет!
Статика это смерть, ее нужно дозировать как яд, в минимально0достаточной дозе на максимальном удалении от переднего края где кипит жизнь а не тащить «по поределению» потому что «гвоздями к голове»
А прав я потому, что апогей статики и паттернов — Java приказала долго жить, сожрав бюджеты планетарного масштаба и так ничего путного и не родив, кроме пары мелочей типа джава-игр на старых мобильниках да джава-кард в банковских терминалах.
И вот теперь вышедшие было в тираж джава-лиды-чемпионы приползли в райский сад JS и стали кадить своей ересью направо и налево, коварством поистине адового уровня — type script
сажать на кол, и жечь под городской стеной упырей!
Приверженцы статической и динамической типизаций никогда не поймут друг друга. И TypeScript им не поможет