Когда мы с другом учились в школе и только мечтали стать разрабами, мы думали как сделаем вместе какую-нибудь штуковину — игру или супер-полезную программу.
Я начал учить плюсы и C#, он — JavaScript. Закончили школу, отучились в универах, отслужили в армии, устроились на работы. Нас потаскало промышленной разработкой там и тут, и когда мы оба от нее подустали, вспомнили с чего начинали.
Собравшись уже матерыми разрабами, мы решили, наконец, сделать свой проект — двумерную видеоигру. Так как друг был чистый фронт, а я фулстек, очевидной платформой для нас стал браузер. Раньше я разрабатывал фронт только на TypeScript, и мы подумали, никаких проблем, TS — всего-лишь надмножество JavaScript. Возьмем его и все пройдет гладко. Как бы не так. Когда мы начали обсуждать работу, столкнулись с невероятной бездной непонимания друг друга.
Вот я увидел задачу сделать игру. Я такой — ага, у нас есть тип «игра», тип «инвентарь», тип «вещь», тип «карта», тип «локация». Я примерно представляю, как они работают вместе, описываю их, собираю проект и все работает. Компилятор меня проверил, я все сделал правильно. Дальше я начинаю писать код, который использует эти типы. Из-за того, что они есть, мне намного проще. IDE мне подсказывает и проверяет на ошибки. Если проект собирается — скорее всего он и работает. Я потратил силы на описания типов, и это принесло пользу. Вот мой подход.
Мой друг хотел делать наоборот — сразу писать код и не описывать никакие типы. Он не был готов определять проблему в виде семейства типов. Не хотел отталкиваться от нее, не видел задачу, как набор классов, типов, рекордов или чего-нибудь еще в таком роде. Мне это казалось просто немыслимым. Мы оба во многом были правы, просто правота каждого из нас исключала другую.
Серьезно, мы говорили часами, но при этом каждый говорил о своем, как будто на разных языках. И ведь не спишешь на то, что у нас мозги не гибкие. Буквально год назад я безболезненно переехал в функциональный мир из объектно-ориентированного, и обратно. Больше того, я потратил достаточно много времени на изучение JS, а он — на изучение нескольких статически типизированных языков.
Но технология, которая стала для разрабов первой «боевой», определяет их настолько сильно, что два взрослых опытных человека просто не готовы друг друга слушать. За годы изучения разработки наше видение сформировалось слишком по-разному и подходы к решению задач совершенно не работали вместе.
В итоге мы отказались от идеи работать друг с другом. Сразу приходит в голову мысль, что проблема только в нас двоих. Может быть, но я видел такое и у других людей в индустрии.
У статической и динамической типизации есть принципиальная непримиримая разница
Мой код отвечает на вопрос «Как с этим работать», а как код разрабов, привыкших к динамической типизации — «Как это работает». Оба подхода имеют право на жизнь, и есть инструменты для обоих, но только один может стоять во главе угла.
Статическая хороша для больших проектов, которые годами делают сотни разрабов, а динамическая — для маленьких команд, для задач, где часто нужен write-only код. С динамической ты экономишь время и силы в начале разработки, а со статической — в конце.
Идея ставить во главу типы серьезно повлияла на мое мышление разработчика.
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю. Увидев любую задачу, я пытаюсь представить ее решение как набор типов и принципов их взаимодействия. Когда я разрабатываю модуль, первым делом определяю, какими типами он оперирует и какими взаимодействует со своим окружением. Я даже не помню, как я подходил к решению задач раньше.
Все обучение программированию на Java — это обучение проектированию и использованию типов. .NET CLR — рантайм C# — построен на типах и для типов. Cтатическая типизация лежит в центре дизайна объектно-ориентированного программирования (привет, классы из JS, я решил, что вы не нужны). Канонические реализации большинства ООП паттернов пестрят кейвордом Interface, который совершенно бессмысленнен в динамически типизированном языке.
Сами паттерны — штука кросс-языковая, но кто-нибудь может мне сказать, зачем нужен паттерн «состояние» в динамически типизированном языке? А билдер? Эти паттерны не про разработку, они про типы. Типы и ООП связаны неразрывно.
Нельзя строить свою бизнес логику, основываясь на типах, но при этом ничего о них не знать в процессе разработки. Именно поэтому есть фронтендеры, которые покрывают свой код невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors.
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.
Это не значит, что динамически типизированные языки не нужны (на самом деле я и правда думаю, что не нужны). Это значит, что при их использовании, тебе нужно отказаться от ООП, как главенствующей парадигмы. Все вот это единство данных и операций над ними — это для парней, у которых есть статическая проверка.
Разрабы, с которыми я имел дело, не считают, что наличие статической типизации меняет подход к разработке, и пишут такой же код, как и в динамических языках, добавляя проверку типов. Я считаю — это в корне неверно. И наиболее сильно это заметно именно в кейсе с современным фронтендом.
Знаю, критиковать фронтов — табуированная тема. Однажды мы с друганом зафигачили ИИ, который троллит всех в твиттере, и на нее сагрился Брендан Айк. Серьезно, создатель джаваскрипта воевал с нашей нейросетью в комментах.
Эти ребята по неведомой причине просто не готовы жить в мире, где их видение содержит серьезные пробелы. Поэтому я критикую только тех из них, кто суется в мой definitely-typed проект, и наводит там свои any порядки.
Мы бы так и жили в своих мирках, но TypeScript нас столкнул
Вот я пишу код, который за счет типов невозможно использовать неправильно. В проекте я рассчитываю, что и другие будут использовать типы. Тогда мой код будет работать так, как я задумал. И я осознанно не покрываю все кейсы неправильного использования этого кода (потому что типизация это исключает). Но в проект приходит JS-ник, берет этот мой тип, заворачивает его в any, и начинает использовать его неправильно, что приводит к трудноуловимым багам.
JavaScript-разработчики убеждены, что Тайпскрипт — это все тот же JS, но с возможностью иногда добавлять статическую проверку типов, если она им нужна. Это не так. Тайпскрипт в сотню раз мощнее, а их интересуют только три процента его возможностей.
Самый разрушительный аргумент — TypeScript лишь надмножество JS. Но на практике ты не можешь не рассматривать Тайпскрипт, как самостоятельный язык, даже если ты чертов король фронтендеров. Потому что тут нужен другой подход — статический, а не динамический.
Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.
Многим кажется, что TypeScript — это компромисс систем типов между JS и Java. Никакой он не компромисс, его система типов просто особенная.
Все усугубляется тем, что сегодня каждая вторая вакансия на фронта требует знания TS. Это приводит к тому, что JS-разработчики поверхностно изучают возможности Тайпскрипта, и начинают писать на нем код, создавая огромное количество вредительских практик. Возникает ситуация, когда им на самом деле не нужен статический тайпчекинг, но им его навязали, и они его испортили. Нужно наконец признать, что подходы к разработке при динамической и статической типизации противоречат друг другу, это не те вещи, которые можно смешивать.
JavaScript, как мне видится, очень хороший инструмент, чтобы написать код, который решает проблему, не выделяя при этом лишние абстракции. Самый вопиющий кейс в нем — паттерн Ice factory. Этой штуке можно скармливать свои инстансы, и она обмазывает их рантайм-иммутабельностью. Если я обработаю этой фабрикой свой объект, она отдаст мне такой же, но при попытке изменить значение одного из свойств, я получу исключение. Просто, WAT?!?
Паттерн возник, потому что фронты где-то прочитали про крутую иммутабельность, и решили затащить это в свой язык, никоим образом не предназначенный для иммутабельности. В Тайпскрипте я могу сделать такую же фабрику, но с компайл тайм запретом мутаций, и без всяких исключений.
С другой стороны, и это не нужно, потому что есть чистое функциональное программирование. Бери F#, Haskell, OCaml, Кложу, ReasonML — там мутации запрещены из коробки. Но я боюсь, что если дать фронтендеру функциональный язык, он тут же начнет модернизировать его и сделает поведение похожим на JS.
Потому что выбор типизации — путь без возврата. Все компромиссные решения дают только иллюзию компромисса. Ты либо ставишь во главе угла типы, либо нет. Не знаю, как бы все сложилось, начни я учить C# и JavaScript одновременно и параллельно друг другу. Но сейчас я так крепко прибит к своему мышлению, что не вижу и не хочу видеть плюсов динамической типизации. Они есть, но вне поля моего зрения, и все что мне остается — закрывать на них глаза, как на любые чуждые мне проявления мира, с которыми приходится жить. Понимаю, что не прав, но работать мне надо здесь и сейчас, и метаться между полюсами бюджета нет.
Поэтому я не хочу искать компромиссов, а говорю прямо. Если только начинаете изучать разработку, начинайте со статической типизации.
AnutaU
А чем, например, Ruby не ООПшный?
Bringoff
А куда за тот год, пока я не видел Ruby, с него убежали типы?
phoenixweiss
Никуда не убежали, но в статические они так и не превратились.
Halt
Мне кажется, статья будет неполной без этой, классической уже, картинки:
OnYourLips
Внутри функции разница несомненно есть, но поймать ошибку на таком уровне уже очень сложно, инструменты статического анализа в этом помогут.
Что касается типизации, то люблю оба типа. Динамическая очень полезна, когда надо что-то на коленке на несколько сотен строк написать.
aikixd
Имеется ввиду type inference, Когда компилятор догадывается о типах самостоятельно.
BlessMaster
Если они используются для статического анализа, в чём смысл их проверять в рантайме? Если после статического анализа приходится проверять в рантайме, в чём смысл такого "статического" анализа?
VolCh
Как минимум, результаты статанализа можно игнорировать, поэтому проверка в рантайме нужна. А статанализ нужен для выявления ошибок без запуска.
0xd34df00d
Нельзя, если он встроен в компилятор и называется тайпчекером.
VolCh
Речь о динамически типизируемых языках.
BlessMaster
Вопрос как раз об этом, зачем нужен статанализ, результаты которого могут быть проигнорированы? Что этот статанализ вообще тогда даёт? :-)
VolCh
"Предупреждён — значит вооружен" :) Очень многие инструменты статанализа, даже встроенные в компиляторы языков дают лишь предупреждения в опасных ситуациях, но не прекращают компиляцию.
codemake
Как-то за js обидно
zodchiy
Знакомое чувство.
HerrDirektor
Ага, есть такое…
LMSn
Я тоже благодаря C# признаю только статическую типизацию, но не понимаю, от чего вы страдаете?
HerrDirektor
От того, что иногда (часто) приходится работать с ЯП, имеющими динамический подход. Вот с ними я и страдаю. Но не прям так уж чтобы сильно.
ЗЫ
А начинал я вообще с изучения Си в самом начале 90х по книге Кернигана и Ритчи, которую я, будучи студентом, случайно купил в славном городе Краснодаре.
Представляете, как я страдаю от самой мысли, что существуют ЯП с другими подходами? :) (шутка).
zodchiy
От лютого непонимания парадигмы F#, например. Очень трудно перестроить мозг с ООП на функциональщину. 12 лет с C# не проходят даром.
LMSn
Ну не надо путать теплое с мягким. Мы все же про типизацию говорим, а в F# система типов тоже строгая статическая. ООП и ФП — это из другой оперы, и, в отличие от типизации, ООП и ФП не взаимоисключающие, к нашему общему счастью. Многие недавние плюшки C# пришли после обкатки на F#.
gleb_kudr
Я начинал с C#, недавно влез во фронтенд. Не пониманию страданий автора. Динамика позволяет очень легко писать костыли в самом начале, когда структура сырая и ты сам не уверен, как лучше тут сделать. Затем после нахождения определенного пути, ты просто рефакторишь это в типы и все. В итоге в проекте есть фронтир, где на каждом шагу any. А есть устаканившаяся часть, где все по-полочкам. По-моему очень удобно получается.
mihacoder
А я начал с php и js. И теперь понимаю, насколько им мешает динамическая типизация. Точнее, мешает мне, особенно когда в чужом коде роешься.
VolCh
Вы уверены, что мешает вам динамическая? Большинство проблем типизации с которыми я сталкивался за почти 20 лет PHP+JS были связаны со слабой типизацией, а не динамической.
Neikist
Лично мне в 1с именно динамическая мешает. Когда чтобы посмотреть что нужно в метод передать приходится лезть в код этого метода. Особенно весело когда метод может не вернуть ничего, один элемент, коллекцию, или строку с ошибкой и все это одновременно.
IvanNochnoy
Да чё говорить-то. Хотели, как проще, а получилось как всегда. В PHP и Python, добавили аннотации типов. В PowerShell и TypeScript используются с самого начала. Тенденция, однако.
worldmind
Ну так оно и логично, есть выбор — мелкий проект, прототип — можно без типов, что-то серьёзное — юзай типы.
alexesDev
Намешано.
— я пишу на C++/go/js/js+flow/typescript, увлекаюсь Haskell и ReasonML и тп. Я понимаю какие ошибки вы описываете, но не понимаю каким боком тут JS. Писать на C# можно ещё хуже, чем на JS и типы не спасут, можно глянуть www.govnokod.ru/csharp
— JS относится не только к фронту, но и к беку, а вся статья пронизана ФРОНТЕНДЩИКИ ПИШУТ ОТСТОЙ
— в проектах совсем мало ошибок, которые уберёт статическая типизация. Никто не пишет невообразимое количество тестов на type errors в js… так же как на C# никто не пишет проверки ссылок на null повсеместно или вы пишете?
— много чего ещё
У меня вывод один, нужно расслабиться и заниматься свои делом. Новичкам эта статья не поможет, а остальные и так все понимают.
Grox
Ну как пронизана, когда речь о только о методах работы с данными? Автор же так же пишет фронт, но на TypeScript, что сейчас совершенно логично делать ибо даёт все описываемые им преимущества.
Cidevant_Von_Goethe
А вот скажите мне, как типы помогают в фронтенде? Как написать тип, когда одна переменная зависит от другой? Например бэкенд возвращает что то вроде:
Например, если `other_flag: true`, то возвращается еще один параметр `other_param: 'Some string' `
Каким образом можно описать зависимость данных в типах? Только не надо писать: «Поставь `other_param?: string`». Это не решение проблемы.
mayorovp
Вот так можно же:
Cidevant_Von_Goethe
И сколько нужно создавать под-типов одного типа? Данное решение является самоубийством!
ApeCoder
Очевидно в данном случае два.
Любое решение является самоубийством, если его применять везде и для всего.
mayorovp
Столько, сколько потребуется. Не забывайте, что кроме оператора
|
есть еще и оператор&
, а сильно сложные перекрестные зависимости между разными полями признаются говнокодом в том числе и на бакенде.Cidevant_Von_Goethe
Не путайте «говнокод» с извращением. То, что предлагает наш уважаемый коллега mayorovp, является извращением. Такое решение «пригодно» (очень с натяжкой можно использовать это слово) для маленьких систем, где нет больших данных. Представьте себе объект с 200 атрибутами и более 30 внутренних зависимостей. Перечислять все возможные состояния объекта (делать под-типы) будет только человек, не имеющий большого опыта в программировании и дебаге. Очень хочу посмотреть на этого человека, когда появится первый баг.
Также, очень заметным является то, что наш уважаемый коллега mayorovp, имел дело только с типизацией в JavaScript. Если бы у него имелся опыт работы с типами в других языках (например функциональных или в языках с сильной типизацией), я уверен, что он бы такое решение не предложил.
mayorovp
Во-первых, не надо про меня ничего выдумывать. Я имел дело и с C#, и с Haskell.
Во-вторых, объект с 200 атрибутами и 30 зависимостями просто нуждается в декомпозиции. Само существование такого объекта — признак лютого говнокода. Но даже если на бэкенде сидят странные личности, которые не видят никаких проблем в таких объектах — Typescript позволяет провести декомпозицию исключительно на уровне типов с помощью упомянутого мною оператора
&
. Разбиваем этот мега-тип на 10 типов (именованных!) с 20 атрибутами и 3 зависимостями каждый — и вот уже все не так страшно.В-третьих, даже если вы не будете ничего разбивать, и напишите 200 опциональных свойств в одном типе — это все еще будет лучше чем то что предлагает JS.
Cidevant_Von_Goethe
«Само существование такого объекта — признак лютого говнокода.»
С этим не согласен. Не имея понятия, зачем это делается, не стоит сразу выносить вердикт. Но эту проблему можно смотреть с разных сторон:
1) Как вы и писали — декомпозиция, т.е. подкачка данных с других источников (api endpoint). Но это несет за собой такую неприятную вешь, как отклик сервера и количество соединений с ним.
2) Сделать один api endpoint, где будет вся информация.
Что лучше, «засорить» сервер кучей маленьких requests или сделать один request, где будут все данные? На эту проблему нужно смотреть с высоты «нужд» и производительности системы, в частности бэкенда. То, что для вас является «говнокодом», на самом деле есть потребность.
«Я имел дело и с C#, и с Haskell.»
Что-то вообще, ну прям вообще не ощущается у Вас. Могу предположить, что у вас мало опыта (работы с типами) в этих языках и что основным вашим языком является JS.
mayorovp
То есть вариант "сделать один api endpoint, но с адекватным контрактом" вами не рассматривается в принципе?
Могу сказать тоже самое про вас. Может быть, вы вместо пустых обвинений возьмете да расскажете как это делается на том же C#?
Cidevant_Von_Goethe
«Могу сказать тоже самое про вас. Может быть, вы вместо пустых обвинений возьмете да расскажете как это делается на том же C#?»
Коллега, вы вероятно ошиблись. Я с С# никогда не работал и никогда этого не утверждал.
«один api endpoint, но с адекватным контрактом»
Поподробнее, пожалуйста.
mayorovp
Ну тогда приведите как это делается на том языке, с которым вы работали!
И пример с того объекта на 200 свойств и 30 зависимостей тоже приведите.
Cidevant_Von_Goethe
А почему вы думаете, что я знаю решение? Проблема относиться к фронтенду (увы, я его писал не в Haskell) и я никогда не писал, что у меня есть решение. Я знаю о существовании проблемы, мы наступили на все грабли, на которые только могли наступить. В итоге, после несколько месяцев мучений, мы просто вырубили «flow» в этом куске кода (где работаем с этим мега-объектом), и в ручную делаем валидацию. Идеального решения этой проблемы я так и не нашел.
Поэтому я так активно принимаю участие в этой дискуссии и мне очень любопытно наблюдать за решениями, которые вы предлагаете! Разница только в том, что все эти решения мы уже пытались внедрить, но ни одно решение не дало хорошего результата (нагрузки на сервер, читаемость/поддержка/добавление кода и т.д.). В итоге мы остановились на решении, которое вы называете «говнокодом».
Очень хочется узнать о «один api endpoint, но с адекватным контрактом»
mayorovp
Вы можете сообщить хоть какие-нибудь подробности о своих затруднениях? Хоть один инвариант объекта из тех 30? Примерную структуру объекта на сервере? Правила формирования? Почему вы ожидаете что все остальные способны угадать ваши проблемы?
Вот еще один из вариантов. Избыточный для исходной вашей задачи, но применимый в сложных случаях.
Cidevant_Von_Goethe
По своей сути — это композиция типов и это означает, что новый тип не определен для других функций. В целях переиспользования кода (и композиций функций), все типы должны быть определены перед использованием. А это в свою очередь ведет к «types hell» — миллиону вариаций (типов) одно и того же объекта, и разобраться с этим человеку — почти не реально (что в свою очередь никаким образом машине не мешает, только пару байт сверху).
Спустя некоторое время, я все более склоняюсь к идее, что можно «вертеть типами» во все стороны, чтобы правильно реализовать задачу, но есть задачи, которые невозможно типизировать способом, который удобен программисту. DRY (do not repeat yourself), читаемый код, поддерживаемый код — это пожалуй самые уязвимые места этой проблемы.
Druu
Вы можете написать типы с T | undefined (aka T?), тогда тип будет один.
Вы можете написать как вам выше указали — каждый кейз отдельно и потом объединить через & — будет всего тридцать типов, для каждого кейза. При этом данный набор типов будет прекрасной документацией вашего апи (которое весьма сложное учитывая наличие такого количества зависимостей) сам по себе, это уже не говоря о статических проверках.
Мне кажется, что все ваши упомянутые выше "грабли" были исключительно из-за того, что у вас не было человека, который бы нормально умел в используемый инструмент (flow в вашем случае). Еще вариант — дело было давно, когда во flow еще не было discriminated unions.
ApeCoder
Почему не решение?
Cidevant_Von_Goethe
Это является «кривым» решением в JS. Мой вопрос касался типов в общем. Не стоит использовать реализацию типов в JS как нечто стандартное, универсальное. Такое явно не прокатит в Хаскелле и вероятно в других языках тоже. Перестаньте смотреть на JS как на «Cвятой грааль», эталон.
Chamie
Это вообще не JS, это TS. Не надо путать.
Cidevant_Von_Goethe
Я ничего не путаю. Даже автор статьи написал: «TS — всего-лишь надмножество JavaScript.». Каким бы крутым не был язык TS — он все равно компилируется в JS. И самым слабым звеном здесь является JS. Какой бы «syntax sugar» нам не давал TS, все равно все переводится в JS.
PsyHaSTe
Каким бы ни был крут Haskell, он все равно переводится в машинный код. Какой бы «syntax sugar» нам не давал Haskell, все равно все переводится в машинный код.
Cidevant_Von_Goethe
Коллега, причем тут это? Или это не удачная попытка «перевернуть» мои слова против меня?
PsyHaSTe
Совершенно не важно, во что все в итоге конвертируется, ибо есть транспилеры/компилеры во что угодно. Вопрос, с чем мы работаем, и какие гарантии оно дает.
Cidevant_Von_Goethe
«ибо есть транспилеры/компилеры во что угодно»
Согласен. Есть даже интересный проект на github.
«Вопрос, с чем мы работаем, и какие гарантии оно дает.»
Тогда, коллега, задам наводящий вопрос: «Почему TS компилирует в JS а не в машинный код?»
faiwer
Где востребовано, туда и компилируется. Будет сильно нужно — будут через LLVM компилировать в бинарники. Why not?
Cidevant_Von_Goethe
«Где востребовано, туда и компилируется.»
Вы сами ответили на свой вопрос! Поздравляю! Сила цепи определяется самым слабым звеном, что в нашем случае является JS.
faiwer
Г — Логика.
Cidevant_Von_Goethe
Очень конструктивно. Жаль, что не могу минусовать.
PsyHaSTe
У вас странное представление.
В ассемблере нет никаких типов. При этом хаскель в него компилируется. Вопрос, теряем ли мы все гарантии хаскеля от того, что компилируем его в васм? Приобретем ли мы что-нибудь (кроме производительности), если сделаем машину, способную нативно исполнять хаскель-код?
Cidevant_Von_Goethe
Давайте я спрошу по другому:
Гарантирует ли «Static type checking» в TypeScript тот-же результат при «Dynamic type checking» в Javascript в Runtime?
То, что у вас все компилируется в TypeScript, совсем не означает, что в Javascript (Runtime) все будет работать. Слабым звеном является Javascript.
Druu
Вообще-то означает. Все гарантии, которые вы выразили в тайпскрипт, будут выполняться для сгенеренного js-кода.
Cidevant_Von_Goethe
«Приобретем ли мы что-нибудь (кроме производительности), если сделаем машину, способную нативно исполнять хаскель-код?»
Я понял вашу мысль и вероятно не совсем понятно выразил свою.
Chamie
А машинный код — это тоже слабое звено?
PsyHaSTe
Вполне себе компилируется
Cidevant_Von_Goethe
«Неужели это возможно, Ватсон?»
github.com/mame/quine-relay
faiwer
Вы и C++ можете в JS транспилировать.
Meloman19
С каких пор такая зависимость должна решаться на уровне типов?
Описываете обычный тип состоящий из всех четырёх параметров. При десериализации параметр other_param, в случает отсутствия, останется равной null. А вот уже в коде вы же перед использованием параметра проверите, param_flag равен true или нет?
ApeCoder
Почитайте про зависимые типы
Meloman19
Спасибо, прочёл. Но как это в коде реализуется?
Видимо тут пример неудачный, потому что, как уже говорил, я сходу вижу конкретный тип состоящий из 4 параметров. Просто в одном случае мы получим other_param равной null, а в другом — какому-то значению. И уже в коде мы в любом случае будет проверять other_flag равен true или нет перед использованием.
Или в динамических языках это делается как-то по другому? Мне (я серьёзно) интересно даже узнать.
ApeCoder
Код знает какие типы могут быть. И может делать выводы о гарантиях. В typescript прочитайте про discriminated unions и type guards
https://www.typescriptlang.org/docs/handbook/advanced-types.html
Meloman19
Хм, ну type guards — это просто проверка типа получается?
Аналог typeof:
PsyHaSTe
Одна компания разработчик, один автор языков… Хмм… Да c# похоже все-таки повлиял на тайпскрипт!
mayorovp
Нет, type guards — это вынос проверки типа во внешнюю функцию, применяются совместно со смарткастами. Аналогов в C# у этой техники нет, за отсутствием смарткастов.
Druu
Тут все как раз наоборот — это в C# гварды как в typescript (нормальный паттерн-матчинг не осилили, вот и прилепили гвардо-костыли) :). А в typescript — они как в Racket.
VolCh
А я два типа из двух и трёх параметров и проверкой типа перед использованием. Код типа
При этом классы не имею поля other_flag и могут быть вообще не связаны друг с другом.
Cidevant_Von_Goethe
Как я уже писал — использовать под-типы (композицию типов) для описания всех возможных состояний возможно только в очень маленьких проектах и не является самой сутью решения проблемы.
0xd34df00d
Но в данном случае вам никто не мешает не проверить этот флаг перед использованием
other_param
, а с завтипами — мешает.Druu
C таким типом, как выше приводили:
не проверить флаг будет нельзя. И ЗТ никаких не требуется.
0xd34df00d
Как это не требуется, когда вон оно торчит, где
other_flag: false
илиtrue
?mayorovp
И что? Этого недостаточно чтобы называться завтипом…
0xd34df00d
Чтобы говорить, что язык обеспечивает полную поддержку завтипов — да, недостаточно, но речь-то не об этом. Потому что если мы начнём заниматься буквоедством, то окажется, что у нас и идрис не завтипизированный (потому что phase distinction есть и ограничения на то, что можно выражать в типах, потому что иначе тайпчекинг вообще неразрешим), что не очень интересно.
Я чуть ниже напишу сейчас Druu подробнее.
Druu
Ну это не ЗТ.
здесь нет зт, это по сути аналог паттерн-матчинга. просто если при стандартных АТД когда у нас data YobaType = C1 | C2, то C1 и C2 не являются сами типами, это просто конструкторы. А вот в случае типов-объединений — это именно типы самостоятельные, по-этому конструктор C1 возвращает один тип, конструктор C2 — другой тип, оба эти типы — подтипы типа YobaType. С-но в хаскеле когда мы находимся в какой-то из веток паттерн-матчинга мы имеем типа YobaType (т.к. типов C1/C2 нету, сузить тип в ветке матчинга протсо не на что), а вот в ts компилятор может сузить тип до более конкретного, т.к. соответствующие типы есть.
Ну и да, тут автолифтинг, то есть значения вроде true, false, undefined, 125, "yoba" автоматически лифтятся в соответствующий тип-синглтон, когда мы используем эти литералы в типовом контексте (вроде же кстати в хаскеле чот такое тоже прикручено в каком-то расширении).
То есть в объявлении типа выше true/false — это, с формальной точки зрения, именно лифтанутые типы-синглтоны, а не значения.
0xd34df00d
Ну так этот паттерн с синглтонами — это как раз классический способ выражения ЗТ в языке без них нативных, но с GADT (например) и DataKinds.
Тем более, что оно потом так же автоматически опускается обратно, когда вы в if проверяете, и с проверкой из if тайпчекер может вывести, какие операции над значением допустимы. Прям dependent pattern matching.
То есть, как бы, понятно, что это вопрос определений, но как бы общепринятый консенсус [в хаскель-коммьюнити], что когда вы пишете что-то вроде
то всё, приехали в славную завтипизированную страну.
Кстати, конкретно в хаскеле это вы написать так не можете, ибо конфликты типов лейблов (хотя, теоретически, наверное, компилятор мог бы и унифицировать до, скажем,
someFlag :: forall otherFlag. Yoba otherFlag -> Bool
), так что тут х-ль соснул у ts.А YobaType как записать можно?
Druu
Ну это конечно да, в некотором роде буквоедство, но смысл в том, что отображения тип-синглтон -> тип это по факту отображения тип -> тип, то есть, формально, ничего кроме лямбда-омеги тут не надо.
Я знаю, что тут есть другая точка зрения, но сам считаю, что зависимые типы — они только тогда, когда есть фичи, которые без лямбда-P не реализуются. Причем, да, получается, мы без лямбда-Р иногда можем делать некие вещи, которые с практической точки зрения мало отличимы от того, как ведут себя зт в определенных кейзах, тут спору нет.
Ну так объединением и записывается, неразмеченным. Допустим, у нас есть два типа A и B, мы сперва каждому типу добавляем тег в виде типа-синглтона, т.о. получаем типы-кортежи <tag1, A> и <tag2, B> и потом эти типы объединяем через обычное (неразмеченное) объединение, получаем YobaType = <tag1, A> | <tag2, B> (опускаем всякие несущественные нюансы со структурным подтипированием, тем что у нас поля на самом деле будут именованные а не безымянный как в кортеже и т.п.).
В случае когда мы при помощи АТД строим тип-сумму, то мы выполняем эти два действия в один шаг, по-этому типы <tag1, A> и <tag2, B> у нас просто не проявляются, то есть операция суммы — примитивная. В данном же случае она не примитивная и выражается через другие операции — умножения и объединения, в итоге у нас типы-компоненты объединения существуют.
В примере выше у нас tag'ом выступает поле other_flag. Ну и тот факт, что внутри true-ветки внутри if мы знаем какой конкретно подтип у значения — он тождественен тому факту, что в ветке паттерн-матчинга мы знаем, что значение может быть рпазобрано паттерном этой ветки. Просто в случае хаскеля мы этот факт (что значение х может быть разобрано паттерном Y) не можем выразить в виде утверждения вида "значение х имеет тип Y", т.к. нету подходящего типа Y. А в тс такой тип находится.
Ну и да, конечно, существование в системе типа, который позволяет делать утверждения определенного рода — это и есть характеристика выразительной силы системы, то есть в определенном смысле тут можно говорить что зт есть, но просто в таком случае само понимание зт становится очень размытым.
Еще кстати есть такая штука как фильтры (в ракете так называютя, хз как называется в тс). Например есть ф-я filter (обычная над списками), и у нее есть перегрузка:
и тут с-но вопрос, что значит это "value is S"? а это значит, что данная ф-я является гвардом (т.е. предикатом над T и истинность этого предиката влечет за собой сужение типа Т до типа S). Иными словами, мы можем подать на вход фильтру массив (A | B)[], гвард isA в качестве фильтра и получим массив A[] на выходе.
Но тут надо добавить что в тс гварды не чекаются. То есть можно взять любой предикат и объявить его гвардом любого типа, и компилятор это съест:
yoba: 0[] :)
0xd34df00d
Не, забавный этот ваш TS, я был о нём худшего мнения.
Например? Я с системой типов TS незнаком, но в том же GHC можно писать вполне себе почти завтипизированный код на синглтонах, проблемы возникают только при типах, зависящих от термов, принадлежащих какому-нибудь сигма-типу, но, насколько я понимаю, это ограничение реализации, а не принципиальное ограничение системы типов.
Druu
Ну классическое, мы сможем как-нибудь описать Vec[N], создать вектор длины N с соответствующим типом, где N считано из файла, и потом сделать type-safe доступ по индексу?
Cidevant_Von_Goethe
«С каких пор такая зависимость должна решаться на уровне типов?»
А с каких пор она НЕ ДОЛЖНА решаться на уровне типов?
«Описываете обычный тип состоящий из всех четырёх параметров.»
Вы работали с Haskell? Если бы работали, то не констатировали эту чепуху. То, что это реализуемо в JS (flow, typescript), не означает, что такое прокатит в других языках, где есть типы.
Мой изначальный вопрос ничего общего не имел с js.
0xd34df00d
В чистом хаскеле это очень больно решается, кстати, завтипы там прикручены сбоку и криво. Берите Idris.
Cidevant_Von_Goethe
Благодарю за Idris, не знал о таком. Жаль, что эти псевдо-эксперты типизированных языков, которые накидали мне минусов, типы использовали только в JS и думают, что знают о типизации все. По вам видно, что вы — человек с опытом и знающий.
Ogoun
Я не фронтендер ни разу, но когда возникает необходимость, делаю примерно так, если исходить из вашего примера:
Так как обмен данными формализован, набор полей которые может вернуть сервер известен, описываю тип в котором есть все поля которые могут придти, и после приема данных вызываю map, в нем можно доделать нужные трансформации и присвоить к полям типа который будет использоваться уже в JS. Ну и докинуть все нужные проверки.
lega
gnaeus
К сожалению без "
as any
" и "// @ts-ignore
" в TypeScript пока не обойтись.Потому что существуют библиотеки вроде React, в которых типизация поставляется отдельно (в пакетах "
@types/...
"). И часто типизация написана неаккуратно или отстает от последней версии библиотеки.Drag13
Так напишите свой интерфейс в своем проекте и прикастите к нему. Получите и строгую типизацию без any и минимум заморочек. И чужой код править не надо.
thekip
Специально для таких случаев существует Declaration Merging https://www.typescriptlang.org/docs/handbook/declaration-merging.html. Не нравится как описан какой то тип из тайпинов? Допиши сам!
А ставить any или того хуже @ts-ignore это подкладывать грабли себе же самому
gnaeus
А хотите пример? React
useRef()
. На flow он выглядит так (исходник):В
@types/react
так:А теперь попытаемся открыть в TypeScript пример из документации
Для Flow все нормально, он даже типы выведет. А TypeScript скажет:
[ts] Expected 1 arguments, but got 0
. И пока саму библиотеку и ее типизацию поддерживают разные люди, такое будет встречаться регулярно.Или вот прекрасная библиотека mobx-state-tree. Все в ней хорошо: и реактивность, и снапшоты, и статическая типизация, и проверки типов в runtime. Но похоже, что ее TypeScript интерфейсы не являются публичным API. Несмотря на то, что они эскпортированы из модуля. Да и сама библиотека написана на TypeScript.
Внезапно, между двумя минорными версиями 3.5.0 и 3.6.0 мы видим:
Ну и как таким пользоваться?
Это все не проблема TypeScript как языка. Это проблема экосистемы в целом. Я до такого докопаюсь. Возможно, напишу свои декларации. Буду их скрупулезно поддерживать. Но придет мой коллега из лагеря динамической типизации, и скажет: "Почему я должен тратить время на все это?"
IvanNochnoy
Могу предложить вот что:
1. Сделайте временный workaround, закомментируйте его.
2. Откройте issue в трекере DefinitelyTyped.
3. Откройте issue в трекере Facebook c предложением уже забить на Flow. Правда, учитывая, что их год просили добавить поддержку TypeScript в create-react-app, вряд ли они отрегируют в ближайшее время.
4. После того, как будет исправлено, уберите workaround.
Если мы будем искать экосистему без проблем, то не напишем ни одной строчки.
gnaeus
Да есть уже у них issue. Просто утомляет это все очень =(
vintage
Так не используйте такие библиотеки, какие проблемы?
Beyondtheclouds
все там нормально у реакта с тайпингами :)
aikixd
Типы рождаются не из ключевого слова class/struct/type а из вашей головы. Когда вы думаете о задаче, вы сводите ее к набору категорий, взаимодействующих по определенным правилам. Это относится не только к программированию, это способ мышления вообще. Если мы думаем об обществе, мы не думаем о каждом человеке по отдельности. Поэтому никого не удивляет что у человека есть метод «спать», а у общества нет. Поэтому, даже используя JS я создаю категории объектов которые типами не называются лишь номинально.
Вы начали с Шарпа и строгая статическая система типов потребовала от вас умения проводить свои мысли в порядок. Те же кто начал со скрипта и не имел врожденной склонности к порядку получили в голове кашу, причесать которую очень сложно. поэтому их код похож на доску конспиролога.
aikixd
Добавлю ещё что ООП и типизация перпендикулярные понятия. ООП получается если прикрутить код к данным (объекты носят свой код с собой). Это можно сделать на любом современном языке. Где-то проще, где-то сложнее.
khim
Можно написать достаточно удобную и типизированную программу даже на классическом Бейсике с двухбуквенными имеными переменных и отсуствием структур. А можно — кашу на любом «строгом» языке типа Ады.
Хотя корелляция между и есть, но разница между подходами, описанными в статье — примерно как между строителем мостов типа нашумевшего Крымского и строителем мостов через ручьи. Где можно натянуть несколько верёвок, прикрутить к ним палок — и всё.
Попытка построить таким путём мост длиной в 10 километров приведёт к тому, что его шторм унесёт в море задолго до того, как вы закончите его строить… а попытка строить мост через ручей «по феншую» приведёт к тому, что он обойдётся в 100 раз дороже… хотя и стоять будет дольше — но это не всегда оправданно.
taujavarob
aikixd
Истинно так. ©В JS незачем тащить никакое ООП как стандарт. Есть внешняя JS библиотека (а может их и много существует), используя которую, вы можете «породить» такое ООП (со своими правилами «наследования», «использования кода объектов») которое только придумаете сами!
Незачем «приколачивать» конкретную реализацию ООП в стандарт JS. Это уже(!) смысла не имеет никакого. — Хватит, что бессмысленный «const» ввели в стандарт JS.
JS чем хорош — что это свободный(!) язык. Как там говорится — используя JS вы можете делать что захотите, в том числе и плохие вещи — не каждый язык программирование это позволяет, да ещё в таких(!) масштабах. ;-)
eugenk
Понимаете ли какое дело… Будь у этих ребят в голове каша, мы бы не видели весьма сложных и нетривиальных библиотек ни на js, ни на питоне, ни на чём-то подобном. Но мы их ВИДИМ. И КАК они разработаны, для меня, как приверженца типов и статики, полная загадка… Возможно я чего-то в этой жизни не понимаю. И в этом мне дико хотелось бы разобраться, хотя сам скорее всего так со статикой и останусь.
aikixd
Те кто писал эти библиотеки, действительно талантливые программисты. И библиотеки эти создают некий порядок в проектах, который хоть как-то спасает. И заодно прочности у них хороший, так как насилуют их нещадно.
SquareRootOfZero
Он же не пишет, что у всех программистов на динамически типизированных языках в голове каша. Каша у тех, кто не может в самодисциплину. Статическая типизация дисциплину навязывает, а динамическая — нет, приходится самому.
x67
Откуда вы знаете, может это private метод, к которому просто никто не обращался)
marperia
Вы оскорбляете конспирологов, у многих из них доски поддерживаются в чистоте и порядке.
nexus478
На самом деле, думаю, что вы совсем не одиноки. Я уже как-то скидывал однажды картинку из доклада Романа Елизарова про развитие ЯП, скину еще раз, так как она весьма показательна.
AnutaU
Системы типов в мейнстрим языках становятся всё более развитыми и позволяют всё большую гибкость (= меньше напрягают, больше помогают), поэтому остаётся всё меньше поводов выбирать динамическую типизацию.
Aingis
Вопрос интерпретации. Я вот из картинки вижу только, что языки со статической типизацией сильно не удовлетворяют нуждам, поэтому народ постоянно выдумывает новые языки.
В то время как тот же Javascript быстро развивается (это уже не тот язык что был даже 5 лет назад), а его экосистема растёт экспоненциально. TypeScript же — просто калька, занимающая нишу подражания (старо-)новомодной типизации.
t13s
Интересная точка зрения.
В свою очередь, моя интерпретация вашей интерпретации, связанной с экспоненциальным ростом экосистемы Javascript, заключается в том, что без вагона костылей на Javascript написать ничего вменяемого ни у кого не получается. Отсюда и перманентные попытки придумать серебряную пулю.
aikixd
Что является следствием того что JS был много лет единственным языком для фронта. В свою очередь его архитектура является следствием того, что нужно было обеспечить быструю интерпретацию на слабых машинах конца девяностых.
eugenk
Интересно девки пляшут… И как же по-Вашему динамическая и никак не выводимая из контекста типизация помогает быстро интерпретировать код? Наоборот, в рантайме перед каждой операцией придётся ещё проверить, что именно в переменой лежит, если надо сделать какие-то умолчательные преобразования и т.д. А вот то что для подобного языка гораздо проще и быстрее написать интерпретатор, это да. Сильно подозреваю, что с js именно так оно и было.
khim
Получился, конечно, уродец тот ещё… но могло быть и хуже…
areht
> В то время как тот же Javascript быстро развивается
В чем это выражается? JS за последние 5 лет что-то принес остальному миру, новые идеи, практики? Или он растёт только относительно себя же?
> экосистема растёт экспоненциально
Рост экосистемы ценен тогда, когда каждый элемент работает из коробки, а вместе дают синергетический эффект. А когда «часто типизация написана неаккуратно или отстает от последней версии библиотеки» — это рост в сторону бесконечного решения специфической «проблемы ромба» из за несовместимости кусочков инфраструктуры и вечных войн тупоконечников с остроконечниками, в каждой итерации заканчивающихся созданием новых, несовместимых со всем остальным, кусочков. И переписывания старых на TypeScript.
А есть ли за всем этим синергетический эффект? Или только экспоненциальный рост сложности?
eugenk
Вот тут пожалуй с Вами не соглашусь. Да, развивается. И пожалуй да, быстрее чем всё прочее вместе взятое. Поглядите на библиотеки! То что js ИСКЛЮЧИТЕЛЬНО гибок, этого у него не отнять. Гибче пожалуй только Forth(хотя на мой взгляд это гибкость самоубийцы). И именно потому что бурно развивается, мне очень хочется понять ПРИЧИНЫ такой дикой популярности… Чисто любопытства ради.
khim
Пока что всё, что я видел — это, в основном, новые способы сделать то, что раньше можно было уже сделать и так — только с новым синтаксисом и порождением большего количества кода в результате.
C4ET4uK
Вы имеет ввиду библиотеки вроде left-pad?
slonopotamus
В процитированном вами тексте находятся одни вопросы. Как можно "не соглашаться с вопросами"?
Druu
С риторическими — прекрасно можно!
slonopotamus
Druu
Так выше, тот самый пост, на который вы отвечали. Риторические вопросы являются вопросами синтаксически, но при этом содержательно — они являются утверждениями. А с утверждениями можно соглашаться либо нет:
такие дела
В точности так же, как я (и любой другой человек) вообще определяю смысл сказанных (или написанных) слов. Исходя из своего опыта, что те или иные речевые конструкции значат в данном контексте то или иное.
Раз человек мне что-то говорит, то он дает мне право решать, какой смысл в эти слова вкладывался. Потому что никто другой это решить не может.
Free_ze
В чем же состоит ИСКЛЮЧИТЕЛЬНАЯ гибкость самого JS, которая оказывается вдруг не свойственна остальным языкам общего назначения?
faiwer
Полагаю автор комментария выше имел ввиду, что в JS в runtime вы можете вытворять практически всё, что угодно, кроме перегрузки операторов (её нет). Всякие там proxy, символы, defineProperty с хитрыми полями, getter-ы и setter-ы, toPrimitiveValue|toString|valueOf и пр. позволяют с виду простой очевидный донельзя код заставить делать что-то совершенно иное. Чем-то сродне
define true false
но в runtime. Весь этот хаос может как позволить написать супергибкие решения, так и опрокинуть вас в пучину хаоса с использованием минимума кода. Мне кажется, что JS это язык с беспредельным количеством анархии. Этакий godmod. Даже прототипная система наследования устроена так, что вы можете менять предков, поля\методы предков в мановению щелчка пальцев. Вы можете легко переопределять почти любые объекты и методы из "стандартной библиотеки". Ну в общем если в двух словах — уровень допустимого беспредела неограничен. И всё это в runtime. Хорошо это или плохо — судите сами.За другие языки "общего назначения" не скажу. Полагаю таких языков полно.
Cerberuser
Не знаю насчёт «общего назначения», но, когда я увидел, как можно вывернуть наизнанку код на Ruby, вмонтированный в RPG Maker (обычный Ruby, только немного урезанный), мысли были в общем и целом схожие.
Free_ze
В этом нет ничего уникального. Ладно, если на C/C++ это будет выглядеть колдунством над сырой памятью, но на языках с полноценным рефлекшном, вроде C#/Java это будет происходить в управляемой среде с контролем типов.
Манкипатчинг вроде считается плохой практикой и часто используется, емнип, для костылей, вроде полифилов. Но это локальная проблема веб-фронтенда.
faiwer
Я не знаток C#/Java, но разве рефлексия в этих языках про это? Беглый гуглёж пишет: "В мире .NET рефлексией (reflection) называется процесс обнаружения типов во время выполнения". Да и судя по примерам там сплошные: IsGeneriсParameter, GetNestedTypes, InvokeMember, GetType и подобные штуки. Т.е. скорее анализ существующих методов. Я выше писал про вещи соооовсем другого порядка.
Free_ze
Да, рефлекшн — это про рантайм. С помощью него можно выполнять некоторый код динамически, создавать объекты неизвестных типов и прочее. Например, проксировать вызовы методов неизвестного типа.
Развитые типобезопасные аналоги in, hasOwnProperty и isPrototypeOf и прочих.
Приведите пример задачи, которую по-вашему можно выполнить в JS, но нельзя, скажем, в C#.
Конечно, синтаксически языки отличаются, но обеспечить ту же семантику возможно. Например, в JS Object — это по сути Dictionary, написать ему обертку с прототипами, про модификацию свойств, итерирование, упоротый кастинг в соответствующие примитивы и будет так же весело.
faiwer
Если бы я ещё хоть что-то понимал в C#. На мой взгляд все тьюринг-полные языки позволяют решать все задачи. Никто же не мешает написать на brainfuck операционную систему.
Ну и работать это будет только на вашем отдельно взятом велосипеде, верно? А просто взять какой-нибудь первый попавшийся объект и устроить над ним и его предками аморальные генетические опыты получится? Скорее всего нет, ведь все эти вещи уже на этапе компиляции оптимизированы на заранее созданную структуру данных с соответствующими смещениями.
Другой вопрос — а оно надо вообще? Вся эта анархия. Оставлю его за кадром. Речь шла о гибкости. И да, JS ну прямо очень гибкий. Можно построить какого угодно, даже неудовлетворенного желудочно, кадавра из практически любого объекта, что попадётся под ноги. Любителям извращений JS подходит на пятёрочку.
Free_ze
Чтобы решить мою задачу, мне пригодится мой велосипед)
Наследоваться, проксировать, покрывать фасадом. В зависимости от задачи. Не нужно искать прямых синтаксических аналогов JS здесь)
sshmakov
Абстрактный пример. Есть некая библиотека, в которой есть фабрика, создающая экземпляр класса, и использование методов и полей этого экземпляра. Методы не виртуальные, поля приватные. Вам нужно, не меняя в целом логику библиотеки, поменять поведение некоторых методов.
Free_ze
В реальной жизни я не буду лезть в кишки чужой имплементации ибо: а) это риски на будущее, б) могут быть лицензионные ограничения.
Но даже если упоролся, у меня нет причины заниматься манкипатчингом в рантайме, я сделаю правки в исходниках библиотеки.
sshmakov
То есть нельзя. Аминь.
nightwolf_du
Вообще, строго говоря, можно.
Рефлексия позволяет сделать всё, включая прямую генерацию IL-кода.
Я не идеально разбираюсь в процессе, но из кажущихся решений — можно подменить вызов метода своим методом.
Но надо понимать, что это code injection со всеми связанными с этим особенностями, и относится больше к области хакинга.
Мне это понадобилось ровно один раз — в стандартной библиотеке работы с сетью при https запросе внутри происходит два http запроса, и между ответом первого и отправкой второго не прокидываются куки. А мне очень надо было. После быстрого гуглежа и попытки — я получил ошибку с крашем стека приложения и сделал всё совершенно иначе.
После чего открыл для себя возможность поправить и перекомпилять уже собранную сборку через дизассемблирование парой хитрых инструментов, и больше вообще не запаривался этим вопросом.
areht
> поля приватные
Это рефлекшену разве мешает?
Ну а вообще, можно хоть Microsoft Fakes в боевой код притащить. Но могут побить.
faiwer
Вот например в эко-системе redux необходимо работать с immutable-данными. Каждая манипуляция над данными приложения должна менять весь store-приложения цельно (привет ФП). По сути это что-то вроде 1 переменной на всю "программу". А т.к. JS это не Haskell, то код, позволяющий этого добиться, выглядит… несимпатично, если сказать мягко. Та же картинка, я полагаю, со всеми императивными языками. Так вот, в JS отдельно взятые авторы написали велосипед, позволяющий писать визуально мутабельный код, оставив его под капотом иммутабельным. Работает это за счёт использования механизма Proxy. В итоге:
Мы казалось бы явным образом задали 12 элементу массива в поле someValue число 12, а на деле произошло нечто совершенно другое. Просто каждая манипуляция с store трекалась, и вместо реальных объектов всё время подсовывались Proxy-обёртки, которые все операции записи превращали в down-up rewrapping.
Дичь? Ну наверное. По словам автора это даже довольно быстро работает. В C# получится написать такой костыль?
mayorovp
Насчет скорости и "довольно быстро работает" тут еще подумать надо, но синтаксически вот так сделать получится без особых проблем:
faiwer
Ну вы сравнили. У вас какой-то Immutable.JS получился. Даром не нужен :)
mayorovp
А что не так-то? 9 лишних символов (
.Set(s>,)
) так принципиальны?faiwer
А всё не так. В реальном redux-reducers коде у вас только эти 9 символов, помноженных на каждую мутацию, и останутся. За ними мало что разглядеть можно будет. В гробу я видал такую гибкость и наглядность. redux-reducers в себе хранят по сути всю бизнес-логику приложения. Т.е. весь треш и угар как раз здесь. Чем более наглядно здесь описана логика, тем проще с этим работать. Так что да, более чем принципиальны. А если ещё вспомнить, что это поделие (immutableJS) тормозит, то я вообще не понимаю, зачем им кто-то пользуется.
mayorovp
А почему вы решили, что предложенный мною вызов будет тормозить так же как ImmutableJS?
faiwer
Ну тут я погорячился, да :)
Free_ze
Звучит действительно дико)
Решение влоб — через обертку над стором, который бы внутри пересоздавался на каждое изменение. Основная проблема, над которой придется пободаться — это поддержка синтаксиса, ибо для поддержки кастомного сеттера проксировать необходимо весь граф объектов внутри. Но это должно быть решаемо и сильно зависит от исходного API доступа к свойствам.
ЗЫ Но, да, в .NET вряд ли появится redux, ибо глобальное состояние — это отличный маркер плохого дизайна и за это там гоняют тряпками.
У JS-сообщества это еще впереди.Druu
А какой толк от того, что код где-то там иммутабельный, если он выглядит мутабельно?
faiwer
Мутабельный код в JS выглядит проще, писать его легче и быстрее, читать и понимать тем более. За счёт простоты и наглядности в нём потенциально меньше багов. При этом в redux нам нужен именно иммутабельный код, для нормального функционирования системы. Этот инструмент призван помочь усидеть на двух стульях сразу. Дёшево и эффективно писать бизнес-логику не лишаясь преимуществ flux-подхода.
Справляется или нет он с этой задачей — не знаю. Я не пробовал это в деле. Запомнил как пример самой яркой дичи в JS для продакшна, что мне только попадалась.
khim
Вообще-то тут речь идёт не о «мутабельном коде», а о «мутабельных переменных».
Работать с которыми сложнее, отлаживать сложнее и понимать сложнее.
Но при большом желании всё это делается и на C++ (через шаблоны) и на C#/Java (через reflection)
faiwer
Не очень понял вас. Возможно мы говорим о разных вещах. Я веду речь как раз о коде с псевдо-мутациями псевдо-переменных.
Druu
Почему бы тогда просто не писать просто мутабельный код, который будет просто мутабельно работать?
Смысл иммутабельности именно в том, что код пишется как иммутабельный. Аналогично, если код выглядит как иммутабельно, но по факту является мутабельным — это тоже ок (например в clean система типов позволяет так делать). А вот наоборот (по факту иммубательный код, который выглядит мутабельно) — это просто бессмыслица, попытка взять худшее из двух миров.
faiwer
Что? Я вас не понимаю. Иммутабельность нужна для… иммутабельности? Что?
Смысл иммутабельности в заключается в ссылочной целостности. Пока ссылка на объект не изменилась вы можете быть уверенными в том, что и содержимое не изменилось. А это открывает двери ко множеству оптимизаций. Плюс этот подход заставляет писать значительную часть кодовой базы в виде чистых функций, что повышает предсказуемость, тестируемость и багоустойчивость системы в целом.
Druu
Я говорю, не важно как у вас что работает ВНУТРИ. Пусть оно хоть миллион раз будет мутабельно — это не важно. Иммутабельность ценна именно интерфейсом — тем, как мы работаем с данными.
И если мы работаем с данными мутабельно, то никакого толка от того, что где-то внутри там что-то иммутабельно — нет, все профиты сразу исчезают. С другой тсороны, если у нас интерфейс выглядит как иммутабельный, хотя внутренняя реализация мутабельна — мы все профиты иммутабельности имеем. С-но это и есть идеальный вариант "на двух стульях" — пишем иммутабельный код, который по факту мутабельно работает. Попытка же сделать наоборот — это попытка отобрать худшее.
mayorovp
Вы забываете, что интерфейсами можно не только пользоваться, но и реализовывать их.
У редьюсеров иммутабельный интерфейс — он требуется редуксом. А вот внутри редьюсера можно уже писать как угодно — при условии сохранения интерфейса.
Druu
И к чему вы это?
Только стейт не редьюсеры меняют, а диспатч, который мутабельный.
faiwer
В смысле не важно, как оно работает внутри? Что вы такое говорите? Как может быть не важным то, что по факту исполняется? Это как раз в первую очередь важно.
Затем, как вы можете потерять "профиты" от иммутабельности, если под капотом работает иммутабельность. Куда они денутся? Если вместо мутации объектов вы используете новые, то ссылочная целостность никуда не девается, pure-функций не становятся impure-функциями.
Ну и касательно — писать иммутабельный код, который на самом деле под капотом мутабельный… Ух. Я себе если честно даже представить себе не могу, что это. И уж тем более не могу представить зачем.
Я вас решительно не понимаю. И кажется не только я )
aikixd
Человек говорит не о ключевом слове interface, а о понятии интерфейс. Если у вас есть мутабельная переменная, но кто-то непозволяет вам ее изменить ее можно расценивать как иммутабельную.
F# в пример. Под капотом, там все мутабельное, как завещала CLR. Иммутабелным все делает язык (интерфейс к коду).
Druu
В том и смысле, что не важно.
Конечно, на перформанс влияет. Но лучше если внутри оно мутабельно, т.к. тогда оно быстрее. Изменить текущий объект быстрее, чем создать новый.
А их и изначально нет. Вы же пишете код, который выглядит как мутабельный. Откуда тут профиты?
Выглядит оно точности так же как и обычный иммутабельный код. То есть создаете новый объект, передаете его куда-то дальше и т.д.
Просто работает это быстро т.к. по факту вместо того чтобы создать новый объект рантайм изменит старый.
За тем, что имеются все плюсы иммутабельности, и при этом работает быстро.
Так это под капотом где-то они новые. Вам-то какая разница? С точки зрения вашего кода они как старые.
faiwer
Druu, я думаю вам стоит ознакомиться с документацией Redux поближе. И возможно с документацией к React.pureComputed | React.memo. На случай если вам вообще интересны эти библиотеки и вы планируете использовать их на деле.
P.S. я это не с вами мы полгода назад вели очень странный спор про бизнес-логику и асинхронные "экшоны"?
Chamie
Про «экшоны», наверное, я писал.
VolCh
Мне всё понятно. Пишите чистую функцию, внутри которой используете мутабельные переменные, хотя бы классический императивный
for (i=0; i<param.length; i++;
а не функциональные map, reduce, etc. и получаете код, который снаружи выглядит как иммутабельный функциональный (чистая функция), а внутри сплошная мутабельная императивщина.mayorovp
… а потом приходит Druu, и утверждает, что ваш код выглядит как мутабельный, а значит что чистота вашей функции не важна и ни на что не влияет.
Druu
Нет, ситуация немного другая, например есть ф-я setArrValue: (array, index, value) -> array, которая принимает массив, индекс элемента и новое значение и возвращает новый массив в котором значение по этому индексу изменено. Функция, очевидно, чистая. При этом вместо того, чтобы создавать новый массив, по факту меняется старый, но заметить мы это не можем, т.к. ссылки на старый массив не существует (это гарантируется на уровне типов). В итоге мы работаем с массивом имутабельно, но по факту все операции будут мутабельные, хоть мы и не можем заметить этот факт.
faiwer
Так вот вы о чём. Вы просто копнули уже на уровень интерпретатора\компилятора, а не велосипедов на уровне самого языка.
Я думал о таком ранее. Первое, что мне пришло в голову, так это, что чистые FP языки под капотом примерно как-то так и должны работать, отслеживая кол-во ссылок на уровне runtime. Но не уверен, что это используется на деле, т.к., мне кажется, тут много тонкостей может всплыть, и может оказаться, что выигрыш от переиспользования уже выделенной памяти будет испорчен потерями на проверки и отслеживание ссылок. Ведь перед мутацией неоходимо быть уверенным в том, что на старую константу более никто не ссылается, иначе всё поломается.
Druu
Это гарантируется на уровне системы типов, то есть если кто-то на старую константу ссылается — то такой код просто не будет скомпилирован из-за ошибки типов.
Гуглите clean и linear types, если интересно.
0xd34df00d
Вопрос в том, гарантирует ли мне язык, что вся эта императивщина не вылезет наружу (как ST тот же), или нет.
mayorovp
Толк — в интерфейсе. И в общем инварианте redux «ссылочная эквивалентность старого и нового значения равносильна отсутствию изменений».
Chamie
Кстати, не планируют ещё для immutable splice массивов сделать такой же spread-синтаксис, как для объектов?
Тогда бы весь этот код выродился во что-то типа
faiwer
Хрен редьки не слаще. Глаза от этих точек и скобок разбегаются :)
Chamie
Даже если переносы строк оставить?
Для изменения одного единственного значения, конечно, выглядит многословно. Но если и под это будут пилить сахар, то боюсь уже диабет получить.justboris
я бы вот так написал
Chamie
Зачем перебирать все элементы?
KostaArnorsky
nightwolf_du
Если вам нужен дизайн с подменой функций — в c#
someObj.existingFunction должен иметь тип Func или Action.
и вы спокойно сможете подменить функцию.
Либо делегат (delegate ), либо event (он всё равно подтип делегата). Всё это является указателями на функции и может быть переприсвоенно, либо в случае с event — на него можно подписаться-отписаться любым своим обработчикам.
Хотя всё это вы и сами знаете.
VolCh
Вот именно "должен иметь". Чтобы подменить реализацию нужно вносить изменения в исходный код объекта, если вдруг обычный метод был.
areht
«когда в руках молоток, все предметы начинают напоминать гвозди»
Это у вас пример решения. А зачем вы такое решение используете?
KostaArnorsky
Я такие решения не использую, хотя по молодости как-то переопределил WebForm_что-то что-бы какую-то хитрую логику прикрутить. Но это не отменяет факта, что JS позволяет творить невообразимые вещи.
areht
> JS позволяет творить невообразимые вещи.
Ок, записал в колонку «недостатки»
Free_ze
Понятие «предка» объекта в C# и JS разное. JS-объекты ссылаются на общего предка (который может ссылаться на своего предка и далее по цепочке), в то время как в C# объекты полноценно включают в себя экземпляры объектов всех родителей (отдельно для каждого ребенка).
Обеспечить похожую семантику (map-объектов и прототипного наследования) — это как раз не проблема.
KostaArnorsky
Если у вас это изначально запланировано, то конечно не проблема. В JS для этого ничего делать не нужно — берешь и меняешь prototype.
Free_ze
Естественно) Особый класс JS-ных проблемы с сопутствующими решениями для C# сначала должны как-то появиться.
Моей целью было показать, что такую же семантику обеспечить можно, это не убер-фича JS, которой нигде больше нет. А то, что фактически разный код будет работать по-разному — это достаточно очевидно на мой взгляд (=
IvanNochnoy
PsyHaSTe
Что если мы хотим подменить Count у стандартного класса
List<T>
? Просить майкрософт поменять свойство на делегат?IvanNochnoy
А не надо этого хотеть, подобный манки-патчинг нарушает принцип подстановки Лисков, если Вы это сделаете, то посыпятся все билиотеки, использующие
List<T>
. Для таких случаев есть интерфейсIList<T>
делайте с ним, что хотите. Только не надо говорить, что, де, есть сторонняя библиотка, которая используетList<T>
и я ничего не могу поделать. Раз автор хотел конкретный тип, то пусть его и получит. Раз MS не позволяют перегрузить Count, значит так и было задумано, ограничения как раз и нужны, чтобы нельзя было выстрелить себе в ногу.PsyHaSTe
Вы спросили что нельзя сделать в шарпах — вы получили ответ. Начать после этого мантру «вы не того хотите» немного некорркетно, не находите?
Не говоря про то, что это используется в тонне фич того же дотнета, начиная от всяких DynamicProxy (есть такой класс, реализует все интерфейсы. в WCF активно используется), и заканчивая всякими Moq/MS.Fakes/…
IvanNochnoy
1. Это не я спрашивал.
2. Я ответил на вопрос, как можно сделать запись с функциями в С#
3. Касаемо вопроса «Что если мы хотим подменить Count у стандартного класса List». Это ограничение .NET Framework. .NET является ООП-фреймворком и использовать там записи с функциями не приянято, вместо них используются интерфейсы. Так вот, авторы библиотеки намернно запретили изменять класс List. By design. И, да, в С# нельзя изменить то, что было запрещено. В этом весь смысл. А в JavaScript можно менять всё и везде. Если бы авторы .NET хотели бы этого, то все поля сделали бы публичными, а методы — виртуальными. Так что, да, в C# нельзя изменить, то, что запрещено, но в рамках своего собственного кода Вы свободны от ограничений.
PsyHaSTe
Вы ответили не на тот вопрос. По сути вопрос «как расширить или подменить объект или поведение объекта, когда автор не предусмотрел такой возможности». Код на C# делает совершенно не то, что код на JS.
faiwer
Может я зря вмешиваюсь, но если копать в эту сторону, то и в JS можно написать такой код, который хрен поменяешь. Например в замыкание вам не пролезть никак. Так иногда private методы и значения реализовывают.
IvanNochnoy
Дело в том, что в JS «расширить или подменить объект или поведение объекта, когда автор не предусмотрел такой возможности» тоже невозможно! Хотите знать как? Object.freeze() и фигу Вы там чего поменяте. Разница только в том, что в JS по-умолчанию разрешено всё, что не запрещено, а .NET наоборот. А это уже вопрос дизайна, а не возможностей.
PsyHaSTe
В этом случае вы переопределяете
Object.freeze
, чтобы он проигноировал заморозку свойства, которое вы хотите подменить. Profit.IvanNochnoy
Однако… Сильный ход, честно. Object.freeze, который нифига не фризит?! Мне неизвестна в С# такая простая возможность отключить базовую систему безопасности. Но это опять же by design.
Chamie
Фризит, но не всё.
areht
> Вы спросили что нельзя сделать в шарпах — вы получили ответ.
Мне кажется, это некорректный ответ. Ну как и, например, «чего нельзя сделать на JS — интерфейс».
Да, в языках разная механика внедрения изменений в поведение объектов. А ещё синтаксис разный.
PsyHaSTe
Смысл в том, что в шарпах вообще нет механики внедрения изменений, если автор их не предусмотрел. В JS — есть. Смысла спорить с этим я вообще не вижу, так языки были спроектированы.
На любом языке можно сэмулировать фичу любого другого языка, это не значит, что все они равнозначны.
areht
> Смысл в том, что в шарпах вообще нет механики внедрения изменений, если автор их не предусмотрел
Так у вас проблема с автором, я не с языком.
Понимаете, в 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#, но и это тоже некорректный ответ. Формально верный, но не вызывает ничего, кроме недоумения.
PsyHaSTe
Так можно про что угодно сказать. Чуваки оберон тут рекламировали, у них тоже язык прекрасный, а проблемы в разработчиках.
Я прекрасно понимаю. И даже согласен, мне не нравится анархия, которая дается таким образом.
Тем не менее, если такое понадобилось, то JS дает больше средств.
Я помню, как у нас люди работали с SharePoint, так там пришлось сборку декомпилировать и подменять в IL-коде константы, потому что майкрософт захардкодили там коннекшн стринги и имена своих таблиц, которые иначе нельзя было изменить. В итоге патчили хекс-редактором сборки, и загружали обратно, с красной пометкой "НИКОГДА НЕ ОБНОВЛЯТЬ И НЕ ЗАМЕНЯТЬ". Костыль, конечно, но вот один случай когда такое понадобилось у меня в коллекции есть.
areht
> Тем не менее, если такое понадобилось, то JS дает больше средств.
Так их не больше, они просто доступнее.
Ну то есть, основные кейсы по сложности мало отличаются, фактически только синтаксисом.
А когда проблема не в программировании, а в SharePoint — это точно проблемы с автором. Я в hex-редакторе драйвер принтера правил, который лишнюю команду слал. Он был на каких-нибудь сях написан, это был очень плохой язык программирования, там даже IL кода нет. Причем, на каком языке печатать ни пытайся — тот язык тоже плохим становится. Даже JS.
Так что я предлагаю ограничиться тем, что проблемы языка заканчиваются вашими (доступными вам для изменения) исходниками и BCL. А проблемы сторонних продуктов — уже нет.
IvanNochnoy
Больше свободы, я б сказал. Расскажу одну историю из реальной жизни. Дебажил я как-то чужой код на PHP два часа в состоянии «то ли я дурак, то ли лыжи не едут». Наконец понял, «где собака порылась». Оказывается, сторонний модуль находил в этом коде строку регулярными выражениями (!) и патчил ее на свой лад в памяти. В результате у меня в файле один код, а в памяти выполняется другой. Стоит ли говорить, что после этого я пошел, и напился?
В этом принципиальная проблема манки-патчинга. Допустим, есть Алиса из отдела биллинга и Боб из отдела доставки. Алиса получает объект, патчит его, передает в функцию, написанную Бобом. А в этой функции Боб тоже патчит этот объект, но уже на свой лад. Поведение системы становится непредсказуемым.
Ну сейчас кто-то скажет: пишите тесты, проводите митинги, делайте документацию. А зачем? если можно просто запретить такое самовольство?
То что JS дает больше средств, спору нет. Вопрос только в том, больше от этого пользы или вреда?
Meloman19
В общем-то можно подменить, но с некоторыми оговорками:
1) С дженериками без указания конкретного типа не сработает.
2) Придётся лезть в unsafe.
3) Оптимизация может загубить всю подмену, если в одном и тоже методе вызывать смену метода у типа и использовать этот тип. Я не спец по .NET, но видимо причина во встраивании.
Реализация уже существует: Harmony
А конкретно метод DetourMethod
PsyHaSTe
Да, это известные схемы, в частности была отличная статья на хабре с иллюстрациями (тыц). Но это все «чит» и обход C# хаками нижележащей системы CLR.
Meloman19
А как это реализовано в JS?
Весь «чит» тут в подмене ссылки на скомпиленный метод, если бы не защита памяти, то даже PInvoke не пришлось бы использовать и всё в пределах C# (хоть и в unsafe) можно было бы делать.
areht
А вообще рефлекшн — это C# или CLR?
Или typeof(System.Int32) — ещё C#, а typeof(System.Int32).Attributes — уже CLR?
ApeCoder
typeof это C# а System.Int32 это CLR :)
PsyHaSTe
К рефлекшену есть доступ из C#, а например хак `ICorJitCompiler` нужно писать на плюсах (на шарпах не сделать вообще), и подключать в шарпы уже сборку на них.
PsyHaSTe
Любую задачу можно сделать на любом языке, потому что в худшем случае сначала пишете на языке А интерпретатор В, а потом пишете оригинальную программу на В и все работает. Только это не та мощность, которая нас интересует.
GiAS
В Java при помощи рефлексии можно не только получить информацию о классе/методе/поле, но и изменить их
Quilin
Не знаю, какие вы примеры смотрели, на MSDN, например, все совершенно прозрачно — GetProperty(«DontDoIt»).GetValue(myObject);
Считается, что использование рефлексии должно быть явно оправдано, какой-то конкретной необходимостью. Процесс обнаружения типов, процесс их создания на лету, изменения и прочего всего такого несколько расходится с основными принципами статической типизации.
Как сказал товарищ Free_ze если вы пользуетесь таким функционалом, вы либо совершенно точно понимаете, что вы делаете, и иначе это не сделать (Mocks-библиотеки, IoC-контейнеры, AOP-фреймворки), либо вы претендуете на свой зал славы на говнокод.ру.
faiwer
Вы точно внимательно прочитали о чём мы с Free_ze говорили? Мне кажется, что нет. Вот GiAS похоже прочитал не по диагонали и упомянул суть ? в JAVA это не readonly.
Quilin
Я, конечно, напрямую не упомянул SetValue, но он там тоже есть. Если бы вы внимательно прочитали мой ответ, то увидели бы, что я привел примерами Mocks, IoC, AOP — далеко не readonly примеры. Все это в шарпе существует.
Если на то пошло, то рефлексия в статически типизированных языках в разы сильнее этой идеи «обратись к методу по строке его имени» — поэтому там и существуют пресловутые IoC-контейнеры и Mock-библиотеки. А в JS приходится довольствоваться spy(«myMethod») и иже с ним.
VolCh
В PHP, в слабо динамически типизируемом языке, есть и IoC- контейнеры, и Mock-библиотеки.
Quilin
Потому что есть рефлексия, которая появилась в PHP5-7 в рамках тенденции к переходу на типизацию и ООП.
VolCh
Типизация в PHP всегда была. Слабая динамическая. Есть тенденция к переходу на более строгую динамическую. Из 23 лет своей публичной истори 18 лет PHP живёт с ООП, с версии 4.
И без стандартной рефлексии вполне можно обойтись обычным кодом на PHP — это, по сути, не фича языка, а обычная библиотека.
Quilin
Окей, я, очевидно, ушел в другую степь. Я никоим разом не имел в виду, что в языках со слабой динамической типизацией есть какие-то проблемы с рефлексией. Я говорил, что данные проблемы есть в JS, и что хотя код вида
можно считать рефлексией, но вообще это крайне слабый и неудобный механизм. В то же время, во многих языках с сильной статической типизацией механизм рефлексии гораздо более функционален, пусть и не настолько прост в использовании. Например, в шарпах.
Пхп, конечно, тоже любопытный язык, но тут шла речь о другом все-таки.
faiwer
А можно на примере, в чём заключается слабость и неудобность.
Quilin
Слабость в том, что нельзя определить, какие аргументы принимает функция и хотя бы их имена, раз уж тип нельзя по понятным причинам. Ещё слабость в том, что надо хардкодить названия в отсутствие nameof.
Неудобство в том что нельзя отличить методы от свойств без лишних телодвижений с typeof, hasOwnProperty и прочим там. Я никогда не видел, чтобы метод переопределяли в свойство, хотя это технически возможно. Да, я понимаю про функции высшего порядка, но на js в основном пишут не в фп, а в ооп стиле, где методы имеют особое значение.
Я уточню, на всякий пожарный, что это конечно не финальная версия рефлексия в жиеси, но пока что она выглядит примерно как промисы без async/await синтаксиса.
faiwer
Очень странные претензии, честно говоря. Скажем имена параметров. Вы же всё равно никак не сможете ими воспользоваться, т.к. именованных параметров функций в языке просто нет. А чего нет, того нет. При очень большом желании
toString()
вам в помощь. Но учитывая минификации, не рекомендую. А для получения количества аргументов есть.length
.По поводу нельзя отличить "методы" (функции!) от свойств без "typeof" — а в чём проблема то? И почему typeof лишний. Ничего не понимаю. И да, функции заменят на свойства (другие виды свойств), и наоборот. Код который проверяет тип свойства перед его применением — рядовой код. Скажем какой-нибудь фильтр может быть задан строкой (привет lodash), RegExp-ом, объектом или методом. Если строкой, то по ней создаётся метод. Ну просто сплошь и рядом в JS такое. Как вы могли этого никогда не видеть. Удобно же. В нединамических языках это вроде называется "полиморфизм", а у нас утиная типизация. Она распространяется не только на параметры функций, но и на объекты с их полями.
В JS методы это first-class-citizen. Т.е. просто разновидность значения. И работают с ними, соответственно точно также. Я так понимаю, вы их куда-то отдельно выносите, и хотите для них отдельного API? Ну так он в пару строк пишется.
По сути я так и не понял, чем "рефлексия" в JS отстаёт. Что такое есть в самом языке, для чего нам не хватает штатных возможностей по рефлексии.
Ну если совсем уж придираться, то кажется нет нормальной возможности отличить async функций от обычных без обращения к toString(). А это костыль. Но этому и объяснение есть, ведь не-async функция может вести себя точно также, как и async-функция.
Ещё, если придираться, то наверное, какие-нибудь грабли с Proxy есть. Но в эту сторону я не копал.
Возможно я вас плохо понял. Поэтому попросил реальный пример. Вот есть задача, как её решить.
KostaArnorsky
В JS так нельзя, что, конечно, минус, но довольно слабый аргумент. Остальные примеры мимо кассы. Действительно мощный концепт, который вряд ли в JS появиться, это Expression. Но это заменяется псевдоязыками и транспайлероми.
faiwer
Попробовал бегло разобраться с тем, что такое nameof в c# и только запутался. Судя по всему примерам, если написать не
nameof(anything)
, а просто"anything"
, то получится тоже самое. Но я не думаю, что такой механизм кому-нибудь мог бы понадобиться. Что оно делает на самом деле? Результатом это штуки можно управлять? Можно написатьnameof(a)
и получить не"a"
?Expression штука забавная. Но оно наверное и правда в JS не появится из-за того, что есть
eval
и его родной братnew Function
. Правда на фоне Expression это более костыльный подход. Неспроста он зовётсяeval
.KostaArnorsky
В C# повсеместно применяется авторефакторниг, и после переименования anything в something вам не нужно вручную менять везде «anything».
Expression это не просто исполнение выражений, вы можете написать на C#, с проверкой типов и прочим, а на выходе получить… да хоть JS, можете склеить несколько выражений и т.д., при должной сноровке инструмент невообразимо мощный.
P.S.: как вы nameof(anything), eval и т.п. форматируете?
faiwer
Да, в таком разрезе это выглядит полезным. Интересная штука. Обе из них.
VolCh
Наверное, всё же, не в С#, а в заточенных под него IDE. В каком-нибудь vim без полноценной поддержки языка вы вряд ли получите те же гарантии правильности рефакторинга, которые есть в средах, заточенных под C#, а особенно использующих его родной анализатор (не знаю, возможно ли это, но со стороны MS было бы логично использовать "кишки" компилятора C# в VS)
mayorovp
Это да, но компилятор все равно проверит что указанный в nameof идентификатор существует.
faiwer
Ну т.е. править структуры можно и на лету. Это другое дело. И да, я не телепат )
aikixd
Необязательно. Если весь процесс инкапсулирован и прозрачен, то никаких расхождений нет. Я писал имплементацию записей на шарпе и все методы записей были сгенерированы. Поскольку весь процесс закрыт, реализация была неубиваема. Это примерно как небезопасный код. Его испльзование не делает вашу программу багованой, просто подходить к вопросу надо ответсвенно.
Quilin
Согласен. Это отличный способ отстрелить себе ногу и должен применяться с осторожностью даже в строго типизированных языках. А в JS за такое надо как-то наказывать и желательно жестоко.
IvanNochnoy
Все, что угодно? Карринга (в языке) нет, паттерн-матчинга нет, перегрузки операторов нет, комбинирования функций нет, рефлекшена нет (так как пользовательских типов нет). Все что есть, это объекты, которые по-сути являются хэш-таблицами, никогда нельзя знать заранее, что находится в этом объекте.
faiwer
Вы про синтаксический сахар или про возможности языка? Что из того, что вы перечислили нельзя реализовать в коде, Кроме перегрузки операторов (про неё я как раз упомянул). Про рефлексию смешно, да. Шампунь для лысых.
Если вы всё же про сахар, то есть куда расти. Сейчас обсуждается очень много "вкусных" вещей, которые нас ждут ближайшие год-два. Правда опять будет очень много криков про кривую обучения и перегруженность языка сахаром.
IvanNochnoy
Сахар имеет значение. Ни один психически здровый человек не будет определять cвойство так:
Поэтому в JS, до появления классов, никто не использовал свойства, только поля. Та же ситуация с мега-гибкостью. Гибкость есть, как у любого языка полного по Тьюрингу. Но пока карринг будет эмулироваться на уровне библиотек, никто его пользовать не будет, кроме пары фанатов. Просто потому что это выглядит вырвиглазно.
faiwer
С такой формулировкой соглашусь. Я сам тот ещё "сладкоежка". Согласен, что в JS есть не все виды сахара, которые доселе были придуманы. Но за последние 5 лет их добавили столько, что спецификация языка, наверное, выросла раза в 4. И на подходе новые и новые. Уж кому-кому а JS разработчикам на острый дефицит сахара жаловаться не приходится. Я вот с нетерпением жду pipe-оператора.
dimaaan
Ну вообщем-то есть один плюс: компиляторы/интерпретаторы для ДЯП писать гораздо легче :)
eugenk
Блин, то же самое написал! Честное пионерское, когда писал, Вашего коммента ещё не видел! :))))
demonarxs1
У вас интересные доводы, я отчасти с вами не согласе, может потому что из фронтенда. Хотелось бы увидеть мир вашими глазами, так сказать. Поэтому попрошу, можете посоветовать книги по ооп и статике. Что можно поизучать и посмотреть.
nexus478
Если хочется попробовать со статикой поработать, то вам надо в первую очередь выбрать язык со статической типизацией, а учитывая ваш опыт фронтенда — то попробуйте написать десктоп или мобильное приложение, заодно посмотрите как с помощью статически типизированных языков разрабатывают интерфейсы. Например, можете взять C# + WPF.
Из книг можете взять следующие:
1. Грэди Буч «Объектно-ориентированный анализ и проектирование с примерами приложений».
2. Роберт Мартин «Принципы, паттерны и методики гибкой разработки на языке C#» — там описываются принципы SOLID, которые очень важны в ООП (особенно Inversion of Control, IoC).
3. Сергей Тепляков «Паттерны проектирования на платформе .NET» — описание классических паттернов банды четырех с использованием C# + SOLID. На мой взгляд, эта книга написана поинтереснее, чем у GoF.
Кроме того, если выберете определенный язык, то в книгах и мануалах по этому языку всегда будет описание того, как работать с типами, наследованием, полиморфизмом.
max1gu
Языки с динамической типизацией — суть исключительно скриптовые.
Динамическая типизация несет социальныую функцию — привлечение к программированию непрограммистов. С ростом сложности задач каждый сам приходит к статической типизации.
gBear
Серьезно?! Какой-нибудь erlang смотрит на вас с легким недоумением :-)
khim
А откуда он на нас смотрит? Из какого проекта? Просто интересно…
wlr398
WhatsApp к примеру. Ну и ejabberd соответственно.
Эрланг в телекоме имеет место, там же и был придуман изначально.
khim
При всей популярности WhatsApp… Он скорее по разряду «привлечение к программированию непрограммистов». Потому что идея использовать номера телефонов вместо ников — это круто, это они молодцы, а вот то, что оно потом не может ни жить на двух устройствах, ни даже бекап сделать инкрементальный… как раз указывает на то, что с идеями — у них хорошо… с программистами плохо.
wlr398
Использование номера телефона к языку программирования не имеет отношения. Да и как видим, полтора миллиарда пользователей использование номера устраивает.
То есть перечисленные претензии к вотсапп это личные проблемы очень маленького количества гиков. Не нравится, не используй.
Goodkat
Goodkat
x67
<сарказм> Оу, то есть это не ограничение эрланга?? </сарказм>
Grox
Есть большая разница между «не может» и «не хочет».
khim
Приложение — именно «не может». А уж какие там высокие идеи у них были, из-за которых пользователям мучиться приходится — не так важно.
mayorovp
Но язык тут ни при чем.
Grox
khim
А это это всё сотворил? Марсиане?
gBear
Про телеком вам уже намекнули.
Из того, что «ближе»… rabbitMQ или какой-нибудь couchDB, например.
gatoazul
RabbitMQ
gatoazul
Есть сложные задачи, в которых нет однозначного алгоритма, а его еще надо найти. Писать такие на статических языках — ад.
ser-mk
Было бы интересно взглянуть на пример такой проблемы.
gatoazul
Например, поиск плагиата. Я решал его, когда это еще не было мейнстримом.
PsyHaSTe
Полагаю, штуки вроде плагинной архитектуры на динамических языках по естественным причинам работают удобнее. Я в свое время с MEF намучился, полагаю, на каком-нибудь жс/тс было бы сильно проще.
eugenk
Честно признаюсь, ни разу не замечал. Хотя занимаюсь подобными достаточно часто.
San_tit
А по моему опыту, такие задачи как раз таки решать проще с статической типизацией: набросал тип, а дальше с ним работаешь. Если нужно добавить обработку — добавил, нужно новое Поле — добавил. Никакого геморроя из разряда "договориться с коллегой об интерфейсах", все отлично читается из кода (аннотации и комментарии — костыли. Тип — самодостаточен).
gatoazul
Набросал тип. Обвесил типизацией. Не работает. Выкидываешь и начинаешь все заново.
mayorovp
У вас странные представления о том как придумываются типы данных.
kagetoki
Если у вас нет однозначного алгоритма в голове до того, как вы начали имплементацию, вам не поможет ни динамическая, ни статическая типизация, вы уже обречены.
Динамическая типизация просто делает этот фейл менее очевидным и откладывает осознание проблемы. Если вы не знаете, что именно вы хотите сделать, и как именно оно должно потом работать, вы в конце получите невнятное говнецо, которое вроде че-то делает, но все время как-то немного не так и вообще кривовато.
gatoazul
У вас есть алгоритм. Вы его делаете, со всей типизацией. Он не работает или издержки неприемлемы. Вы его выкидываете и начинаете заново.
И так раз пять.
mayorovp
У вас странные представления о том как пишутся алгоритмы.
gatoazul
У вас странные представления о моих представлениях
rjhdby
Похоже тут обычная несогласованность терминологии. Насколько я понимаю, речь про некую исследовательскую работу.
Т.е., утрировано, дано «Абляция», а в результате надо получить «Прецессия».
1. Придумываем «как решать»
2. Реализуем
3. Что-то не устраивает — скорость, качество, потребление памяти, удобство использования, косяки с граничными случаями, вообще не работает, etc.
4. goto 1
И, как мне кажется, в таких случаях таки да — проще прототипировать на объектах JS или ассоциативных массивах PHP, чем каждый раз переделывать статически типизированную модель данных.
kagetoki
В описанном сценарии типизация не виновата никак вообще. Возможно, алгоритм херовый, возможно, у постановщика задачи в голове моча и он не знает, что требовать и как продукт будет развиваться, возможно, программист херово знает язык, возможно задача в принципе не решаема. Ни динамическая, ни статическая типизация к этому отношения не имеют.
О каких издержках кстати речь? Что код тормозит? Или процесс разработки слишком долгий? Или памяти много потребляется?
gatoazul
Статические аннотации требуют лишней работы. Если программа не дает результата и требует переделки, вся эта работа становится лишней. По-моему, это очевидно.
Банальный пример. Напишите с нуля систему обработки естественного языка при ограничениях на скорость и память.
Чем вам поможет типизация?
Введете тип Text и его метод Process?
Введете типы String и Word? А нужны ли они? И если нужны, то как должны быть реализованы? А если реализация оказалась неудачной, но просто заменить ее внутренности, не затронув верхние слои, не получается?
0xf0a00
Введу, я еще и тип «предложение» введу, который будет массивом указателей на свои «слова». :)
Типизация мне поможет выстроить иерархию объектов, написать короткие и безопасные методы, поможет избежать мне ошибки «не туда».
А еще как очень правильно написали выше — типизация структурирует и приводит в порядок мысли.
Druu
Вы так говорите, как будто конкретно написание кода занимает сколько-нибудь существенный процент времени.
eugenk
Таварисчь наверно имел в виду ситуацию, когда задача сильно изменчива. Тогда возможно и да. Типонезависимые синтаксические формы высокого уровня возможно лучше будут выглядеть на чём-то динамическом. Хотя я правда для для подобной задачи (моделирование алгоритма БПФ Кули-Тюки с целью его конвейерезации и запихивания в FPGA) успешно юзал С++. Кстати это был вообще первый случай, когда добровольно и сознательно заюзал классы, до того писал в чисто процедурном стиле. А ведь есть ещё современные языки с выводом типов, такие как Rust! На чем-то подобном вообще всё должно ложиться мягко и пушисто…
eugenk
Уже несколько раз здесь писал, повторю ещё раз. Готов был бы в это поверить, если бы не сложность библиотек, написанных на js, питоне и прочем динамическом. Это говорит о том, что скорее мы, сторонники статической типизации чего-то не понимаем.
khim
Посмотрите лучше не на сложность библиотек, а на сложность решаемых с их помощью задач. Ну вот, хотя бы, пресловутый новый дизайн GMail: огромное количество кода, «сложнейшие» библиотеки… и что в результате?
Вот чего всё эта бурная деятельность породила? Более скруглённые уголки? Сравните, хотя бы, с Eclipse или Intellij Idea… ну или функциональность Google Docs сравните с MS Office 95 (и то и то — 12 лет с первой версии)?
eugenk
Ээээ батенька… Это Вы ещё яндекс-почты не видели! Вот где жопа так жопа… У меня почта на яндексе с 2004-го года, постепенно дозреваю, чтобы от неё отказаться. Ноут хоть и не самый современный, но и далеко не слабый. Тормозит так, что материться хочется! Впрочем думается это скорее проблема не js, а говнокодеров. И скорее всего она одна и та же что для гугла, что для яндекса. Смешно. Помню как повсеместно отказывались от флеша (мне тогда было очень грустно ибо AS3 я очень любил и часто юзал как язык моделирования перед реализацией на С или в FPGA). Одной из претензий было что говнокодеры заполонили все тырнеты своими говнобаннерами, и в результате нормальному юзеру стало невозможно серфить. Ну хорошо. Отказались. Сейчас та же жопа, но только с js. Проблема в ЧЕЛОВЕКЕ, а не в технологии…
Абсолютно согласен. Но тут проблема того же порядка. Когда из-за какой-то мало кому нужной фичи прикладывают титанические усилия, и в результате как Офис 95 тормозил на 486DX, так теперь Google Docs тормозит на i5. Нет, вру. Сейчас всё круто! Ибо тормозит в онлайне, в броузере! Прогресс твою мать! :)))
А вообще мне кажется человечеству давным-давно пора несколько притормозить с онлайном, гаджетами и прочей информационщиной. И заняться чем-то скучным и неинтересным. Например колонизацией океана. Или космоса. Иначе боюсь добром это не кончится. Живём всё-таки в реальном мире…
Free_ze
Парочка из крупнейших IT-компаний не могут найти «не говнокодеров», но к JS это не имеет отношения, конечно же.
faiwer
Было уже несколько статей объясняющих тормоза gmail. И там всё объяснялось не уровнем разработчиков, а системой поощрения и мотивации. В общем менеджментом. Пожалуй JS к этому отношения и правда не имеет. Не исключено, что на стороне backend-а там тоже "всё плохо", но это покрывают железом.
khim
faiwer
Ну так и в ruby\python\php нет ежегодной революции. Дело не в дин. типизации. Это на JS навалилось так много народу и новых задач за относительно короткое время. По сути была пустыня с кактусом, пришёл прораб и сказал — здесь должен быть город на 40 миллионов человек, роща и огромное озеро, и заверте… Как тут без революций.
eugenk
Вам уже ответили, причем на мой тупой и неоригинальный дилетантский взгляд — очень логично. Добавлю от себя. В веб-дизайне (что клиентском что серверном) на мой взгляд процент говнокодеров вообще сильно выше среднего по IT. Только это особенность не js или php, а рынка труда. Просто вакансий по вебу приходится наверно сотня на одну про мои любимые FPGA. И куда идёт человек, полгода назад решивший стать программистом? Правильно! В веб-прогерство. Потому куча народу постоянно пробует в этом свои силы. В том числе народа совершенно левого, не пригодного для программирования вообще. С другой стороны народ, почувствовавший к IT вкус, из веба вобщем-то рассасывается. Ибо задачи там обычно не сильно творческие. Я например люблю веб-программирование только из-за его «отзывчивости». Я могу сделать проект, который увидят миллионы людей и я могу вступить с ними в какие-то отношения. Но всё это только потому, что пишу я исключительно для себя (ну хорошо, признаюсь, что собираюсь кое-что из своих поделок продавать). Работать же предпочитаю в совершенно другой области.
Free_ze
Эти компании имеют достаточно ресурсов, чтобы отсеивать кандидатов при малейших сомнениях, поэтому я не думаю, что проблема избытка проходимцев там стоит настолько остро.
Другое дело, что JS своим дизайном не способствует росту скилла «рабочей командной лошадки». Получаются именно «ниндзи», творцы с полетом мысли, когда код пишется быстрее, чем обдумывается ТЗ (как тут уже неплохо подметили), которым путь в другие области и языки уже практически заказан (ибо слишком много шаблонов придется рвать, а тут уже неплохо кормят).
kozar
В Google Docs реализовано одновременное редактирование документов несколькими пользователями в почти реальном времени по сети.
Ничего подобного не было ни в Office 95, ни даже 2007. По-моему, сложность реализации этого не меньше, чем форматирования ячеек в таблице.
khim
Проблема в том, что эта фича как раз была реализована ещё 12 лет назад — и сделали её за полгода (или, может, даже меньше). И за прошедшее время весь «прогресс» не привёл ни к появлению стилей, ни к поддержке 3D-чартов, ни к чему-либо ещё из полезных фич, про которые бывшие пользователи MS Office спрашивают.
Зато не то два, не то три раза изменили весь дизайн и переписали код с нуля, неизмеримое число раз «игрались с цветами» и прочее.
kozar
Функционал действительно очень ограниченный и не особенно развивается, но нельзя говорить, что collaboration за 12 лет не менялся. Еще когда приходилось им активно по рабьоте пользоваться (лет 5 назад), я замечал, что стабильность и корректность мерджа изменений несколько раз заметно улучшалась. Что сейчас, тяжело сказать. Кроме «неопознанных капибар» ничего не заметил.
diseaz
Так Gmail-то как раз — про статическую типизацию. Исходно там были всякие Java+GWT и Closure со включённой строгой типизацией при компиляции. Финальный JS, который грузится на клиент — машиногенерённый, как байткод Java, который, к слову, тоже не особо statically typed со своими дженериками через object.
Free_ze
Вы так говорите, будто из-за дженериков там пропадает вся остальная типизация. Я не эксперт в этой области, но говорят, что там рефлекшном вытаскивается типовая информация даже из «затертых» объектов.
diseaz
Вытащить информацию о типе можно и в питоне, например. Питон — строго типизииованный, но не статически. К слову, в стиле duck typing можно херачить на, наверное, любом языке с достаточно мощным reflection'ом, например на Go.
PsyHaSTe
Ничего вы не вытащите.
Причем, все настолько плохо, что по сути вы можете с этим столкнуться и во время компиляции (А не только в рантайме):
khim
Я сейчас беру GMail не в качестве «динамического языка», а в качестве «бурного развития JS». Потому что бурную деятельность я вижу. А вот развития — нет. Есть попытка как-то решить проблему изначально ужасного дизайна наваливаением многих уровней костылей поверх всего того, что уже было порождено за долгие годы. Причём в результате ничего подобному тому, что призошло при переходе, скажем, Turbo Pascal CRT => Turbo Pascal Turbo Vision => Delphi (когда на каждом этапе количество библиотек возрастало, но зато количество кода, который нужно писать руками для получения сравнимого результата падало) я не наблюдаю.
Наоборот — чем дальше в лес, тем более сложными становятся решения для тех же задач.
По мне — так это скорее регресс, а не прогресс…
diseaz
Значит Gmail — плохой пример в данном конкретном случае. Ибо бурная деятельность тут происходит не на уровне JS, а как раз на тех уровнях, где статическая типизация. И JS мог развиться до любых высот, но есть груз легаси в инструментах компиляции и потребность запускаться на legacy-браузерах.
faiwer
Ну дык кол-во кода для написания одного и того же на портянке jquery+велосипеды или, скажем, backbone+any_template_engine радикально упало. В разы. А сам код стал проще. Местами даже с элементами FP.
Дык почему же? Наоборот. Во frontend эти задачи даже и не пытались решать. Даже само слово frontend стало популярным лишь недавно.
Или вы всецело имеете ввиду? Ну я думаю со времён изобретения MVVP и vDom люди только и делают, что экспериментируют с уже известными подходами и технологиями, пытаясь найти что-то оптимальное. Скажем команда React в одном из новых релизов обещает COMMIT (прямая работа с DOM) фазу разбить на подзадачи с использованием iddleCallbacks. Что в этом плохого?
Фреймворки двигаются в сторону ускорения разработки, уменьшения времени исполнения и лучшей поддержки инструментов отладки. Я вижу один прогресс.
Не понимаю вашего пессимизма. Есть кривой и косой gmail. Это следствие менеджмента в Google. Можно его же сделать быстрым как метеор. Благо технологии позволяют. Подходы все известны. Никакой магии нетребуется. Но надо чтобы кто-то взялся за это. Но ведь премию и повышения дают только за редизайны.
PsyHaSTe
Меня например лично печалит, что когда мне поставили задачу "определить, есть ли на страницу google pixel", мне пришлось селениум в проект затаскивать, просто чтобы понять, есть ли некоторая подстрока на странице. А все потому, что я не могу просто сделать
GET
урла и поискать текст, ведь всё, что я там найду это<html><body><script src='app.js' /></body></html>
.faiwer
Ну так себе проблема, если честно. Вот поставят вам задачу, определить используется ли библиотека X в бинарном приложении Y, там никакой Selenium вам уже не поможет. Хорошо если исходник найдёте. Ну или есть навыки джедая в дизасемблерах. А если нет? :)
PsyHaSTe
Да ничего страшного, достаточно просто открыть файл на диске и поискать имя библиотеки. Где-то в импортах оно будет светиться.
ДА нет, нормальная проблема. Раньше можно было вызвать GET и получить страницу. А теперь ты вызываешь GET и получаешь непойми что. Семантика HTTP как по мне поломана на прочь. Тебе нужен целый рантайм и куча несвязанных вещей чтобы появился контент. Не даром в HTTP урлы называются «ресурсами», на которых хранится контент. А сейчас это тупо заглушки, в которых нифига нет.
faiwer
Да её вообще уже к лешему послали. Как и почти все остальные устои. Тут где-то что-то мелькало, что мол Google от URL хочет отказаться, а вы про гипертекстовый
фидоинтернет :)int03e
Веб перестал быть «набором html файлов», и это естественная эволюция. Семантика нифига не поломана, сделайте GET/POST запрос к любому API и получите предсказуемый результат в формате JSON/XML.
PsyHaSTe
Да, но любой запрос (до недавнего времени) содержал полезные данные.
Теперь же запрос страницы отдает заглушку-лоадер, который и будет загружать непосредственно страницу.
В общем, как я уже сказал, мне как бек-разработчику это добавило очень много геморроя и небходимость тащить браузер в приложение, где его вообще быть по идее не должно.
VolCh
Это не техническая заглушка типа редиректа, это полноценный html
PsyHaSTe
Да нет, заглушка и есть. Это не мешает ей быть валидным HTML-файлом, но контента в ней нет.
VolCh
В ней есть, как минимум, гиперссылка на исходный код приложения с указанием что этот код нужно исполнять. Практически принцип единственной ответственности во всей красе :)
VolCh
Кое-где она поломана, да, но общий подход по GET / получить html-страницу-заглушку не ломает семантику. Это вполне себе ресурс — A network data object or service that can be identified by a URI. Про какой-то определ'нній вид контента речи нет.
0xd34df00d
Особенно если оно заинлайнится в каких-нибудь там плюсах.
PsyHaSTe
Как подгрузить библиотеку, не зная её имени? Это было бы странно.
0xd34df00d
Я что-то потерял нить.
Зачем её подгружать в рантайме? Может, в компилтайме
#include
нули хедер?PsyHaSTe
Я привык к дотнету, там все зависимости исключительно динамически подгружаются :)
creker
Сравнение графов функций. Часто есть строки. Ну и инлайнится довольно редко. В общем, решаемо, если только использование библиотеки не ограничивается каким-нить leftpad из огромного фреймворка.
DelphiCowboy
А потом в результате в соседней теме спрашивают почему софт тормозит? habr.com/post/431282
PS похоже, сейчас, у многих от этой правды бомбанёт и мне сольют Карму.
.
.
.
.
.
.
.
.
.
.
.
Тормоза и отжирание памяти конечно породила!
eugenk
Ещё раз. Это не проблема js. Это проблема с одной стороны тупых говнокодеров, не думающих об оптимизации от слова совсем. С другой стороны текучки у фирм-разработчиков, когда кто-то начинает работать, потом увольняется оставив кучу говнокода, в котором толком никто не может разобраться. Затем приходит другой, нарастив сверху один слой точно такого же говнокода. Затем третий и т.д. В итоге несложная веб-страничка превращается в море жидкого дерьма. Которую по-хорошему надо бы переписать с нуля, но во-первых лень, во вторых жаба душит. В третьих это проблема человечества, слишком увлёкшегося IT и тратящего на это кучу усилий, абсолютно не соответствующую полезности этой деятельности. А когда коту нечем заняться, он как известно яйца лижет. Что мы и наблюдаем.
P.S. Минуснул не я. Честное пионерское! :)
impwx
С одной стороны имеем язык, спроектированный совсем не для тех задач, для которых его применяют. С другой — недальновидный бизнес, желающий максимально сэкономить на каждом этапе. В итоге действительно получается порочный круг, где всех душит жаба, плохое решение все прочнее вмуровывается в фундамент, а общая ситуация напоминает баян про продажу 20 долларов за 200.
Вспомните, как появились V8, HipHop, KPHP и другие подобные проекты. Когда язык поощряет пресловутую «свободу самовыражения», писать костыльный код на нем становится легко и приятно. Говнокодеры радуются тому, что им теперь удобно работать, и вкладывают миллионы человеко-часов в решение прикладных задач. В итоге у бизнеса есть система, которая худо-бедно выполняет свою функцию, но переписать ее как следует уже экономически невозможно, и приходится изобретать умопомрачительно сложные способы оптимизации.
Crandel
> Это не проблема js.
Не соглашусь. Проблема исключительно в жс. В котором нету дисциплины и который поощряет быстро наваять на коленке. А если выстрелит, никто же не будет переписывать на другой язык, потому что браузер поддерживает только один язык. Вот отсюда все проблемы
Aingis
Позволяет ? поощряет. С большой силой приходит большая ответственность.
А если разработчик не может поддерживать дисциплину без внешних ограничений, то это весьма характерно говорит про разработчика.
impwx
Ого, вот это требования. «Настоящий» разработчик, видимо, должен держать весь проект в голове и писать сразу идеальный код?
PsyHaSTe
Ну да. Причем я часто слышал это как раз от С++ разработчиков, но там тоже с типизацией не всё хорошо, поэтому можете ознакомиться с их аргументацией, что "настоящий программист" должен уметь держать в голове. Думаю, будет довольно много пересечений с потенциальными ответами того же товарища Aingis.
0xd34df00d
Настоящий C++-программист должен держать весь C++ в голове, а это, к сожалению, куда сложнее :(
Aingis
А меньше, чем держать весь проект в голове, для вас уже не дисциплина что-ли?
impwx
Держать в голове то, что можно отдать компилятору на автоматическую проверку, вообще не нужно.
Chamie
«Проблема в JS» ? «проблема JS».
HTML тоже называли недостаточно строгим и хотели перейти на строгий XML. Не взлетело. С JS, думаю, если бы его попытались сделать «дисциплинирующим», вышло бы то же самое.
PsyHaSTe
Жалко, что не взлетело.
Если у кучи людей регулярно возникает проблема с Х, то проблема не в людях, а в Х.
Chamie
PsyHaSTe
Правильно.
Под кучей мы имеем в виду большинство, конечно же.
KvanTTT
Это должет быть не одномерный, а двухмерный: динамическая — статическая, слабая — строгая. И тогда JS будет в самом углу. Да и почему все заканчивается Хаскелем? Есть еще более строгие языки типа Idris, Agda.
nexus478
Потому что в том докладе были взяты топ 20 языков (учитывались такие рейтинги как TIOBE, PYPL, RedMonk (stackoverflow + gihub)). То есть это языки, которые наиболее часто обсуждают и используют.
(вообще говоря на слайде чуть больше 20 языков, потому что автор делал исключения и взял D, например)
eugenk
Я тоже. Вот поэтому-то мне и было бы крайне любопытно услышать серьёзные аргументы сторонников динамической типизации. То что они есть, я не сомневаюсь. Уж больно нетривиальные вещи пишут на том же js. К сожалению то что пока прочел от них в комментариях, увы детский лепет. Правда читал выборочно, сейчас начну сплошняком. Ну и очень надеюсь что подтянутся люди, серьёзно работающие на js.
khim
Я вижу огромное количество бурной деятельности и огромное количество… не хочу ругаться матом… но вот чего-то, где я мог бы понять — почему оно такое большое и сложное… не вижу. Вижу поделки, похожие на то, что в прошлом веке делались с помощью Visual Basic или Delphi — только с примерно в 100 раз большими требованиями к ресурсам и со в 100 раз большим количеством кода.
eugenk
Признаюсь абсолютно честно. Я тут особо не специалист. Я таварисчь в основном очень и очень низкоуровневый. FPGA, микроконтроллеры, реальное время и т.п. Сходу так приходит из того чем пользовался и что дико понравилось (а пожалуй даже где-то как-то удивило) это jsplumb (рисование графов) и d3.js (визуализация графиков и диаграмм).
Вот с этим остаётся только согласиться. Сейчас ваяю потихоньку некий чисто свой проектец. Пишу на ts. Так из всех библиотек у меня там только require.js и то только потому, что без неё непонятно как грузить то безобразие, которое ts собирает в единый файл. Даже jquery не использую. И rich text editor у меня свой (он довольно специфический). Глянул в своё время на angular, это просто ужос, ужос, ужос. Дерьмовина типа hello world тащит два с половиной мегабайта всякого говна! У меня весь проект (на данный момент) меньше 200 килобайт! Так что да, ощущение того что всё сделано через жопу, самое что ни на есть явственное. Но повторяю ещё раз, я человек из совершенно другой области. За веб-программированием наблюдаю с интересом и даже кое что пишу. Но оценить профессионально не в состоянии, для этого в этом надо жить. Но на мой тупой дилетантский взгляд со стороны, развивается всё очень и очень бурно. Хотя согласен, качество порой сильно оставляет желать…
defaultvoice
Подозреваю, что так же, как и в обычном js – динамические импорты. Судя по доке ts они ещё в 2.4 появились.
eugenk
Честно говоря не знаю как это называется, в конфиге задается генерация в виде atm-модулей. Раньше ts вообще всё лил в один файл по умолчанию, а теперь вон чтобы слилось в один и оттуда стартовало, приходится немного постучать в бубен. Давно на ts ничего не писал, с версии 1.8. А тут сразу третья…
mayorovp
А куда слияние в один файл пропало-то? Просто указываете
outFile
вместоoutDir
и наслаждаетесь жизнью же...eugenk
Он для этого требует тип модуля AMD и в таком виде и льёт. Простым способом стартануть оттуда при загрузке, у меня не получилось. Хотя честно говоря особо и не разбирался. Работает через require.js и ладно. Благо сама require.js всего 17 килобайт.
mayorovp
Хм, и правда. Но формат там довольно простой, можно было и без require.js обойтись
vintage
Вам возможно будет полезна MAM архитектура, где не нужно париться по поводу сборки и запуска — просто пишете код в соответствии с простыми и естественными соглашениями.
KostaArnorsky
Она очень удобна для быстро меняющегося фронта. Не для хардкорной бизнес-логики, которая в множестве переехала в браузер, а для UI, маппинга, viewmodel, раутинга, анимаций — все равно это все как правило долго не живет и постоянно переписывается, статическая типизация там только обуза и дополнительный источник мерж-конфликтов меняющегося boilerplate кода.
Я сам .NET дев, но при всей любви к статический сильной типизации на бэке, до появления анонимных типов и dynamic С# временами подбешивал необходимостью в очередной раз менять одноразовую фигню потому что-то переехало в другое место на странице, и количеством DTO.
eugenk
С одной стороны да, согласен. С другой несколько спорно. У меня пожалуй было два проекта, такого типа, о которых Вы говорите. Первый — некая хитрая плата сбора данных на FPGA. Чтобы не паять туда микроконтроллер, управляющий процессор я реализовал прямо в той же FPGA. Следующим шагом было написать для всего этого безобразия ассемблер. Но поскольку задача постоянно менялась и система команд под неё постоянно оптимизировалась, ассемблер я сделал таблично управляемым. Просто в некий файл в специальном формате забивалась текущая система команд и на основе неё компилился код.
Второй случай — когда я тяжело болел форексом, писал некую софтину для финансовой аналитики. Серверная часть писалась на С++ в виде некоего ядра и набора плагинов. А отображалось всё в броузере. Проблема была точно такая же, как в первом проекте с FPGA. Я не знал заранее, что и как я в следующий момент захочу изобразить. И решена была точно так же. Вместе с данными в броузер отправлялся некий заголовок в текстовом формате, их описывающий. И всё работало хотя и не на пять с плюсом, но на твердую четверку.
Мораль сей басни такова. В любых, самых изменчивых проектах, всегда есть некоторое относительно стабильное ядро. Скорее всего не совсем простое. Управляемое некими командами. Вот это-то ядро и имеет смысл реализовывать тщательно, не скупясь, на чем-то хорошо помогающем отлавливать ошибки (в частности статически типизированном). А все изменения выносить в некий управляющий этим ядром простой язык. Думаю (хотя могу ошибаться, ибо не в точности знаю Ваших задач) на Вашем месте я точно так бы и поступил. Хотя Вам конечно виднее, на истину я никак не претендую.
areht
Анонимные типы — это статические типы. А зачем dynamic в UI?
KostaArnorsky
Но анонимные типы их не нужно описывать.
НапримерЗавтра дизайнер скажет, что упячки больше не нужны, нужны уточки. Причем на одной странице, на второй оставить. Я такой подход не приветствую, но…
PsyHaSTe
Option<Upjachki>
чем не подходит?Зато не будет проблем с
Model.Upjacki
.Динамики вообще не нужны. В языки испокон веков был Hashtable (позже Dictionary), который делает все, что нужно. Никаких проблем вместо
foo.a.b
написатьfoo["a"]["b"]
. Зато исчезает куча проблем как с производительностью, так и с неявностью, что есть у объекта, что нет, где упасть может, а где нет.KostaArnorsky
Тем, что его нужно описать. Когда у вас сотни страниц и одноразовых DTO, начинает напрягать. Зачем Option, кстати? Явность foo[«a»] ничем не лучше foo.a, так же замечательно падает.
PsyHaSTe
И хорошо. Описано в одном месте, все инварианты проверены только на границе. Дальше можно безопасно пользоваться. Слышали про защитное программирование? Удобная концепция.
Кодогенерация в помощь. Ну и да, я люблю согласовать схему данных, а потом тыкать в неё коллег, которые её не соблюдают, когда вместо данных передана фигня и все ломается. Или вы предпочитаете делать за всех чужую работу, и вставлять костыли у себя?
Ничем не лучше, только вот во всей программе (кроме той, что работает непосредственно со входом) ничего падать не будет. В самом худшем случае упадет при десериализации, это значит, что на вход подсунули чушь, которую программа не ожидает. Она может по счастливой случайности смочь обработать и этот новый тип, но чаще ошибка вылезет где-нибудь в километре от того места, где реально произошла, и ищи потом ветра в поле.
KostaArnorsky
Я в целом с вами согласен, но весь этот boilerplate…
Хорошо бы иметь что-то типа @model returned by Foo.Bar, тип то фактически описан.
PsyHaSTe
Ну вон я на F# пример привел, как раз то, что нужно, по идее.
Druu
TS так умеет.
VolCh
Часто достаточно посмотреть на TS описание типов для простой вроде JS функции и преимущества будут видны.
DRVTiny
Язык Си создан в 70-е — и в нём нет никакого ООП.
Язык QuickBASIC (компилятор, не путать с интерпретатором QBASIC), созданный в середине 80-х, позволяет одной директивой заставить разработчика декларировать типы всех переменных, и там тоже нет ООП.
При этом современный статически типизированный язык Crystal, например, который по задумке тотально ООП, не только в коде самого компилятора очень часто процедурный, но и позволяет без проблем писать в процедурном стиле.
При этом z = x + y в большинстве статически типизированных языков запросто приводит к неверному результату, и упомянутый тут язык Java долгое время таскал внутри себя баг в алгоритме бинарного поиска, когда результатом m = (l + r)/2 было число намного меньше l. Ибо Int склонен переполняться (впрочем, как и float, там просто разрядности мантиссы становится недостаточно), а проверить это разработчикам компиляторов не досуг.
При этом многие интерпретаторы динамически изменяют разрядность переменной при переполнении.
Я, собственно, к тому, что в статически типизированных языках нет ничего «современного», они появились вообще-то раньше динамических, в статически типизированных языках совершенно не обязательно использовать ООП (и совершенно не факт, что в динамически типизированном языке можно не использовать ООП), ну и да, в конечном итоге мне совершенно непонятно, причём тут вообще проектирование классов и статическая типизация.
P.S. И да, если убрать колоссальный пласт «неправильного» кода без ООП на том же языке Си — боюсь, что автору не пришлось бы написать свой пост, ибо нечем и некуда.
gatoazul
Добавлю, что Smalltalk — отец ООП — имел динамическую типизацию. Статическая типизация ООП а ля С++ — это более поздний костыль, призванный увеличить производительность за счет переноса связывания на время компиляции.
VolCh
Картинка не полна без разделения ещё и на строгую, и слабую.
nexus478
Неполна она потому что находится в контексте доклада, а там строгость/слабость не была важна.
dipiash
К сожалению, данные API в runtime TS мне никак без валидатора не поможет прочекать (в отличие от тех же нормальных статических языков). Консистентность кода в TS? ровно до того момента когда надоест бороться с быстро развивающимися библиотеками (а тайпинги для них отстают), придет манагер и скажет: «что-то долго» и т.п. => вы свалитесь к any.
Так что, давайте оставим статическое статическому, а динамическое динамическому и не будем пытаться сделать из одного другое и наоборот
Grox
any, в случае надобности, вы воткнёте для чужих библиотек, а для своих скорее всего обойдётесь без него.
Druu
Сейчас все больше и больше "быстро развивающихся библиотек" пишут сразу на ts.
evocatus
Тот способ мышления, который вы описываете, скорее можно назвать «data driven» и «bottom up», это не связано напрямую с типами.
Вот, например, отличная презентация с демонстрацией такого подхода к дизайну (что забавно — тоже на примере игры), притом человек пишет на Clojure — языке с динамической типизацией (но строгой иммутабельностью).
impwx
evocatus
Тут недавно на хабре была пара статей, где человек делал стековую машину на Haskell. Отрезвляет своей сложностью.
impwx
Реализовать интерпретатор с состоянием на платформе, которая ориентируется на отсутствие мутабельного состояния и побочных эффектов, да еще и эффективно — это сама по себе крайне сложная задача.
VolCh
Ну как нет? Нет необходимости описывать все типы. Гибкие системы типов обычно позволяют описать что угодно, но вот это описание может быть очень и очень громоздким, по сути write only.
impwx
Описание типов — это одновременно каркас и документация системы. Если даже в ней сложно разобраться, то как вы вообще предполагаете работать с такой системой без нее, в состоянии «черного ящика»?
Возможность не описывать некоторые типы в статически типизированных языках тоже бывает: например, в C# есть
dynamic
, в Typescript —any
. Но в хорошем коде это допустимо только в качестве локального исключения из правил, когда нужно вкрутить какой-нибудь незначительный костыль.Free_ze
В dynamic/C# не означает потерю информации о типе в рантайме, в отличие от any/TS.
mayorovp
Разве в TS информация о типе в рантайме как-то теряется?
Free_ze
Разумеется) TS со своими чудными типами живет только на этапе компиляции, потом он деградирует до JS.
А C# проверяет типы всю дорогу, dynamic лишь отключает проверку типов на этапе компиляции, а в рантайме они продолжают бросаться экепшнами, если что-то пошло не так.
VolCh
Иначе не было бы type guard'ов и то как костылей.
mayorovp
Напротив, тайп гуарды потому и могут существовать, что информация о типах в рантайме не теряется, и они ей способны пользоваться.
VolCh
Как я их понимаю, на этапе проверки типов компиляции, анализатор делает предположение, что он возвращает реальный результат о типе как instanceof.
Вся ответственность за саму проверку на программисте, он, например, анализирует наличие методов из интерфейса в объекте, чтобы "сохранить" в рантайме информацию о том, что объект реализует интерфейс. И если проверка не очень тщательная, тайпскрипт на этапе компиляции будет обманут.
mayorovp
Так ведь наличие методов потому и можно проверить, что информация о типе не потерялась, и ее можно вытащить через
Object.keys
,Object.getOwnPropertyNames
или операторin
...VolCh
Нет, о типе информация теряется, проверка наличия метода обычная операция для JS с его манкипатчингом, где метод может біть добавлен почти куда угодно. И это не информация о типе, это информация о наличии конкретного ключа в таблице.
mayorovp
А чем "информация о наличии конкретного ключа в таблице" концептуально отличается от "информации о типе" в контексте структурной типизации?
VolCh
Тем, что в языке нет средств проверки типа, есть средства проверки наличия ключа. А там уж от программиста зависит проверит, он, например, все методы интерфейса (про сигнатуры вообще молчу) или только одного, а может вообще проверять не будет.
mayorovp
А где я писал про «средства проверки типа»? Информация о типе — не то же самое, что средства проверки типа.
VolCh
А информация о структуре типа — не то же самое что информация о типе.
Druu
Мне кажется, прежде чем продолжать разговор вам следует уточнить, что вы все-таки подразумеваете под информацией о типе, и в каком смысле она теряется. Иначе ваши слова выглядят странно.
Anton23
Фраза в конце порадовала)).
justboris
Про нейронную сеть интересно. А она точно нейронная? На каких данных обучалась?
Я посмотрел на пару твитов, и это больше похоже на подготовленные человеком ответы:
Context in #reactjs is a crutch – никто так не пишет, в результатах гугла только этот самый твит. Больше похоже на русского программиста, который буквально перевел слово "костыль" на английский.
Есть твиты с картинками. Откуда нейронная сеть их возьмет? Поиск по картинкам в гугле ничего не дал, а в Яндексе находит только этот же самый твит. Получается, что ИИ сочинил текст, сделал его скриншот и вставил в твит? Слишком фантастично.
Получается, что если там и есть робот, то он постил твиты, которые для него заранее сочинили люди.
Alexey_Zal
Хабрачани, а почему все забывают, что динамическая типизация, практически, была всегда? В С++ всегда использовалась динамическое выделение памяти и в ней можно было творить что угодно. Главное не выйти за рамки и не забыть закрыть. Но её же не зря придумали? Просто нужно понимать когда притягивание за уши статический типизации переходит все рамки разумности — тогда используй принцип «Сделай сам». Хотя при таком подходе мой приятель обвиняет меня в раздвоении личности. Но это мой путь. Я всегда стараюсь использовать статическую типизация, но когда нужно передать, большой блок данных в другую подсистемы, то я могу и породить трёхэтажную не типизированную структуру. В разработке это, как правило, получается быстрее, да и адаптировать её при дальнейшем развитии проекта в неизвестном направлении — проще. Так что считаю что используя только статическую типизация вы себя искусственно ограничиваете. Хотя начинать свой жизненный путь, конечно, нужно со статический системы. Иначе — каша в голове обеспечена.
NeoCode
Это не динамическая типизация, а низвоуровневый доступ к памяти. Такой доступ нужно делать явно, и применяется он только в очень специальных ситуациях.
Alexey_Zal
В c++ специальные ситуации уже начинались при создании двухмерного массива… И конечно понятно, что это не динамическая типизация. Но подход то у меня в голове остался, я просто пытаюсь сказать, что в полной свободе от типов, в некоторых ситуациях, есть свои прелести.
NeoCode
Ну я тоже не сторонник абсолютно жестких систем типизации — когда между int и bool заставляют явно преобразовывать. Формально они правы конечно… но соглашение ноль==false, не ноль==true кажется вполне однозначным.
Вариант С/С++ (впрочем с некоторыми улучшениями — что-то я бы усилил, что-то ослабил) мне нравится больше всего (хотя наверное здесь есть фактор привычки).
areht
> но соглашение ноль==false, не ноль==true кажется вполне однозначным.
А зачем ноль преобразовывать в false? Откуда взялся этот ноль со значением false?
unsafePtr
В доисторические времени типа bool не существовало. Его симулировали на макросах брав за основу тип int. Любое число отличное от нуля было true, а если ноль то false.
areht
Ну да, но я так понял человек хочет сейчас зачем-то иметь такое преобразование на уровне языка.
VolCh
Удобно, многие проверки записываются короче типа `if (count)`, а не `if (count != 0)`. Ещё удобно преобразование null к false, особенно для nullable объектных типов
mayorovp
Ага, особенно "удобна" вот такая форма условного оператора:
if (count=0)
:-)areht
Это не «удобно», это типа сэкономили 3 символа (на самом деле нет).
А «удобно» — это когда при виде `if (count)` не возникает вопросов:
— Это проверка на «проинициализирован ли count»?
— А count вообще проинициализирован?
— А count где-то инкрементируется? Точно один раз?
Вот `if (items.Any())` — удобно. Ни переменной лишней не надо, ни следить за ней.
Это даже не вспоминая про удобство `if (count = 0)`…
VolCh
Это не просто экономия, это паттерн, распознаваемый с одного взгляда. При явном сравнении с нулем распознается паттерн сравнение со значением и только на втором шаге анализируется с каким значением.
areht
> Это не просто экономия, это паттерн, распознаваемый с одного взгляда.
Тяжёлое детство, лишенное витаминов, восьмибитные игрушки, паттерн, распознаваемый с одного взгляда…
> паттерн сравнение со значением
Только семантика у него не про сравнение, так ещё и потерю выразительности в удобство запишите.
KostaArnorsky
Вам распознаваемый, кому-то нет. Я оператор "!" снова начал использовать только из-за нытья других разработчиков, но где я могу задавать правила, все по прежнему пишут boolVal == false, потому что "!" маленький и незаметный.
PsyHaSTe
Сравнение в bool как по мне вообще атавизм из си, вызванный как раз этой проблемой, как и йода-стайл
if (5 == x)
PsyHaSTe
Удобнее было бы сделать скобки необязательными. В символах получается столько же, а читаемость возрастает на порядок.
Cerberuser
Явно преобразовывать между int и bool, говорите?
Ta-damm! Ошибка — за давностью лет уже и не вспомню, какая именно, но я, глядя на неё, решил попробовать явное приведение:В мою бытность студентом (шесть лет назад) у нас был компьютерный класс. Ubuntu на терминалах, первый урок — как перейти в консоль и выйти из случайно запущенного vi, код пишется в mcedit… Первые эксперименты (примитивная fork-бомба, завесившая намертво терминал), первые куски кода, про которые никто не понимает, как это работает, и прочие радости юного кодера.
Одной из этих радостей был gcc, настроенный лично завкафедрой. -Werror — наш лучший друг, в этом я убедился ещё тогда; особенно, когда количество активных предупреждений начинает потихоньку переваливать за рамки очевидного.
Писали мы какую-то программу для работы с массивами. Уж не помню, что такое требовалось, но помню, что решил поместить индексы к массиву во второй такой же массив (типа double). Никакой арифметики с ними не производилось — только перестановки. В конце же, соответственно, требовалось взять элемент первого массива, индекс которого лежит в нужном месте второго массива. Казалось бы, дело простое, да?
Фиг вам! Та же самая ошибка.
… В общем, конечная строка кода выглядела так:
И удаление любого из двух явных приведений типа приводило к выбросу предупреждения (ну а в силу -Werror — к ошибке компиляции).
Явно преобразовывать между int и bool, говорите?..
P.S. А ещё я про этот случай периодически вспоминаю при работе с Rust. Со всеми этими
Но здесь хотя бы понятна суть: весь язык устроен так, чтобы не давать выстрелить себе в ногу, обрезав результат под выходной тип.
yomayo
Это кажется.
Возьмём логический тип. Пусть false – это 0. Тогда ~0 (не ноль) — это 0xffffffff или -1 в дополнительном коде. Применяя как операции «&», «|», «^»,, так и операции «&&», «||» мы получаем правильные результаты.
Для числового типа это не так. Возьмём значение 1 и значение 2. И то, и другое — это true. Применив к ним операцию «&» (побитовое «и»), получаем 0, т. е. false. Получается, что true & true равно false. Для операции «^» тоже можно подобрать примеры противоречий.
Вывод: между типами int и bool разница есть.
sentyaev
Вы почему-то смешали булеву логику и битовые операции.
PsyHaSTe
По старому соглашению,
a && b
это сокращенная записьa & b
, где b не вычисляется, если a false. Аналогично с |.KostaArnorsky
В С-подобных языках && и & два совершенно разных оператора.
PsyHaSTe
Для bool-like типов данных они должны давать один и тот же результат.
KostaArnorsky
А если в выражении вызов функции? А если функция с сайд-эффектом?
PsyHaSTe
Какая разница? Вопрос про тип данных.
Насчет функций, то в случае && вторая часть не выполняется, если вернулось false, а в & обе части выражения вычисляются. Но я это уже говорил буквально двумя строчками выше…
KostaArnorsky
Разница в том, что && это не сокращенная запись &, это два совершенно разных оператора. В .NET даже определены для разных типов, можно 3 & 5, но не &&.
PsyHaSTe
Я не зря про bool-like говорил.
Для остальных типов побитовое И может быть любым.
если у вас есть
результаты c и d должны совпадать для любых a,b.
KostaArnorsky
В C/C++ int bool-like (хотя что это вообще такое?)
Я надеюсь, вы все так пишите, удачи с отладкой.
PsyHaSTe
Так никто и не спорил, что В С эта часть стандарта откровенно плохая.
KostaArnorsky
Все в порядке с этой частью стандарта, хотите стрелять себе в ноги — стреляйте.
yomayo
Есть момент, который подрывает веру в статическую типизацию. Допустим, пользователь вводит строковые данные. Эти данные надо обработать функцией «распознать(строка)». Эта функция должна вернуть значение числового типа (если пользователь ввёл число), дату (если введённые данные похожи на дату) или исходную строку (если ничего распознать не удалось). Статически определить такую функцию не получится. Функция должна будет возвратить не только значение, но и типовую переменную, поскольку тип заранее не известен.
mayorovp
Все возможно. Функция без проблем может вернуть
number | string
(Typescript),Either Int String
(Haskell) илиstd::variant<int, std::string>
(C++).yomayo
Да написать-то так можно. но получается потом не по фэншую. Дальше придётся писать проверки типов. И тогда уместно к этому случаю приклеить комментарий, который написали ниже:
У нас получается не статическая типизация, а кустарная (вручную написанная) динамическая типизация.
mayorovp
А проверки типов вам придется писать в любом случае, если только вы не передаете объект в чужую библиотеку целиком (в таком случае эти самые проверки типов написаны в этой самой библиотеке).
TargetSan
В динамике вам точно так же придётся писать проверки для обработки собственно значений
Типы-суммы это очень даже статическая типизация. Вы статически знаете, что значение внутри или типа А, или Б. Назвать её динамической — примерно как назвать int динамически типизированным, просто потому что там могут лежать разные числа.
Meloman19
А что потом делать с этим значением? Мне правда интересно, как новичку.
Я просто не совсем себе представляю код в котором мы из метода получаем: или число, или дата, или строка, а после свободно работаем с этим значением без каких либо проверок типа.
nexus478
Уже сами исходные условия вызывают вопросы. Если надо возвращать разные типы, то получается клиентский код не знает, какой тип ему придет. Как он вообще будет дальше работать с этими данными? А если ему неважно, какой тип, то можно и дальше со строковым форматом работать и тогда задача теряет смысл.
Если же вам все-таки надо вызвать определенную логику обработки в зависимости от типа, то это например в C# это можно быть сделано вот так
Для абстрагирования от типов можно также можно использовать ad-hoc полиморфизм (перегрузка операторов или методов), параметрический полиформизм (дженерики) или полиморфизм подтипов (абстрактные классы, интерфейсы). Инструментов куча.
И вообще, что это у вас за поле такое в интерфейсе, которое может быть и строкой, и числом, и датой?
KostaArnorsky
Да ну, чушь. Вот 2018.12 — это число? Вы уверены?
Не знаю, где это может понадобится, для синтаксического разбора что-ли. Но для этого более вменяемые инструменты существуют.
creker
В C# если совсем прям не париться, то можно вернуть object и в рантайме проверять тип на выходе. Только вот, зачем? Я не помню в практике задач просто распознать. Я всегда знаю заранее, что конкретно я хочу распознать. Если я распознаю то не знаю что, то зачем мне вообще это делать, пусть строкой остается. Когда парсится какой-нить JSON, то я все равно проверяю все типы на выходе, чтобы быть уверенным, что мне пришли правильные данные, а не строка, в которой может быть какой угодно тип.
zagayevskiy
Прекрасно получится, например в Kotlin. Примерно так
Всё статически определено, проверено и обработано. Профит.
KostaArnorsky
Пользуйтесь
areht
И как этим пользоваться? Вызов функции «распознать(строка)» можете показать?
KostaArnorsky
?Если вам нужен синтаксический анализатор — используйте синтаксический анализатор, опишите правила и распознавайте ваши строки. Вашу функцию можно использовать как одноразовое решение, но надо понимать, что это костыль, потому что habr.com/post/431250/#comment_19430178
В остальных 99% случаев вы знаете требуемый тип. И не забываем про
про whitelisting by default.
areht
А предыдущие 2 комментатора смогли осилить ТЗ, а не рассказывать, что вместо пары if надо синтаксический анализатор настраивать.
> Вот 2018.12 — это число? Вы уверены?
Я уверен, что неуверенность в этом только вам по ночам спать мешает.
Да, число. Если бы дату хотели ввести — ввели бы увереннее, например «2009-06-15T13:45:30.0000000-07:00»
KostaArnorsky
А где вам такое понадобилось? Мне за всю практику понадобилось ровно один раз, где пользователи могли писать что-то типа sum(workdays({requests.submited}, 11/03/2015)) * 8. Первым делом мы выкинули багованные велосипеды неосльщиков, взяли PEG и описали нормальную грамматику. А вы для чего тип ввода определяете?
areht
Ну, задание не я придумывал. Но, например, указывать «вчера» как "-1" наравне с «28.11.2018» с подобным парсингом в (datetime|int) где-то под капотом вполне могу представить.
KostaArnorsky
Я бы наверно regex написал в этом случае. Тем более что JS Date 28.11.2018 все равно не понимает, а -0.01e+2 вполне себе валидное число, только оно вам такое надо?
areht
Вы опять какую-то свою проблему решаете.
> -0.01e+2 вполне себе валидное число
Понимаете в чем дело, ввод "-1" делают что бы быстро(!), не включая мозг, ввести дату «вчера». Если вам быстрее ввести -0.01e+2, то и оно корректно обработается — флаг в руки, чем бы дитя не тешилось.
То у вас синтаксический анализатор, что бы можно было ввести как минимум sum(workdays({requests.submited}, 11/03/2015)) * 8, то нельзя -0.01e+2… Не понимаю.
KostaArnorsky
Извините, самое главное забыл, функция в любом случае должна возвращать дату или невалидное значение (e.g. undefined or null), так что ваш пример мимо. Так можно какой нибудь реальный пример, где требуется возвращать разные типы?
vintage
А NaN — это true или false? A -0? A Infinity?
PsyHaSTe
Да просто заучиваете всю табличку и всё. Заодно тренировка памяти!
evocatus
Динамически типизированному LISP скоро 60 лет.
impwx
И сколько крупных приложений на нем написали за все эти годы?
evocatus
понимая под LISP семейство языков (Common LISP, Scheme, Clojure, ...):
Emacs, Maxima, некоторые части AutoCAD (и там есть встроенный интерпретатор LISP), полётная система Boeing 747, Apache Storm, Datomic (база данных), бэкэнд Walmart, оригинальный Reddit, HackerNews,…
Немного, но достаточно, чтобы доказать, что писать крупные приложения на нём вполне возможно.
impwx
Полноты по Тьюрингу и очень упорного программиста достаточно, чтобы написать что угодно на чем угодно. Было бы странно, если бы на LISP не было вообще ни одного серьезного приложения — сама по себе идея очень заманчивая. Однако всего пара десятков проектов за шестьдесят лет — это все-таки показатель.
evocatus
О честном сравнении не может быть речи в условиях, когда:
impwx
Осталось только понять, что из этого причина, а что — следствие…
Druu
Проходят сейчас в универах и лиспы и пролог и даже хаскель иногда (отдельные курсы по функциональному и логическому программированию), пусть и немного. Исключение — только какие-то совсем непонятные шараги.
evocatus
Как проходят? Как у некоторых: по языку в семестр? Пользы от такого зоопарка никакой, только каша в голове.
Druu
А надо 5 лет лисп учить?
evocatus
А что вы понимаете под учить? Синтаксис можно объяснить за 5 минут, а вот начать думать по-другому, научиться решать задачи по-другому — это требует время.
KostaArnorsky
Я все языки проходил по одному в семестр, C только два, да и то второй был системное программирование с обязательным условием писать на C. И был Lisp и даже Prolog.
Free_ze
Вести из параллельной вселенной (=
igrishaev
Полистайте https://lisp-lang.org/success/
Проекты на Лиспах писали еще в то время, когда никаких Гитхабов не было.
gatoazul
Начнем с того, что IDE, за использование которых ратуют многие комментаторы, появились именно для Лиспа. Ну и еще куча других концепций, которыми вы сейчас пользуетесь.
Помните, как Маяковский писал о Хлебникове? Есть поэты для читателей, а есть поэты для поэтов.
impwx
Не поймите неправильно. Для своего времени LISP содержал в себе множество революционных идей, однако идеальным все же не был. Поэтому с тех пор появились более практичные инструменты, которые взяли хорошее и исправили остальное.
impwx
Это не динамическая типизация. Динамическая типизация в вашем примере — если бы у вас все аргументы и возвращаемые значения всех функций были
void *
.alexesDev
Не void*, а {type,void*}, как std::variant
0xd34df00d
Тогда уж any, а не variant.
alexesDev
Вы писали на C++ под nodejs или ещё что? V8::Value похож на std::variant, точно не на any. В any можно положить все, что угодно, а std::variant это предопределяет. v8docs.nodesource.com/node-0.8/dc/d0a/classv8_1_1_value.html
0xd34df00d
Нет, я вообще не писал под nodejs. Правда, я не понимаю, причём тут вообще V8, если речь про аналог динамического типа (а это именно тег + значение) в C++.
alexesDev
Покажите как адекватно можно сложить два std::any? Возможно я не понимаю как это делается. Для std::variant все просто — static_visitor и простым перечеслением, что делать в странных ситуациях, когда нужно int и строку сложить. Я использовал этот подход, когда писал игрушечные языки. Он удобный, простой и даже, неожиданно, быстрый, если не нужно делать тяжелых конвертаций.
0xd34df00d
Никак без очень дурно пахнущих проверок типов. Как, впрочем, и в динамически типизированном языке, просто эти проверки конкретно для сложения написали за вас.
gBear
При всем моем уважении, это всё не про статическая vs динамическая типизация, а про сильная vs слабая.
Все то, что так вас раздражает — это все не от «динамики» типов, а от их «weakly typed» :-) Которая, вполне себе, может быть и статической при этом. Обратное, кстати, тоже верно… в том смысле, что динамическая типизация ортогональна сильной. И языки с сильной динамической типизацией вполне себе есть.
Так что, «динамика» тут вообще не причем… даже в плане «статических проверок». Динамическая типизация, совсем не означает их отсутствия. А вот слабая типизация (хоть даже и статическая) — в лучшем случае — сводит их к, скажем так, слабо полезной формальности.
P.S. Весьма сильно улыбнуло противопоставление «динамики» и ООП. На всякий — вообще ортогональные вещи.
P.P.S. И да… *система типов* — это, судя по всему, не совсем то, что вы думаете.
PsyHaSTe
Если под сильной динамической типизация понимать языки вроде Питона, то там с этим больше проблем, чем пользы, имхо. Да и все же слабость и динамичность чаще ходят в тандеме.
NeoCode
Я вот тоже сторонник статической типизации. И поскольку статью писал человек мыслящий также как я, то его точка зрения вполне понятна. Но крайне интересно было бы прочитать аналогичную статью, написанную разработчиком на JS. Именно с развернутой критикой статической типизации и демонстрацией преимуществ динамической, на реальных примерах. Ну или хотя-бы развернутый комментарий. Мне кажется, такое погружение в противоположную точку зрения здорово расширяет восприятие, ну и вообще интересно.
evocatus
Не JS, но от автора динамического языка, который был очень хорошо продуман. youtu.be/2V1FtfBDsLU?t=1610 (временная метка на то место, где он говорит о разных ошибках, в т.ч. связанных с типами и почему это не так важно)
А в этом интервью Рич на вопрос о динамической типизации ответил, что «уважает право разработчика делать что угодно»
justboris
Я большую часть времени пишу на Javascript, а иногда приходится нырять в Java и Typescript. Для себя нахожу вот такие проблемы.
Есть какая-то функция, например создание модального диалога
В типизированном подходе вам понадобится описать интерфейс DialogOptions и перечислить эти поля еще раз. А если внутри появится еще функция
createDialogBody()
, которая будет принимать только часть параметров, то ей понадобится еще один интерфейс. Эта писанина утомляет.При парном программировании с дизайнером нужно быстро перекидывать блоки туда-сюда, не особо задумываясь о безопасности типов. В таких ситуациях динамическая типизация приходит на помощь.
По моему мнению, на Typescript удобно оформлять уже сложившиеся продукты, например при переписывании легаси. Там понятные форматы данных и протокол взаимодействия. А для стартапа или проекта на хакатоне, где решения принимаются за несколько минут, а финальные форматы данных еще не ясны, излишняя типизация будет ненужной рутиной, которая будет стопорить творческий процесс.
Alexey_Zal
Согласен на все 100%.
NeoCode
Непонятно что вы имеете в виду с интерфейсами. Я бы просто написал (псеводкод на некотором типизированном языке, «void=>void» — тип для функции, не принимающей никаких параметров и не возвращающей никакого значения)
Меня вот просто выводила из себя (до желания стучаться головой о стенку) ситуация, когда я смотрю код на php или js, там какая-то функция с аргументами, или просто какие-то переменные, и я НЕ ПОНИМАЮ, каких они типов. Чего мне ждать — числа, строки, структуры (если структуры то какой, с какими полями каких типов), функции (опять же с каким списком аргументов)? Что я имею права с ними делать а что нет? Ну хорошо, что в вебе в основном строки. В основном — но не всегда. Вот честно, не представляю как вы с этим живете :)
Alexey_Zal
Но сколько на отписывание структур нужно времени? А если эта функция используется одним разработчиком в тестовом проекте и в одном месте?
NeoCode
Да нисколько. В моем примере нет структур, а просто у каждого аргумента добавлен тип — можно рассматривать это как часть имени. Структуры это уже посерьезнее, когда понятно что код становится достаточно общим и универсальным, «закрепляется» в проекте как часть архитектуры.
Я тоже не люблю лишнюю писанину. Но только ли в этом вопрос?
khim
aikixd
Как-то, помню, уже с 8 летним опытом писал на JS на Ember. Но тут по интернет и работа закончилась. Вот так и живём.
justboris
У такого размазывания по аргументам есть и обратная сторона. Функции с большим числом аргументом считаются плохим кодом и предлагается избавляться от них созданием DTO. Вот эти DTO как раз утомительно писать, особенно в Java, где на каждое поле еще и геттер/сеттер нужно создать. Да, в редакторах есть макросы, но все равно получается простыня кода просто для того чтобы передать данные из одного модуля в другой.
Читаем документацию, вставляем параметры как там описано. Еще современные редакторы (IDEA и VSCode) понимают декларации Typescript и показывают их для JS кода. Таким образом можно облегчить себе работу с библиотеками, не обкладывая при этом типами весь код целиком.
P.S. Я в целом, не против типов и тоже их умеренно использую. Но начинать новый проект с описания интерфейсов я не могу. Сначала накидаю кода, чтобы он хоть как-то запускался и выполнял задачу, а потом уже можно о типах задуматься, когда понятно, что проект в целом на верном пути.
areht
> Функции с большим числом аргументом считаются плохим кодом и предлагается избавляться от них созданием DTO.
Плохой код — это не когда много аргументов, а когда много зависимостей. DTO — это так, украшательство. Ну, точно можно отложить до «когда понятно, что проект в целом на верном пути».
> А сколько времени займет построение графика по данным из JSON на том же C#?
Вы, наверное, подразумеваете, что надо типы создавать под JSON, и это долго? В том же C#, внезапно, есть dynamic.
justboris
Это действительно внезапно! Потому что теперь я не понимаю автора статьи еще больше. Если в C# есть dynamic, что чего плохого в том, что в Typescript иногда используется any?
areht
Ещё в C# есть goto, но в реальной жизни не встречается.
dynamic достаточно экзотическая штука, неудобная и опасная. Ну вот для «накидать график» она подходит, а как только возникнет желание, например, входные данные валидировать — то, наверное, уже тип опишите. И в любом случае оно вряд ли покинет границы функции.
KvanTTT
Можно ли обобщить до такого?
динамический язык — достаточно экзотическая штука, неудобная и опасная. Ну вот для «накидать график» она подходит, а как только возникнет желание, например, входные данные валидировать — то, наверное, уже тип опишите.
areht
Ну как, динамический язык может и прилетевший {'Age': '42' }, вместо {'Age': 42 } простить, и stuff.Address.City как stuff.?Address.?City обработать.
Так что я исключительно про dynamic+C#+мозг людей привыкших к статике.
PsyHaSTe
Просто исторически сложилось, что статически типизированные языки многословны.
Но в 21 веке можно получит и то и то бесплатно. Например
IvanNochnoy
Встречается, если нужно выйти из вложенных циклов.
Free_ze
В редкой задаче есть больше двух циклов и нет плохокода одновременно.
PsyHaSTe
Тогда пишется функция, где пишется return. Goto ни в одном проекте, слава богу, не встречал.
Szer
ОЧЕНЬ быстрые корутины на дотнете написаны с помощью goto
https://github.com/Hopac/Hopac/blob/6b4fe8e89e95aa9f2119e96f19d37cfe541e7715/Libs/Hopac.Core/Engine/Worker.cs#L129
Free_ze
В системном программировании своя атмосфера. Что там будет оправдано в прикладной разработке может быть моветоном.
PsyHaSTe
А еще yield return/ async-await разворачиваются в стейт-машины на goto, но это не повод так писать в проде.
Не говоря о том, что если так было написано, это не гарантирует, что это оптимальный код.
KostaArnorsky
Я больше одного return пишу с осторожностью. Приятно с таким кодом работать?
mayorovp
Лучше уж два return в функции, чем goto или вспомогательный флаг…
PsyHaSTe
Очень приятно. https://habr.com/post/348074/
KostaArnorsky
Я же не говорил не использовать, я говорил — с осторожностью. Некоторый примеры я бы отрефакторил, к примеру, updatePostAction можно разбить на две. $failResponse переменная вообще не нужна, в success pass она даже не используется, вынести это в третий метод.
Kroid
Да, приятно. Особенно с теми языками, где нет switch-case.
KostaArnorsky
Если нужно писать на порезанном ассемблере, то и goto может оказать верхом дизайна. Только ничего приятного я в этом не вижу.
Kroid
Ранние return в общем случае — это избавление от вложенных блоков. Проверили что-то — если false, то до свидания. Как это может быть неприятно?
areht
return бывает 2 типов:
1) проверка инвариантов — тут да, проверили, false, досвидания
2) из логики функции. И вот тут такое себе. А успели ли мы подготовить данные? А корректно ли всё закрыли? И записали ли про выход в лог? И тут ради раннего возврата начинают какой-нибудь try finally накручивать на ровном месте (к вопросу об избавлении от вложенных блоков)…
Вот нет ощущения, что это всегда прям хорошо.
TheShock
2) Все эти вопросы отпадают при нормальной декомпозиции функции.
unclejocker
Ну в шарпе и unsafe есть, и Span со stackalloc, но то не значит что ими надо всю программу обмазывать. Каждому инструменту свое место.
PsyHaSTe
То, что в C# как правило можно воспользоваться нормальным датапровайдером, или на худой конец сервисом. Динамики же имхо довольно бесполезные, потому что они привносят все те минусы, про которые в статье написано.
mayorovp
Тут выше комментатор жаловался, что ему лень отдельный тип писать — а вы ему, фактически, предлагаете отдельную документацию на функцию сочинять.
impwx
Сэкономив на написании интерфейсов, вы рискуете потерять гораздо больше времени на всяких дурацких моментах, когда нужно будет подсмотреть имя аргумента где-то в коде, или когда вы запустите приложение и внезапно оно упадет из-за опечатки в имени свойства. По сравнению с тем, когда после написания интерфейса IDE понимает вас с полуслова — это куда более существенный «стопор творческого процесса», но если человек все время пользуется динамически типизированным языком, он может этого просто не осознавать.
Впрочем, по поводу хакатонов соглашусь. Если скорость важнее качества — то ДТ возможно будет лучше.
aikixd
Типизация не влияет на скорость разработки, даже на хакатонах (мы использовали Шарп). Все дело привычки. И в повседневной разработке тоже. Тем более, что строгая типизация позволяет легко генерировать и рефакторить код. Мой рекорд 1300 строк идиоматического ООП в день.
justboris
До таких дурацких моментов еще нужно дорасти, и не факт что это случится. На JS можно быстро накидать интерфейс (какой-нибудь отчет с графиками, например) и показать его людям. Примерно для тех же целей аналитики используют Jupyter Notebook с динамически типизированным Python.
А сколько времени займет построение графика по данным из JSON на том же C#? Без сарказма, правда интересно, как бы вы это сделали и какие библиотеки использовали.
impwx
Язык становится пригодным к использованию в определенных задачах в первую очередь благодаря библиотекам, а не типизации. Однако, при прочих равных, использовать чужую статически типизированную библиотеку гораздо удобнее — описание типов одновременно является документацией, которую IDE вам сама подсказывает без отрыва от процесса. Сравните это с необходимостью постоянно переключаться из IDE в браузер с документацией, а она может быть неполной, устаревшей или вообще отсутствовать.
Касательно инструментов для построения графиков и анализа данных — конечно, у Python инфраструктура получше, однако и дотнет кое-что может. Например, в Azure Notebooks поддерживаются как Python/R, так и статически типизированный F#.
Лично я для исследовательских целей использую LINQPad — это REPL-среда для C#/VB/F#. Там можно использовать все, что есть в самом языке\фреймворке (LINQ, любые Nuget-библиотеки), и плюс встроенные утилиты (отображение таблиц, графиков, подключение к БД). Отрисовка графика по JSON выглядит примерно так:
justboris
Спасибо за объяснения! Может быть, пригодится мне в будущем.
Про использование и не использование типов я уже свою мысль донес. Есть ситуации работы с произвольными данными, и на таких динамическая типизация и оправдывает свое существование.
0xd34df00d
Не только.
impwx
Вы правы. Я имел в виду среди тех языков, которые перечислил выше.
IvanNochnoy
Хм, Newtonsoft.Json и OxyPlot. Не так уж и сложно.
eugenk
Я конечно не зверь, не фашист и не садюга, но когда я слышу подобные рассуждения, я испытываю огромное желание, чтобы рассуждающий забыл о своём коде годика так на три, занявшись другими делами, а потом по серьёзной необходимости к нему вернулся. Думаю подобный квест многих опустит с небес на землю.
justboris
Был такой опыт. Большая часть проблем была не с сигнатурами вызовов или структурами объектов, а с банальным пониманием что эта функция делает. Читать код и вникать, что он делает придется в любом случае.
А правила написания читаемого и понятного кода примерно одинаковы для любых языков, статических и динамических. Им лучше, конечно же, следовать.
AlexHell
А еще Find Usages. Вот есть у меня в C# тип MySuperType, и надо мне найти где он юзается: где конструируется — в одном из case, в другом — вообще все использования, объявнения его как параметра в метод и как тип возврата.
Что я буду делать в JS? Т.е всеравно TypeScript вам тут нужен будет если проект не прототип, и даже не факт что все эти IDE современные по TS помогут вам сделать то же самое что с системой типов элеиментарно уже десятки лет IDE делает.
justboris
В современном Javascript активно рекомендуется к использованию синтаксис
import something from './something'
. Он отлично статически анализируется и найти все места использования something не составляет труда. Работает в том числе и автоматическое удаление/добавление импортов.А с поиском использования типов все еще лучше: вам не придется искать, где именно используется этот тип, если у вас не используются типы вообще.
AlexHell
вам аналогичный ответ — что я ответил ниже — про добавление свойства в объект. И да = объекта у вас не может не быть, он есть. Вы же в своей функции чтото делаете с каким-то переданным JS объектом, пусть он и без класса.
Был X,Y, надо Z добавить. Удачи добавлять. Искать не надо вам. Ну да без поиска попробуйте.
justboris
Не очень понял, а в чем проблема добавить еще одно свойство? В источнике данных добавим его создание, в получателе – чтение. С удалением могут возникнуть проблемы, а добавление делается очень легко.
KostaArnorsky
В JS вам это просто не понадобится, как вам уже ответили: нет типов — нет проблем. В JS вы будете искать, где это функция вызывается — обычный тестовый поиск по проекту. Поэтому давайте уникальные имена вашим функциям в JS! Умные разработчики еще псевдо неймспесы используют и ограничивают область видимости по возможности. Или просто используют TypeScript.
Free_ze
Пока все прогрессивное человечество стремится к простоте и наглядности наименований…
AlexHell
Текстовый поиск без самантики в 21 веке? Статические типы и семантика имени что это объект или класс или переменная — были придуманы как раз для облегчения работы.
И допустим я не знаю имя функции. Я знаю что у меня есть сущность у которой X,Y координаты, и мне надо Z добавить. Какую функцию мне искать? Все которые работают с объектом у которого есть X,Y, но пока нет Z. Без семантики — кучу головной боли.
И да такие задачи сплошь и рядом в реальной разработке. Пример с X,Y,Z для упрощения. Реально бывает кучу других задач по расширению кол-ва свойств у сущности. А если вы сущности не ввели явно, а держите их в голове — ну удачи пройтись поиском.
KostaArnorsky
Я где-то сказал, что это хорошо и правильно? Вопрос был, как искать — я ответил.
И пример на самом деле не релевантный — вы же добавили Z с какой-то целью, вот там, где вам Z понадобилось — там и меняйте. Сразу видно, что на JS не пишите, новые свойства вообще не проблема (если не использовать ...), намного большая проблема, если вы удалили/изменили существующее свойство Z. Как узнать, что оно нигде не используется? Даже если вы нашли все места, что если в соседнем бранче новую функцию написали, в которой по прежнему Z используется? Это никак не обнаружится при мереже, компиляции нет… ну вы понимаете, только тесты. То, что вам может прийти любой мусор — вот это проблема.
TheShock
Ага. Ну, к примеру, pipe, it, connect, put, dispatch и другие очень емкие и уникальные словечки из современных хипстерских библиотек. Сколько у вас будет совпадений со словом «put»? Или «it». А еще у нас есть куча нод-модулей, где все функции повторяются по несколько раз, у этих функций разные версии и ты точно не можешь сказать, какая именно из этих используется. Знаете, иногда типизация TypeScript ломается. И тогда я беру тазик и могу наблевать в него до самых краев пока разберусь во всем при помощи вашего совета.
KostaArnorsky
А еще у меня во всех проектах был jQuery c noConflict, никогда $. А еще можно явно указать какую функцию из какого модуля использовать. Еще можно попробовать все зависимости прописать, но пока не получалось. Но кто сказал, что будет легко? Зато работало, а не полная консоль ошибок.
TheShock
То есть вы пользуетесь менее удобными инструментами, чтобы было тяжелее? Я правильно вам понял?
KostaArnorsky
Вы можете предложить альтернативу?
TheShock
Конечно. Статическая типизация и никакого «текстового поиска». Я 7 лет писал на JS, а потом попробовал писать на C#. И осознал, насколько неудобно мне было все семь лет и какие жуткие и неудобные костыли я использовал. Теперь возвращаюсь на JS или с использованием TS (хотя это и не настолько круто как C#) или на скрипты до 50 строк — весь код должен умещаться на экран.
KostaArnorsky
А как вы исполняете C# в браузере? Я пару лет назад смотрел прикольную либу для трансплайна в JS, но это было далеко от production-ready. А в скриптах на 50 строк никаких библиотек никогда не используете? А из TS?
ApeCoder
blazor есть, но тоже не production ready
TheShock
Да. Если скрипту нужна библиотека — значит он уже достаточно большой, чтобы использовать TS.
eugenk
Хотя бы такой момент. Со статически типизированным кодом помогает разобраться сама IDE (хорошая конечно). С динамическим такой фокус не проходит. Она просто не знает как вывести типы. В итоге времени Вы на такие разбиралки потратите куда больше, чем если бы три года назад продумали и явно прописали типы. Нет, согласен, для скрипта в сотню-другую строк, да который ещё и пишется на один раз, можно особо не заморачиваться. Но в более серьезных случаях…
Cerberuser
Присоединяюсь. Именно по этой причине я, поработав с TS, с трудом (и только по особой необходимости) заставляю себя возвращаться на чистый JS.
justboris
В этом и суть. С типами у вас каждая задача – "серьезный случай". В JS типы можно принести позже, когда совсем прижмет. Или не приносить, если и без них нормально, как в d3 или jsplumb.
В d3 исходники разбиты на маленькие модули (например). Эта модульность работает почти как типизация (вы не сможете вызвать функцию, если она не импортирована), только без сигнатур методов. Видимо, автору и без них понятно, что где возвращается, просто по их именам.
В jsplumb все похуже. Иногда там используются аннотации типов через комментарии, но очень мало. Если бы я работал над тем проектом, я бы предложил им перейти на Typescript, там это оправдано.
eugenk
Вот за эту подсказку спасибо. Надо будет поизучать. Тогда может врублюсь, как народ на этом пишет.
AlexHell
Ваши аргументы про «когда приспичит внести типы… да как в d3» относятся к использованию готового API / интерфейса. А попробуйте сам свой API / интерфейс для лругих создать, тогда и скажете что не надо это все.
О да я глянул только что вашу ссылку на arc
github.com/d3/d3-shape/blob/master/src/arc.js
Это жесть, куча копипаста в духе
" return arguments.length? (endAngle = typeof _ === «function»? _: constant(+_), arc): endAngle;"
И всеравно не понятны типы возврата. Тут будет приведение к int или float будет или строка? И в куче аналогичных случаев тот же вопрос.
А когда возврат структуры — вам еще внутри функции покопаться
И только не говорите что неважно. Вы с таким же успехом тогда аргументируете к вопросу «вот вам массив байт — в нем что — целое число и вещественное и с какой точностью, и какая в нем endianness если он сериализован, и что там за переменные в ней и каков размер массива если это он». А вы еще на функции будете проверять (по духу d3). А в колбеках будете выяснять что у вас в аргументах — строки (которые легко уже превратяться в int или float по вашей надобностью) или JSON сериализованная форма (и какого объекта серилизованного) — что даже в этом случае — строка всеравно, а семантика разная. Ну не дураки же придумали семантику. Ну потратите лишнее время на выяснения.
**
function arc() {
var buffer,
r,
r0
r1
и
export default function() {
var innerRadius = arcInnerRadius,
outerRadius
Это и есть объект. И система типов вот она. Даже автор d3 вводит. Только костылями, без явного объявления class (как в подмножестве 2016 или 2015 года не помню уже)
KostaArnorsky
Вы еще не поняли всю глубину пропасти, в JS нет int.
VolCh
Справедливости ради хорошие IDE нормально работают с (некоторыми) динамическими языками, если не использовать всякий манкипатчинг и смену типа (а частично и их поддерживают). Они точно так же статически выводят типы как и в случае статической типизации. Код на языке с динамической типизацией никто не запрещает анализировать статически, выводить типы, предупреждать об их несовпадении и т. п.
khim
Вот только работает это только тогда, когда код написан более-менее как на языке со статической типизацией… а в этом случае непонятно — почему сразу не взять язык со статической типизацией…
Только не берите C/C++ — неплохие языки, но количество ногострелов, прилагаемых «в комплекте», что называется «зашкаливает»…
VolCh
Именно. Более-менее так и написан, а все использования «киллер-фич» динамики как таковой строго задокументированы и частично статически и(или) динамически типизированы (например с помощью *doc аннотаций), чтобы помочь и IDE, и разработчику понять, что происходит.
А мне непонятно зачем его брать. Думаю, за месяц-два я легко перейду с PHP на Java/C#, как когда-то перешёл на PHP и JS с C/C++, пускай и не все их возможности знать буду. Но зачем? Что это даст мне лично и что бизнесу, на который я работаю? Ну вот есть у нас сейчас TS — за почти полгода работы ни разу он мне не помог обнаружить какой-то баг.
faiwer
Ух. Первый негативный отзыв о TS который мне попался на глаза. Реквестирую статью про это :)
VolCh
О чём об этом?
mayorovp
А вы не думали, что он, вместе с автодополнением в IDE, мог вам помочь этот баг не написать?
VolCh
Не заметил существенной разницы между JS с ключом only type based competition и TS в одной и той же IDE
mayorovp
Хм, может быть вы просто не писали достаточно сложного кода? Или не пользовались библиотеками с нетривиальной типизацией?
Мне вот достаточно написать
$.extend(Foo.prototype, { ... })
— и IDE уже не может понять что все методы внутри фигурных скобок могут обращаться к свойствам Foo в контексте this.Или
viewModel.BeginPeriod.typed
— и IDE перестает понимать что происходит, потому что свойства typed у ko.observable нету, а тайпинг через jsdoc чужому классу не добавить.VolCh
Стараюсь не писать сложного кода — KISS и всё такое :) И использовать библиотеки, о которых знает моя IDE (пускай она и использует для своего знания объявления на TS)
mayorovp
Но если ваша IDE использует тайпинги от TS даже для JS — вы уже не можете использован аргумент "Не заметил существенной разницы между JS с ключом only type based competition и TS в одной и той же IDE" в качестве доказательства ненужности TS.
VolCh
Я не утверждаю, что TS не нужен в принципе никому и никогда. Решили разработчики IDE использовать существующие TS тайпинги для улучшения работы их IDE со стандартными или популярными JS-библиотеками — видимо нашли это полезным, я даже могу предположить почему.
Но вот при написании своего кода я особой разницы не замечаю.
creker
Статическая типизация и вообще вся эта модная нынче compile-time валидация в первую очередь про то, чтобы некоторые баги предотвратить, ибо их невозможно совершить в подобных языках. Вместо того, чтобы упасть в райтайме через год, когда выполнение доберется до злополучной строчки кода, оно просто не соберется.
VolCh
Чем качественно хуже статические анализаторы динамических языков, которые точно так же не дадут, например, сделать коммит?
ApeCoder
Либо они являются концептуально статически типизированным языком, написанным поверх динамического, либо они слабее поддерживаются или менее выразительны (т.е. например там нет каких-то аннотаций для типов).
VolCh
Чем концептуально статически типизированный отличается от обычного?
ApeCoder
Думаю, тем что там есть развитые инструменты для описания типов. Т.е. можно декларировать типы переменных значений и т.д.
Во-вторых обязательностью. Как только вы делаете проверку обязательной у вас уже получаются ситуации которые работали бы вполне пока бы вы не вошли в специфический кейз, но статическим контролем будут запрещены.
0xd34df00d
Качественно другой выразительностью подобия системы типов, которую они там внутри синтезируют.
Иными словами, когда статические анализаторы динамических языков будут мне выдавать те же гарантии, что и система типов какого-нибудь хаскеля, тогда можно будет снова об этом говорить.
VolCh
Вы не путаете статическую типизацию со строгой? Гарантии даёт строгая прежде всего.
0xd34df00d
Я говорю о строгой статической. Когда мне рантайм языка даёт гарантии, это уже немножко поздновато.
VolCh
Лучше поздно чем никогда. А иногда лучше даже поздно чем рано :)
creker
Я не знаю о каких анализаторах речь, но достаточно представить, как бы он мог работать, и будет понятно, что без описания типов существенный анализ он сделать не сможет. Я бы даже сказал никакой он анализ не сможет сделать, ибо в любой момент в любом месте может оказаться любой тип и ничего с этим не поделать. TypeScript это то самое описание типов для JS, чтобы компилятор мог сделать статическую валидацию и эти типы выкинуть.
VolCh
Они широко используют автоматический вывод типов, а также информацию о типах, имеющуюся в синтаксисе типа function name(string $param) или в *doc-аннотациях.
creker
Т.е. костыли за неимением встроенного в язык способа описания типов. Работать толком это не будет и не может. А сами аннотации могут оказаться не полными или вообще не верными, т.к. никаких гарантий не предоставляют.
Какой тип можно выводить из чего, когда типов нет? Как только речь не идет о строчках и числах, то пиши пропало. Повсюду объекты с хрен его знает каким полями и функциями. В любом случае, даже все это это костыли по одной простой причине — динамическая типизация мешает и всяческими способами это пытаются решить. Неудивительно в этой ситуации, что typescript имеет такой взрывной рост в одного из лидеров сразу — он пришел в нужное место в нужное время, когда JS приложения переросли объем, когда с ними можно адекватно работать со всей этой динамикой.
VolCh
Если придерживаться определенных правил при написании, то анализатору будут известны поля и функции объектов. Грубо, нужно писать на JS в стиле на который не заругается компилятор TS, полагаясь только на автовывод.
creker
А можно сразу писать на TS и не иметь себе мозг всякими велосипедами, которые от одного шага влево, забывчивости, неосторожности ломаются.
VolCh
TS увеличивает порог входа в проект. Так уж исторически сложилось, что знаний одного TS недостаточно для полноценной разработки, нужно и JS знать. И тут вопрос возникает — а нужен ли на конкретном проекте TS, даже если проект новый, про легаси совсем молчу.
У TS есть плюсы, но есть и минусы.
Free_ze
TS — это же надмножество JS, выучил его — второй уже знаешь.
VolCh
Что интересно, я не нашёл полноценных учебников TS в своё время, в которых JS вообще не упоминался бы (ну пускай "под капотом компилируется в JS, но вам это не нужно", как может быть в учебнике по C про машинные коды или в учебнике по Java про байт-код). В подавляющем большинстве литературы речь о различиях, и подразумевается что JS уже знаешь. Разве что классы описаны "с нуля", но это как я понимаю, потому что по факту они пришли в JS из TS.
Free_ze
Если честно, я полноценных учебников по TS не встречал в принципе (не умоляет годности официальной документации, но, как вы уже заметили — требует старта со знания JS).
Что, кстати, огорчает, ибо в учебниках по JS я встречал достаточно мало информации насчет грамотного построения архитектуры ПО в контексте фронтенда, в стиле «Think in Java» и других хороших книжек.
zagayevskiy
Так эта… может он вам помог не допустить баги?
VolCh
Помог незаметно как-то?
zagayevskiy
Да. Вот у вас был бы баг, связанный с типами, но его нет, потому что вам не дали написать неправильно. Бага нет, и вот так незаметно вас от него спасли.
VolCh
Вот что значит "не дали написать"?
PsyHaSTe
Почитайте блог PVS Studio. Там наглядно объясняется, почему нет смысла запускать стат. анализ на уже проверенном коде, потому что на все баги уже потрачено время на их починку, и вы их не найдете.
А компилятор — тот же стат. анализ, только не отключаемый.
VolCh
Я не пойму про какой сценарий речь идёт. Мой:
В какой момент мне не дали создать баг?
PsyHaSTe
Тогда вы не совершаете ошибок типов, что ведет нас к двум возможным вариантам:
zagayevskiy
Вы идеальный программист, пишете сразу без ошибок.
VolCh
Мои ошибки на другом уровне :)
PsyHaSTe
Ну тогда это пункт 1: вы не смогли логику вынести на уровень типов. Вот пример, как это делается.
В итоге вы, например, не сможете случайно в процессе «получения сообщения» вызвать метод «end()».
VolCh
Я не не смог, а просто не пытался выносить логику на уровень типов в чём отличном от песочниц, не вижу в этом смысла. В логике типов ошибиться гораздо проще чем в простом императивном коде, а отлаживать гораздо сложнее — банально в отладчике не посмотреть. Собственно этот код выглядит на первый взгляд как смесь «ФП головного мозга» и «статической типизации головного мозга» — я лучше со своей смесью «ООП головного мозга» и «динамической типизации головного мозга» останусь — она гораздо ближе к натуральному мышлению человека, по крайней мере к моему мышлению.
PsyHaSTe
Ну так вам говорят, что это полезно, вы говорите «нет смысла». Смысл в том, что можно написать работающую программу с первого раза, вообще ни разу не запуская ни под отладчиком, ни вообще в принципе.
Бытие определяет сознание. Программист на фортране способен программировать на фортране на любом языке программирования. Ваше мышление сформированно динамическими языками, и вам неприятно выходить из зоны комфорта. Но это необходимо, если вы вдруг хотите развиваться как разработчик.
Я уже не раз говорил, что игры с F# перестроили мое мышление, и позволили писать на моем обычном C# намного более структурированный и красивый код.
P.S. к этому
В логики типов ошибиться намного сложнее, потому что типы универсальны, а императивный код может не работать только для некоторых значений. Вторая причина — императивный код нужно проверять самостоятельно, а типы проверяет компилятор. Если типы сошлись, то программа корректна и в условиях тех ограничений, которые наложены, ошибок нет. Чем плотнее и больше ограничений наложено, тем больше выводов можно делать из того, что программа скомпилировалась.
Druu
В логике типов ошибиться сложнее, потому что эта логика обычно не скрывает ошибок. То есть типы с ошибкой либо просто не работают в целом, либо работают сразу правильно.
VolCh
Ну вот как какой-то тип с ограничением типа value > 0 "поймёт", что я решил почему-то что слово "неотрицательный" эквивалентно слову "положительный"?
VolCh
Я не делаю особой разницы между запуском программы и её компиляцией в контексте выявления ошибок. Какая разница будут сыпаться ошибки при исполнении или при компиляции? Ну чисто в теории есть разница, наверное, в пользу выявления ошибок при компиляции, но на практике ошибки рантайма куда более информативны. По крайней мере JS vs TS.
Мое мышление сформировано Pascal, С и C++ в то время когда JS-ом или PHP ещё не пахло.
Я уже несколько раз намекал, что я на динамических языках пишу так, чтобы максимально облегчить статанализаторам вывод и контроль типов. Если и использую сугубо динамические фичи, то с чётким осознанием плюсов и минусов.
Вот в ограничениях ошибиться гораздо проще, а понять где ошибся гораздо сложнее. Опять же я равниваю исключительно JS и TS — не знаю что там с F#, но всегда был уверен, что он про ФП, а не про типизацию, типизация у него такая же как у С# — строгая статическая. И, кажется, мы по разному трактуем слово "ошибка" — мне почти без разницы вылезла ошибка в компайлтайме или в рантайме. Если вылезла — значит допущена 100% и её нужно исправлять.
lgorSL
Разница огромна, если программа работает дольше времени компиляции или на удалённом компе. Очень печально осознавать, что обучаемая несколько часов нейронная сеть не была сохранена просто из за-того, что в какую-нибудь функцию передали не то количество параметров или ошиблись с их типом.
Причём может быть, что в одной ветке ветвления всё хорошо, а в другой — нет. И вроде бы работающая программа может оказаться не очень работающей, если что-то не совпало.
Рефакторить более-менее большой проект на питоне или на java/scala — тоже огромная разница. В статических языках та же IDEA хорошо справляется и можно совершенно свободно менять что хочется. Если не справляется — я узнаю об этом во время компиляции и тут же поправлю. С питоном среда разработки от тех же разработчиков справляет плохо и банальное переименование переменной/поля оставляет в различных местах "сюрпризы" на будущее.
VolCh
Если разница огромна, то может быть имеет смысл. Мне такое почти не встречалось, если не считать SQL, но это вообще свой особый мир.
ApeCoder
Какое тестовое покрытие у вашего кода?
VolCh
Близкое к 100%. Отдельных тестов на типы нет, если что.
ApeCoder
А в фоне тесты гоняются (типа https://wallabyjs.com/ )
VolCh
На сохранение исходников. Автоматическое отключено.
ApeCoder
И сколько они проходят? Просто контроль типов обычно делается фоном в процессе набора
VolCh
Сравнимо со временем проверки TS на одном и том железе на схожих по объёму проектах.
PsyHaSTe
А ведь все тесты можно было бы поменять на типы, и только поднять качество кода… )
VolCh
Как вы типами проверите реакцию системы на невалидные данные, пришедшие с базы или от пользователя? Или как вы типами проверите сложный численный метод, принимающий число и возвращающий число? Или система типов позволяет что-то вроде type A = tan(sqrt(number)) писать?
PsyHaSTe
Позволяет, почему нет?
Валидируете один раз IO, дальше все работает с точными данными.
VolCh
Не закончил мысль, не просто позволяет ли, а даёт ли гарантии, что для пи в квадрате значение будет 0?
Валидировать-то я могу и без типов. Как без хаков типа явного приведения или тайпгвардов прийти от почти нетипизированного json к какому-нибудь
Dictionary<EntityId,<Model<Entity>>>
?PsyHaSTe
При желании можно выразить. Я уже говорил, те же завтипы позволяют компилятору выводить утверждения в стиле "положительное число, делящееся на 3, от 14 до 25".
PsyHaSTe
Вы всегда способны продебажить все возможные состояния всей программы? Тогда я даже не знаю, если не совершать ошибки еще можно (я сам пишу на солидити, где нет дебаггера, со средней скоростью 1 небольшая ошибка на неделю работы), то подобном уровню аналогов просто не вижу.
VolCh
Нет, конечно, как и не могу с помощью типов их все описать.
PsyHaSTe
Ну не знаю, люди как раз с помощью типов и описывают. Напрмер
bar<T: Foo>()
означает "вызвать bar для любых типов, которые имеют метод foo". И проверит, что во всевозможных вызовах метод действительно есть.VolCh
А что он ожидаемо меняет состояние как проверить? На каждую область эквивалентности ожидаемого поведения свои типы где-то написать?
PsyHaSTe
Ну например можно эффектами например описать. Тогда проверите, что он в БД лезет (или не лезет, смотря какие требования).
KostaArnorsky
Компиляция всегда раньше выполнения, как можно не видеть разницу? Находит ошибки как можно раньше, это не основное ли правило… да всего.
VolCh
Не всегда, она может отсутствовать как класс или быть железно встроена в процесс выполнения.
creker
Значит то, что случайная где-то опечатка будет поймана здесь и сразу, а не в продакшене, когда исполнение дойдет до этого места. Рефакторинг, которые провели, но забыли во всех местах поправит, выдаст ошибки здесь и сразу. И т.д. и т.п.
Это очень весело, когда на том же питоне пишешь код и через год его исполнение внезапно доходит до какой-то давным давно написанной строчке, которая до этого ниразу не вызывалась. Как назло там вывод диагностики в лог, которую я потерял.
VolCh
Так я о том, что случайные опечатки мне компилятор не ловит.
creker
Это как это TS вам опечатки не ловит? Ошиблись в названии метода и компилятор ничего не скажет? Это вы как-то неправильно пишете получается, что блага статической типизации вам не помогают.
PsyHaSTe
Просто человек всегда помнит имена всех свойств и методов, и никогда не опечатывается. Бывает ведь.
VolCh
Не опечатываюсь настолько, что компилятор это замечает.
VolCh
Автодополнение
PsyHaSTe
Автодополнение без типов обожает подсказывать любую дич, но не то, что нужно. Потому что ему приходится буквально гадать на кофейной гуще. Особенно когда подключено много библиотек.
VolCh
А кто сказал, что без типов? IDE нынче хорошо выводят типы даже без явного их указания. А если им немного помочь парой аннотаций типа ` // var BaseClass|string $clasname` то и выражение типа `return new $clasname поймёт`.
vintage
...
Вас ничего не смущает?
ApeCoder
Меня смущает такое выборочное цитирование. Cмысл сообщения в том, что IDE и так хорошо справляются (без указания типов), но если им немного помочь (явно указать типы), то будут справляться лучше. У меня ощущение что вы просто не прочитали или не поняли большую часть того, на что отвечаете.
vintage
Компилятор тоже хорошо справляется с выводом типов, при этом ещё и услужливо скажет, где конкретно ему нужна помощь.
VolCh
Нет.
VolCh
Нет на этапе компиляции у меня таких ошибок. В основном инфраструктурные типа забыл добавить новый файл в tsconfig.json или мерж неудачно прошёл нарушив синтаксис.
zagayevskiy
Прекращайте уже писать С и С++ через /. Вы современный Си и современный Си++-то видели?
mayorovp
По части ногострелов они близнецы-братья
zagayevskiy
Понятно. Не видели.
eugenk
Какие например? Я использовал поделия от JetBrains для js и python. Говно-говном. Хотя JetBrains реальный монстр, у них пожалуй самые сильные алгоритмы анализа кода. Просто они не волшебники. И если типы не выводятся в принципе, абсолютно логично что они их вывести и не могут. Тут не JetBrains нужен, а Гендальф!
А вообще со своим нынешним хобби-проектиком (надеюсь правда это продавать !) сижу сейчас на vs code и доволен как слон. Там у меня ts + php. Всё взаимно дружит и отлаживается в рамках одного проекта, тока в путь! :)) Хочу сейчас в добавок научиться писать для vs code свои расширения, как я это делаю для эклипса.
VolCh
JetBrains прежде всего, да. Особенно если им всякие *doc аннотации о типах скармливать там где они вывести не могут.
А для PHP тоже говно-говном? Концептуально он мало чем от JS отличается же с точки зрения статанализа: интерпретируемый язык со слабой динамической типизацией. И даже typehints по сути сахар для
if (is_string($param)) throw new TypeError();
Zoolander
возможно, я не понял проблемы, но если проблема, что иногда не нужны некоторые поля, в TS есть необязательные параметры
Zoolander
Кроме того, можно сократить писанину, используя интерфейсы, расширяющие интерфейсы
IArticleFullData — это полный аналог IData из предыдущего примера
0xd34df00d
Зачем что-то там перечислять ещё раз?
..
— часть синтаксиса, да.justboris
Отвечу сразу и вам, и Zoolander
Такое решение через наследование или обертывание одного объекта в другой не всегда хорошо, если эти объекты не связаны. Просто, так совпало. Если потом DialogBodyOptions потребует еще одно значение, то оно внезапно высветится и в DialogOptions, а это может быть нежелательно.
Поэтому лучше держать эти типы отдельно и мириться с многословностью. Особенно, если учесть, что синтаксис в С# и Typescript более компактный, чем в Java. Может быть и там когда-нибудь сахар из Lombok включат в основу языка.
0xd34df00d
Ну это уж от предметной области зависит, вам виднее. Хотя мне кажется, что опции диалога включают в себя опции тела диалога, ибо тело диалога — ну, часть диалога, внезапно.
Чем вся эта статическая типизация хороша — рефакторинги вроде перетасовывания опций туда-сюда делать одно удовольствие: как только тайпчекер доволен, всё сразу работает.
justboris
Утверждение "как только тайпчекер доволен, всё сразу работает" сильно переоценено.
Допустим, у вас есть некоторый счетчик в коде
Внезапно, после неудачного рефакторинга или мерджа, у вас может проходить инкремент дважды
Компилятор счастлив, ошибки в типах нет, а баг в коде есть. И шансы накосячить таким образом намного выше, чем попытаться прибавить строку к этому числу.
0xd34df00d
Вполне разумное замечание. Я даже задумался, почему так получалось, что у меня за всю практику работы с сильно типизированными языками такого не встречалось. А потом я посмотрел внимательнее на ваш код и понял: такую вещь я скорее всего написал бы как
Ну или там
foldMap
, если вдруг для обработки следующего элемента надо знать, сколько уже было обработано.Нет ручных инкрементов — нет повода для копипасты.
areht
В принципе, согласен с предыдущим комментарием, но…
// тут много кода < — проблема тут
Когда в IDE код легко поправить в
то и проблем, внезапно, не проявляется. У вас просто не будет правок кода тут — ни рефакторинга, ни проблемного мерджа.
И проблема именно в IDE: когда рефакторинг делается в один клик и без ошибок — руки сами тянутся мусор спрятать. А когда мне приходится работать в блокноте — у меня тоже «много кода» появляется.
> А с поиском использования типов все еще лучше: вам не придется искать, где именно используется этот тип, если у вас не используются типы вообще.
А когда IDE не помогает — приходится себя убеждать даже от типов отказываться )
justboris
Это был только один пример. Есть и другие возможности наделать багов, от которых типизация не спасет: написать неправильное условие в if-блоке, забыть позвать метод или позвать но неправильный.
Именно эту мысль я хотел донести: типы избавляют вас не от всех багов, а только от части проблем, ценой затрат на описание этих самых типов. На определенном количестве и сложности кода эти затраты окупаются, а иногда нет. В таких случаях динамические языки (или динамические типы в строгих языках) оказываются удобнее в работе.
aikixd
С хорошо спроектированными типами _очень_ сложно вызвать не тот метод или забыть его вызвать. В ООП ветвление инкапсулируется внутри типа и это тоже спасает от проблем. Типы, в идеале, должны ограничить вас настолько, что единственный код который вы сможете написать будет верный.
Cerberuser
Но для этого надо сначала написать верную систему типов, которая будет по сложности как минимум такой же, как исходный нетипизированный код.
aikixd
Не будет. Типы декларируются, а не исполняются, то есть не имеют временнОго параметра. Выставив время за скобки, вы убираете целое измерение из проблемы. То есть вы создаете правила, которые работают всегда, а не только в конкретный момент исполнения.
Вот наприме метод:
Как бы вы не хотели, вы обязательно верно интерпретируете аргумент и вернете верный результат. Вы не можете вернуть строку или WebResponse или что-еще. Если метод выше требует ApiResult то вы обязательно вызовете этот метод, потому что иначе нужный объект вам не получить. Сам факт того что кто-то требует этот тип уже подсказывает вам что нужно сделать.
Это часто проявляется в работе с новыми библиотеками или незнакомыми исходниками. Не нужно лезть в доки для каждой новой вещи, нужно только найти тип который вам нужен и найти как его получить. Правильно сконструированный АПИ позволит это сделать только правильным образом.
В динамической декларации вы не знаете что должны передать и что получить обратно. Вы может даже не догадаетесь что этот метод нужно вызвать, так как объекты вокруг не могут сообщить вам что им нужно для работы.
mayorovp
Зато этот метод может запросто вернуть константу, которая никак не зависит от входного аргумента — и ваша система типов никак это не отследит.
aikixd
Система типов сделана не для компилятора, а для вас, что-бы вам работать легче было. Если вы специально пишите неверный код, то зачем вам ЯП? Переименуйте любой файл в экзешник и пусть он неработает.
mayorovp
Но вот это ваше утверждение остается неверным:
aikixd
В идеале. Я не просто так это слово использовал. На практике конечно нужно найти золотую середину, как и во всем.
mayorovp
Середину-то найти нетрудно. Но надо понимать, что эта самая «идеальная» ситуация возможна только в том случае, когда система типов программы по сложности превосходит саму программу.
aikixd
Не превосходит, а максимум обладает той же сложностью. На самом деле меньше, потому что еще нужно описать процессы (время), они будут описаны уже исполняемым кодом.
В динамической типизации вся сложность находится в исполняемом коде. В статической она поделена и с ней проще работать.
mayorovp
Время вам тоже придется как-то описывать типами, коли вы собрались защищаться от глупого кода, вроде константного ответа на любой запрос.
aikixd
Опять таки, если человек хочеть пострелять себе по ногам, флаг ему в руки. Это защита от дурака, а не от идиота.
Так можно дойти до вопроса, почему типы не проверяют код на соответсвие с ТЗ.
Meloman19
Ваша… Никакая система типов не отследит константу возвращает метод или нет. Это уже не зависит от системы типов в принципе.
Вопрос же не в этом. Данный метод сразу нам говорит, что он вернёт объект конкретного типа и знать мы об этом будет ещё на стадии написания программы, а не в рантайме.
impwx
Константы отслеживать очень просто, для этого например в C++ есть модификатор
constexpr
.0xd34df00d
Этот метод может вернуть константу (или пяток констант с банальным
if
'ом внутри), что и тесты не отследят. Что же делать-то?Впрочем, если достаточно упороться, то можно доказать статически, что параметр используется, если потребовать возвращать некоторого свидетеля, который предоставляется только параметром. Но это уже не TS и даже не хаскель.
areht
> На определенном количестве и сложности кода эти затраты окупаются, а иногда нет.
А я хотел донести мысль, что сложность и бажность зависит не только от типов, а от инструментов и практик. Если инструменты под типизированный язык есть, а под нетипизированный нету, то баланс сильно смещается в сторону «я лучше var name = (string)json[»name"] напишу, чем с динамикой свяжусь".
А корреляция между типизацией языка и доступностью инструментов есть.
Druu
Нет, можно написать вот прямо так как вы написали:
и выведет автоматом тип: 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}) — все хорошо.
justboris
Это будет не выведение типов, а implicit any, который отключается в strict-режиме Typescript. Именно возможность такого any где попало автор статьи и большинство комментаторов и называют происками Javascript в системе типов.
А в strict-режиме в этом коде будет ошибка, вот такая. Надо будет либо поставить any руками, либо описать типы по-нормальному.
Druu
implicit any будет для конкретных полей. Но у аргумента ф-и тип — не any, его тип — объект с наличием конкретных полей
taliban
habr.com/post/431250/#comment_19422410 вот камент самый правильный, там же и другая сторона, можете ознакомиться, дядька умный рассказывает с многолетним опытом в разработке
arty
Вообще-то научные исследования говорят обратное: что защищённость через покрытие выше, чем через сильную типизацию.
gBear
А можно ссылочку там, или выходные данные на эти «научные исследования»? А тож оно сильно «в зависимости от» должно быть. В смысле, это же столько от типизации должно зависить, сколько от системы типов.
arty
да пожалуйста результаты от IBM: people.cs.aau.dk/~bnielsen/TOV08/material/tdd-practice.pdf
khim
Не увидел там сравнения статических и динамических языков от слова «совсем».
То, что тесты, добавленные к системе типов сделают результат надёжнее — достаточно очевидно.
А вот что они могут заменить систему типов — не видел и не слышал ни разу, извините.
arty
система типов защищает только от ошибок типов
тесты защищают от ошибок типов и ещё многих других ошибок
конечно, если есть время и деньги, можно делать двойную защиту от ошибок типов: и системой типов и тестами
khim
А если денег нет — то можно писать тесты только для проверки на ошибки, не покрываемые системой типов. И это — будет быстрее и дешевле, чем писать на языке, которые требует написания тестов ещё и для обнаружения проблем с ошибками типов.
Ровно это, в общем-то утверждается в статье — и ровно это вы грозились опровергнуть.
arty
зачем это здесь сказано? ситуацию отсутствия тестов здесь никто не упоминал до сих пор
khim
arty
Снова уход от темы. Посмотри, пожалуйста, слова автора, которые я процитировал в самом начале этой ветки, и которые я опровергаю. Про остальные темы я не подписывался спорить.
khim
Смотрю:
Перевожу с русского на русский: «защищенность через покрытие — иллюзия» == «никто не гарантирует вам, что вы покроете все случаи тестами». И ешё: «тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов» — если таки прочитать всю статью целиком, а не видирать из неё пару слов, то можно будет заметить там такой пассаж: «Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.»Откуда видно, что автор категорически против приёмов, которые позволяют «обмануть» тайпчекер — что указывает на то, что он видит преимущество TypeScript именно в том, что проверки типов покрывают весь код, в то время как тесты могут этого не делать. А не будут они этого делать только в одном случае — когда они не написаны.
Рекомендую всё-таки спорить со статьёй, а не воевать с ветряными мельницами…
arty
К сожалению, минусом этого подхода является то, что видя своё уязвимое положение касательно какого-то аспекта, собеседники слишком часто не доводят его обсуждение до конца, а переключаются на другой аспект. Я помню о том, что аспектов много, но начав разбирать один, нужно это и закончить.
Переводить с русского на русский не нужно, каждый может перевести по-своему. Что написано, то и обсуждаем.
arty
когда я искал ссылку на статью, уже упомянутую выше, я видел исследования, которые и это тоже опровергают, но идти искать снова лень. Для разнообразия сторонники сильной типизации могут поделиться научными исследованиями вместо возмущённых постов. С данными в руках удобнее спорить.
khim
arty
Вот, совсем другое дело!
Только проблема этого исследования в том, что оно меряет среднюю температуру по больнице у несравнимых проектов. И, кстати, оно пытается подтвердить вовсе не тезис «можно писать тесты только для проверки на ошибки, не покрываемые системой типов. И это — будет быстрее и дешевле».
Я же сослался на исследование, которое один и тот же проект без тестов и с тестами делал, и это гораздо более честное сравнение, на мой взгляд.
khim
Как видите в том абзаце, который вызвал у вас отторжение речь идёт не о «тестах вообще», а о «невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors».
И да — мне, как и автору статьи, кажется это бредом. Type errors должен проверять компилятор! Именно чтобы освободить время и силы для проверок ошибок другого рода.
arty
Ага, здесь как раз тот случай, когда два человека по-разному поняли написанное. Моё понимание отличается, потому что я ни разу не видел юнит тестов, которые только type errors и проверяют. Я видел во множестве тесты, которые проверяют логику, ну и заодно типы тоже. По-моему, так и нужно делать. И в такой ситуации проверки компилятором добавляют мало пользы.
Раз так, то я сагрился на неправильно понятую цитату, а кроме неё мне в посте обсуждать нечего. Видимо, останемся при своих.
khim
null
илиundefined
(которые запросто могут вылезти из-за того, что свойство в базе или формочке пропало из-за рефакторинга), то вы оставляете в программе потенциальную уязвимость.А назвать проверку на
null
«проверкой на логику»… мне кажется, что это перебор… это в чистом виде проверка на тип…VolCh
Си — язык со слабой типизацией, некоторые его даже считают языком с практически отсутствующей типизацией.
0xd34df00d
Вы ведь знаете, что в достаточно мощных системах типов можно выражать в виде этих самых типов произвольные инварианты?
arty
Вы оспариваете утверждение, что тесты могут проверить больше, чем система типов?
0xd34df00d
Да. Я утверждаю, что ни одно из этих множеств не является подмножеством другого.
Наиболее хорошо это видно для утверждений, содержащих квантор всеобщности. Так что давайте начнём с банальщины: как вы конечным количеством тестов проверите, что функция сортировки действительно сортирует список? Как вы конечным набором тестов проверите, что порядок, определяемый оператором сравнения
<
, согласован с отношением эквивалентности, задаваемым оператором==
? Как вы проверите, что<
вообще порядок?khim
AlexHell
Вы же понимаете что чтобы написать эти тесты в JS (например) нужно приложить больше усилий и времени, на величину «проверки типов», по сравнению с вариантом «написать только тесты бизнес-логики» в варианте статической проверки типов.
Во 2х, я бы сказал что защищают они даже не в момент рантайма программы, а в момент думанья человеком. Вот я допустим читаю вашу JS прогграмму, я должен перечитать тысячи (потенциально) тестовых вариантов чтобы понять какой тип аргумента у вас в функцию передается и может ли там быть null и должен ли там быть int от 1 до 10 вместо enum общепринятого. Верно?
А еще мне документацию надо где-то отдельно от основного кода искать? Т.е не в коде я буду видеть
Move[] GetMoves (TypeOfShape) — для нормального ООП
а вот такое >>
function GetMoves (TypeOfShape)
причем тип возврата вообще не виден, и даже если задокументировать что вертает Move — вы уверены что это быстрей чем написать тип возврата? И тип аргумента?
kagetoki
Это очень популярная реакция людей на новую/непривычную концепцию, встречаю это регулярно. Все равно, что человеку, который всегда ходил пешком, показать велосипед. Мол, смотри, можно потратить немного времени, чтоб научиться кататься, а дальше будешь передвигаться гораздо быстрее, на что тот возражает, что мало того, что сначала надо потратить время, чтоб научиться кататься, так потом еще и велик рядом с собой катить — только быстрей устанешь.
Вы упускаете суть строгой статической типизации: она защищает от ошибок проектирования. Если система типов имеет еще и алгебраические типы данных, то при правильном проектировании она защищает от ошибок бизнес-логики. Это очень хорошо описано в книге Скотта Влашина "Domain Modeling Made Functional", настоятельно рекомендую.
Aingis
Даже интересно стало: как статическая типизация защитит от ошибок проектирования вроде той, что выбран алгоритм со сложностью O(n2) вместо O(n)?
0xd34df00d
А как тесты от этого защитят?
Статическая типизация, кстати, от этого защитить таки может, но мне нужно будет сесть и подумать, как это выразить в типах.
JC_IIB
Ого. Дико интересно было бы взглянуть на реализацию.
0xd34df00d
Я пока ещё не садился и не думал, это так, по дороге на работу пораскинул мозгами.
Не, ну банальный и очевидный способ — взять какую-нибудь агду, выразить на ней необходимую для O-формализма часть матана, а потом доказать всё, что надо, как вообще на агдах теоремы доказываются. Но это читерство, ибо это смешивает язык и метаязык, и немного не то.
Более прямой путь, но более трудоёмкий (ибо O-формализм говорит про асимптотику и поэтому, вообще говоря, неконструктивен, все соответствующие эпсилоны и дельты вас явно указывать никто не заставляет) — определить интересующую вас операцию особым образом. Пусть, скажем, вы хотите рассуждать о числе сравнений в сортировках. Тогда вместо
cmp : t -> t -> Ordering
вы выражаете алгоритм в терминах вродеcmp : Monad m => t -> t -> m Ordering
(потому что сам факт сравнения в данном случае — это эффект, а эффект — это монада), и функцию сортировки какsort : Monad m => (T -> T -> m Ordering) -> List T -> m (List T)
, и рядом пишете доказательство вродеРеализация функции
sortComplexity
будет доказательством, что функция сортировки делает не большеcomparisonsBound n
сравнений для списка длиныn
. Конечно, границу придётся определять точно, съехать на асимптотику не получится.Параметричность вместе с тем гарантирует, что функция не залезет внутрь монады и не обнулит счётчик, например.
Подставляете вместо
Counter
, например,Identity
-монаду — получаете обычную функцию сортировки.ПодставляетеPar
— получаете параллелизм нахаляву.Что-то такое, в общем.
Однако, очевидно, это асимптотика худшего случая. Как доказывать что-то амортизированное, я пока не очень понимаю.
sshikov
Я боюсь, что это эквивалентно например проблеме останова.
Aingis
Как раз тесты производительности давно придумали. Бывают даже стандартные постановки: «должно успевать за 10 мс и умещаться в 64 ГБ памяти» — в том числе на олимпиадах. Но почему вы переводите тему? Я про тесты ничего не говорил.
0xd34df00d
Да, вы о тестах не говорили, просто довольно превалирующий контекст комментариев был как-то о них, сорри.
«Успевать за 10 мс и помещаться» ничего не говорит об асимптотике, кстати.
gBear
И где в это работе нашли, что
?!Она, мягко говоря, вообще про другое.
transcengopher
Но в этой статье не сравнивались покрытие тестами и сильная типизация, только сам факт наличия или отсутствия тестов (если я не прав, то укажите, пожалуйста, конкретное место в статье). По сути, статья является описанием опыта введения TDD в команду.
Кстати, замечу, что в отсутствие строгой типизации упомянутых в статье 2930 юнит-тестов, 100 тестов производительности и >400 интеграционных тестов было бы явно недостаточно, так как пришлось бы писать ещё пару тысяч тестов на то, что
DeviceFactory::createLightBulbDevice
действительно возвращает нечто, что ведёт себя в соответствии контрактуLightBulbDevice
, аDeviceEvent::getType
никогда не возвращает строку.sshikov
Можно ссылочку на исследование?
arty
пожалуйста habr.com/post/431250/?reply_to=19423328#comment_19423456
impwx
Сильная типизация и покрытие тестами не исключают друг друга, а наоборот — вместе они дают лучший результат.
arty
я отношусь к тем людям, которые считают, что порой потраченные на удовлетворение компилятора ресурсы мозга можно было бы применить лучшим образом, особенно если отсутствие ошибок уже проверено тестами, но вот это своё мнение я доказывать не буду
khim
А ведь ровно ссылочку на оправдание именно такого мнения от вас и просили…
arty
ссылочку просили на оправдание не этого мнения, а слов «защищённость через покрытие выше, чем через сильную типизацию», не нужно смешивать сущности
khim
Это вы, кажется, их смешиваете. Потому что если «защищённость через покрытие выше, чем через сильную типизацию», то это значит, что мы можем взять любой язык без сильной типизации (JavaScript или там PHP), написать на нём тестов — и получить меньше ошибок, чем на C++, Java или, скажем, Rust'е.
Тот же пример, что вы привели — относится скорее к доказательству того, что «если ходить в куртке и в шапке, то вероятность заболеть ниже, чем если ходить в одной куртке». Что, в общем, достаточно очевидно.
arty
мои слова в другой ветке заодно отвечают и на этот комментарий habr.com/post/431250/?reply_to=19423678#comment_19423712
arty
а своё мнение про продолжение этой ветки я уже высказал
Druu
Меня вот этот вот тезис всегда удивлял. Я могу понять, когда про потраченные ресурсы мозга заявляет человек, который пишет на идрисе. Или на хаскеле (хотя бы). Но если мы говорим о джаве? Или вообще в контексте тайпскрипта, в котором типы специально так спроектированы, чтобы при должны настройках компилятора код просто брал и работал? На что и кто там "ресурсы мозга" тратить собрался?
eugenk
А Вам не кажется что не стоит тратить ресурсы мозга на написание тех тестов, которые заменяются проверками компилятора?
transcengopher
А не стоит эти ресурсы рассматривать как «потраченные на удовлетворение компилятора», и тогда всё сойдётся. Описание типа, например, некой, уже будет включать в себя как минимум два теста:
Есть, конечно, случаи, когда по какой-то причине нужный Вам тип невозможно выразить в рамках системы типов некого языка, однако это, по моему глубокому убеждению, такие вещи являются скорее недостатком дизайна, и возникают не настолько часто, чтобы быть аргументом против типизации вообще.
Приведу пример (я буду предполагать что система типов моего языка достаточно развита, чтобы всё это поддерживать):
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)
вернёт строго ожидаемое значение. Так система типизации позволила нам писать тесты только одного типа вместо шести или семи, которые я обрисовал вначале, все остальные группы тестов «написаны» и выполнены компилятором.Более того, обычно я даже не задумываюсь над тем, насколько огромно на самом деле количество тестов, которые я не написал вручную из-за систем типизации, но которые, тем не менее, постоянно выполняются в моём коде.
faiwer
Я не думаю, что вам удастся найти живого JS\PHP\Ruby\Python программиста, кто будет писать все эти ваши тесты. Будет 1 тест вида:
И всё.
VolCh
В целом, да. По крайней мере если нет явного требования типа "при передаче не массива выбрасывать TypeError" — тогда добавится ещё один.
VolCh
Для общего развития поясните, пожалуйста, как компилятор будет это проверять в случае каких-нибудь сложных динамических вычислений результата. Ну, пускай, длина будет браться из базы.
mayorovp
Если длина берется из базы — это как раз самый простой случай. В базе данных длина может быть любой, а значит задачей программиста является проверить что значение не меньше нуля. Компилятору остается проверить что программист проверил это условие.
VolCh
Вот как компилятор проверит, что программист проверил? Я навскидку могу с десяток способ проверки сделать. Ну, например, проверить старший бит хоть c XOR, хоть с AND маской. Или сравнить n === abs(n). Компилятор всё это учтёт?
zagayevskiy
Если компилятор вас в таком случае не поймет, то пока вы ему это не докажете, ничего не скомпилируется.
Druu
Ну делаете какой-то тип вида Either<Nat, BelowZero>, теперь чтобы со значением работать вам надо при помощи проверки выделить Nat.
Компилятор освободит ваших коллег от необходимости писать вам замечание на ревью :)
PsyHaSTe
С теми же const generics мы можете Описать сигнатуру
И например такой код во время компиляции скажет unreachable code:
Очень полезно во многих сценариях. Например, когда мы хотим работать только с квадратной матрицей. С этой штукой мы можем выразить это в типах и получить ошибку компиляции, при попытке посчитать например определитель для матрицы 2х3. А так остается только эксепшн в рантайме кинуть.
бтв не очень понял вопрос. Ну берется число из базы, что с ним не так? Делаете:
Компилятор проверил, Как мы преобразовали типы, и что мы не забыли случай отрицательных значений.
sshikov
Стоп, стоп. С каких это пор тесты гарантируют отсутствие ошибок?
Alexey2005
До тех пор, пока программисты не научатся в обязательном порядке писать документацию, я буду убеждённым противником слабой динамической типизации.
На самом деле это не так уж сложно. В конце концов, пользоваться репами вместо папочек привыкли, и тесты писать тоже как-то научились. Но вот мысль о том, что недокументированный код == несуществующий код для большинства программистов всё ещё является глубоко авангардистской.
В итоге подавляющее большинство проектов не содержит вообще никакой документации кроме традиционной «шапки» с лицензией. Зачастую даже о зависимостях для успешной сборки приходится догадываться.
И вот разбираться в таком коде, если он написан на чём-то динамически типизируемом — это просто адский ад. Вот например, прямо сейчас я пытаюсь разобраться в коде PDF.js, для которой разработчики конечно же не завезли вменяемых доков, а примеры покрывают от силы 2% возможностей библиотеки.
И вот мне блин приходится гонять её под отладчиком, чтобы иметь возможность в рантайме проверить: что же, чёрт возьми, попадает в коллбэк при разрешении очередного промиса. Потому что из кода это как-то неочевидно.
Alexey_Zal
Как то я пытался разобраться с модулем lirc для Linux. Он был написан со статический типизацией и комментариями. Но мне это не помогло! В итоге всё всегда упирается в программиста. Мир не стоит на месте и разработка по это всегда компромисс между скоростью и качеством. И динамика, по моему опыту, даёт результаты быстрее. А если проект полетел то можно его потом причёсовать хоть до посинения. Один из первых применил это правило некто по фамилии Гейтс, и причёсывает своё По до сих пор.
khim
Alexey_Zal
Гейтс пожертвовал качеством кода в угоду скорости. Я об этом. И динамика в моем понимании — это жертва ради скорости разработки.
PsyHaSTe
Тогда стоит уточнять, потому что как статья, так и общеизвестное определение вполне конкретно, и про скорость разработки ничего не говорит.
Meloman19
Вы всё же о другом говорите. Если вы с нуля пишете свою программу, то динамика может помочь, но если вдруг начнёте использовать стороннюю недокументированную библиотеку, то тут и появляются проблемы, которые описывает Alexey2005.
Alexey_Zal
Я о том, что если моя подготовка позволяет разобраться с задачей, то мне без разницы какая там типизация. А если нет — то ничего не поможет. Хотя, конечно, я немного лукавлю красивый код всегда читать легче, но к системе типизации это мало имеет отношения.
Meloman19
Я вас сразу так и понял.
Просто вы отвечали на комментарий человека, который говорит, что без документации "динамические" библиотеки превращаются не в простой чёрный ящик, а в ящик с чёрными дырами на входе и выходе.
eugenk
Тут дело даже не в документации. А в том что приходится держать в голове массу деталей, которые при статике берёт на себя компилятор, а хорошая IDE с автокомплитом ещё и подсказывает. Я пытался писать на js. В итоге запутался на третьей тысяче строк кода. Хорошо добрые люди подсказали, что существует такая штука как ts (тогда он был совсем-совсем новым).
eugenk
Спасибо за статью. Что очень бы хотелось, это почитать мнение Вашего друга. Сам начинал с ассемблера. Казалось бы штука с типами и ООП вообще перпендикулярная. Однако всегда думал отталкиваясь от данных. Есть задача. Она работает с такими-то структурами данных. Их обрабатывают такие-то процедуры. И т.п. Фактически то же ООП, к которому перешел вполне безболезненно и естественно. Динамическую типизацию считаю Абсолютным Злом. Тем не менее вижу, какие сложные и нетривиальные библиотеки пишутся на том же js или на питоне. Из чего делаю вывод, что чего-то сильно не понимаю. Поэтому очень хотелось бы почитать статью подобную Вашей, но от сторонника динамической типизации. Пользоваться ей скорее всего никогда не буду (в конце концов мне уже под 60). Но просто интересно чисто с научной точки зрения. Может уговорите своего друга тут высказаться?
movl
Интересно что вы хотите услышать в ответ? Что способ типизации сильно волнует разработчика при выборе технологии, или что динамическая типизация это хорошо, а статическая плохо?
Мне кажется для большинства разработчиков инструмент просто является инструментом, и выбираться исходя из конъюнктуры, на которую способ типизации оказывает не очень большое влияние. Короче говоря, JavaScript стал популярным не только потому что он является языком с динамической типизацией; библиотеки, интерпретаторы и все прочее для него пишутся тоже не только по этой причине, как и для всех других языков. Следовательно ваша постановка вопроса не совсем корректна.
> Динамическую типизацию считаю Абсолютным Злом
И раз уж Вы допустили такое высказывание, то я допущу немного софистики. TypeScript стал популярен, во многом исходя из того, что был запрос на статическую типизацию для JS, и те кто пишет/писал на JS, в этой популяризации сыграли основную роль. Дальше совсем прикольно получается: выходит, что в целом, разработчики, которые принимают такие гибкие технологии как JavaScript, со всеми плюсами и минусами, готовы принять и нечто более строгое, такое как TypeScript, да впрочем и любые другие технологии и принципы: например функциональщина также очень распространена. А в обратную сторону, в случае разработчиков, вот таких как автор, сталкиваешься с какой-то древневековой косностью мышления и иррациональной верой в то, что всякому необходим код, который будет работать веками, а данные должны быть пропитаны десятками абстракций, якобы для какой-то еще более абстрактной «правильности» кода.
PsyHaSTe
Второе.
Потому что все примеры, что я видел, легко разбиваются современными системами типов. "А вот надо много писать" — не надо, всё автоматически выводится. "А вот надо 2 разных типа вернуть" — не вопрос, вот вам ADT… Ну и так далее.
В итоге я не видел ни одной статьи, где не использовались бы мифы про "в типизированном языке в 10 раз дольше писать, потому что все типы надо описывать" и подобных, где прямо показано, вот преимущество, в таких-то случаях.
Потому что для меня, в скриптах до 500 строк разницы на чем писать нет, потому что и то и то можно написать за пару дней на любом языке. А сложный проект лучше писать с поддержкой типами. В итоге очень узкий кейс, где динамические языки имеют минорные плюсы просто не дают оснований на то, чтобы тратить приличное время на их полноценное изучение.
justboris
Прямо совсем все типы автоматически выводятся? Скорее всего нет, какие-то все равно придется описать. Вот так мы снова и приходим к аргументу "не хочу, не буду писать никаких типов", за который топят некоторые сторонники JS (не я).
С другой стороны, если можно вывести типы без явного их указания, то может быть, можно то же самое сделать с Javascript, анализируя функции и как их вызывают? Бинго! Есть flow – запускаем этот инструмент поверх ванильного Javascript и получаем предупреждения о несовместимости типов. При желании можно и аннотации типов добавить, но это уже отступление от канона "не хочу писать никаких типов".
IvanNochnoy
Кстати, то же самое может и TypeScript: Type Checking JavaScript Files
PsyHaSTe
По месту использования — не нужно вообще. Ну вот пример на расте (Который типизирован выше крыши):
который на JS будет выглядеть как-то
Разницы почти нет.
В местах объявления типов и методов — придется писать, но jsDoc и прочее люди и так пишут. Типы это и есть такая документация, только всегда актуальная.
Типы, они как прививки — если 100% кода ими покрыто, они очень действенны, если 95 — то уже поменьше, а если 90 и меньше — то уже ничего не спасет этот проект.
movl
Ваш «очень узкий кейс» с «минорными плюсами», это и есть ключевая причина успешности интерпретируемых языков с динамической типизацией. Вспомните изначально для чего создавались такие языки как PHP, JavaScript, Bash и другие. То что они получили развитие, это мне кажется логичное явление после успеха. То что способ их применения мог значительно усложниться, это всего лишь некоторый этап развития, в следствие успеха. И например TypeScript, я считаю, достаточно естественным развитием некоторой технологии, которая внешне сильно ограничена (теми же браузерами), и в которой не хватает средств для построения больших абстрактных систем. Причем, я считаю, что TS появился слишком поздно, но если бы не он, то многие писали бы на каком-нибудь Haxe или Flow. У меня в голове и мысли нет противопоставлять различные способы типизации в категории: хорошо или плохо — этим я пытался показать абсурдность подобных обсуждений.
Давайте так подытожим:
Для того, чтобы загрузить с сервера некоторый текст и посчитать сколько раз в этом тексте встречается выражение «Hello, world!»: строгая статическое типизация — плохо, слабая динамическая — хорошо.
Для того, чтобы построить систему, в которой используется сотня отношений в базе данных, и где бизнес-логика, которая связывает эти отношения, описывается огромным количеством всяких условий: строгая статическая типизация — хорошо, слабая динамическая — плохо.
А вот если задача находится между этими двумя случаями, то ответ не может быть таким однозначным. И например если бы передо мной стояла задача реализовать нечто не очень объемное и не очень маленькое, а самое главное быстро, то для меня та технология, которой я пользовался в последнее время, имела бы значимый вес при выборе, независимо от способа типизации в ней.
PsyHaSTe
Динамические слабые языки хороши обычно во всяких сценариях, когда ошибочный результат не так уж сильно заметен. Типа нарисовали врага на полпикселя левее — ну и хрен бы с ним. Поэтому они почти всегда используются как скриптовые языки в игровых движках и редакторах карт.
Я бы согласился, но буквально полгода назад был эксперимент, который мне было интересно провести: мы взяли небольшое JS-файл на 150 строк, который рисовал какую-то фигню, снежинки там и все такое. И переписали на TS. Переписывание заняло минут 30 (у человека, который TS в глаза не видел, я ему его рекламировал), и мы нашли 3 бага, в основном связанных с тем, что где-то записывались свойства, которых у объекта нет, а где-то они, наоборот, читались. На 50 строк кода была одна ошибка. Который в проде использовался больше года, и постоянно просматривался.
Таким образом, мое мнение, что динамические слабые языки удобнее лишь тем, что вам не нужно особо ничего знать, чтобы начать на них писать. То есть пока человек изучает там интерфейсы, паттерны проектирования, DI, вот это все, человек на JS уже давно написал прототип, добавил функционала, и стрижет денежки.
Но вот если человек вложил десяток лет в понимание, что такое типы, как работать с ними, то он пусть даже потратить на 20% времени больше на реализацию того функционала, что сделал неопытный JS-ер чуть быстрее, зато его решение будет работать. А главное, ему не придется помнить все длинные списки хаков и нюансов разных фреймворков, браузеров и т.п.
По сути есть два инструмента, один из которых слегка хуже работает в сценариях типа А, и нормально в сценариях типа Б, а другой нормально работает в сценариях типа А, и почти не работает в сценарях типа Б. При этом разница между "чуть хуже на сценариях А" и "нормально на сценариях А", при достаточном вложении времени в инструмент 1 такова, что совершенно теряется смысл изучать инструмент 2. Просто потому, что это слишком большая когнитивная нагрузка при практически отсутствующей разнице в результате.
VolCh
А если уже оба инструмента знаешь (скажем, на момент изучения инструмента 2 инструмента 1 не было в принципе), то почему не выбирать инструмент по сценарию?
PsyHaSTe
Потому что я не вижу особого применения динамическим языкам. Как сказал товарищ в этом топике или соседнем, его рекорд: 1300 строк идеоматичного ООП кода в день. А больше 1000 строк это уже объем, где статика предпочтительнее.
Таким образом, если у вас проекты длятся в среднем больше одного дня, статика предпочтительнее. Если нет, то нет, конечно.
zagayevskiy
Ну-ну, на этот «идиоматический ООП» ещё посмотреть надо. Там, ЕМНИП, речь была в контексте генерации кода. Может он там 1000 строк дата-классов нагенерил и радуется.
VolCh
По-моему, довольно наивно выбирать инструмент по количеству строк кода.
PsyHaSTe
Никто не выбирает. Но мое персональное мнение, что писать больше тыщи строк кода без типов — себя не любить.
eugenk
Вы немного не поняли. Я признаю что разработчики на чистом js вполне продуктивны и пишут очень неплохие вещи. И мне дико любопытно понять их стиль мышления, который явно отличается от моего. Вот и всё что я хотел сказать.
burfee
Прихожу на проект. Там 10 индусов писали полгода. Оно разваливается. Прод через неделю. КАКИЕ ТУТ НАФИГ ТИПЫ?!?!))))
Ну на js помогает линтер.
Понятно что если есть время, перспектива и др. ресурсы, то лучше жить с типами. JS изначально был языком для «подхачить страничку быстренько». Сейчас все меняется, и пришли другие масштабы, на которых лучше жить с типами. Тем не менее в динамике тоже есть преимущество для некоторых оптимизаций, когда дженериков становится мало.
impwx
А можно пример? Я предполагаю, что в языках с гибкой типизацией и макросами можно сделать то же самое, только как следует, а не monkey patching-ом.
VolCh
js js'у рознь. Я когда пишу на чистом js взял в привычку натравливать flow или ts на него, проверяя достаточно ли информации в коде для автовывода.
Druu
А в чем проблема просто писать на flow или ts? Какие-то, честное слово, извращения у вас
VolCh
Придётся не игнорить там, где вышли ошибки типов.
b1de0
Проблема в том что у JS слабая динамическая типизация, а Python сильная (строгая) динамическая типизация. Это убирает в питоне много проблем, так как он не дает совершать действия которые не свойственные определенному типу.
Приведу пример: JS — «Hello» + 123 = «Hello123»; Python — «Hello» + 123 = TypeError: must be str, not int.
Мне кажется что(как многие писали выше) большинство вопросов у сторонников того же C# к JS связаны с слабой типизацией, а не динамической.
P.S.: C++ имеет статичную но слабую типизацию.
eugenk
А вообще-то угораю конечно. Очень умные люди, разработчики движков, сушат мозги над тем, как из кода на js вытащить типы и использовать их для всяких хитровывернутых оптимизаций в виртуальной машине. Потом другие, тоже очень неглупые люди, создают такие штуки как typescript, scala.js и т.п, чтобы как-то подружить ту же скажем браузерную разработку с типами. Очень напоминает тот самый анекдот, где два ковбоя задаром говна поели :)))
AlexHell
Я начинал изучать программирование с StarCraft1 GUI, потом Warcraft3 (тоже с GUI) — там даже не код, а парадигма натаскивания / выбора из dropdown подходящего (ограниченного множества) — события и условия и действия.
В техникуме на программера изучал JS — это был 1й язык именно написания кода (а не GUI).
Потом был assembler :) круто показало внутрянку.
И всегда на протяжении кодинга хоть на JS хоть на asm, я думал как с точки зрения сущностей / объектов… у нас был преподаватель на объектах чуть помешанный, ну там «очередь», «слот очереди», «животное», хотя и делали то на массивах параллельных, ну типа x[], y[] вместо объекта Obj {x, y}
Т.е тут дело как это в голове уместить. И абстракции в духе «очередь» и «слот» и т.п. ложатся на мозг человека очень хорошо… уж не помню по каким результатам исследований, но чел не может в голове держать несвязанные разрозненные концепции — далеко не уедешь, надо пытаться свести к формальной модели с определенными входами и выходами, и разными уровнями абстракции… в том числе несколько абстракций нельзя держать, вроде как 7 шт и дале уже забываешь о других объектах…
Если же исходить не из ООП объектов (которые по опыту можно и в JS и а ASM даже, главное концепция в голове), а строить какие-то сотни функций (function DoX(a, b, c) в JS) то в них очень просто запутаться… помню писал и шашки, и крестики-нолики, шахматы начинал — все на JS, по опыту знаю что путался… в итоге всеравно к объектам приходил, и к разным уровням абстракции, вроде это «поле» а это «фигура» а функция построения возможных ходов принимает «тип фигуры» в шахматах например… на Си без плюсов а потом и на C++ переписывал шахматы и явно было видно что статические типы облегчают концепции, в плане возврата в этому. Да это не Write-only, а когда недели/месяцы пилишь прогу (пусть и учебную в начале) то облегчают.
А потом пришел к нормальному ООП в С++ и C# — это было круто и никогда не уйду от них в пользу динамических (уж не силен в терминологии, строгая там или нет и т.п свойства) как в JS.
Да даже на статических и ООП заточенных типа C# бывает код абы-как написан, не используя нормальные общепринятые концепции… это как описывать кучу функций, кучу get-set или public свойства что по сути одно и тоже, но без адекватного поведения внутри объекта, и инкапсуляции… т.е добавить в объект свойство — это прошлый век (начало программирования), надо в него еще поведение заложить и инкапсулировать (чтоб все кому не лень не пытались извне копипастить, а если этот функционал в подчинении класса он и должен быть в нем описан — так понятней где искать — потом, при чтении кода и доработках)… имхо от недостатка опыта, они не допоняли концепции объектов со свойствами и поведением.
Что до паттернов, которые типа унивесральные (даже к JS применимо есть паттерны), суть их в том чтобы другой чел (или ты через годы) вспомнил/понял как оно тут работает в соответствии с общепринятыми джентельменскими нормами… и не 100% программеров придерживаются, но они формально есть, все эти «State в динамических языках не нужны» от непонимания что есть самый State, т.е может у вас вся программа из иммутабельных объектов, а тут у вас есть класс который имеет внутренный вложенный private (или нет — в JS) класс и вот он мутабельный State т.е вы его там гоняете и собираете по колбекам (парсер какойто иерархии, да хоть XML/JSON/SVG для наглядности) и в итоге вот у вас StateOfParsingProcess — вот он формально есть. Даже если вы его не напишите в виде статически class StateOfParsingProcess и не обозначите private — он в вашей голове есть. Но вот напишете — потом вспомнить и вам же будет проще.
Какие-то люди могут не понять как запрограммить процесс парсинга какогото формата файла на JS (не говорю про какие-то спец инструменты вроде разбора грамматик и построяния деревьев и т.п), вот они придумаю свою реализацию на любом языке, и она будет не понятна другому кто будет читать, потому что не то что не очевидна (это тоже), в ней может и логики не быть, потому что ее и не рефакторили, и сделали «чтоб работало» / write-only. А комуто это читать и допиливать вероятно.
prijutme4ty
У вас в голове дикая путаница.
а) «Мой друг хотел делать наоборот — сразу писать код и не описывать никакие типы. Он не был готов определять проблему в виде семейства типов».
«Это не значит, что динамически типизированные языки не нужны (на самом деле я и правда думаю, что не нужны). Это значит, что при их использовании, тебе нужно отказаться от ООП, как главенствующей парадигмы».
«Канонические реализации большинства ООП паттернов пестрят кейвордом Interface, который совершенно бессмысленнен в динамически типизированном языке.»
Вы очень узко смотрите на ООП. ООП — это не какая-то одна цельная парадигма, а очень широкий спектр различных вариантов ввести в язык объекты.
Ну вот, например, вы сетуете на то, что в динамически типизированных языках интерфейсы не декларируются явным образом (хотя используются постоянно). А вы в курсе, что статически типизированный ООП-язык C++ не имеет интерфейсов вообще, и предлагает совершенно другие способы решения проблем?
Динамические языки вполне себе описывают типы — возьмите Smalltalk и всех его последователей. Мне как человеку из ruby-мира с его тщательно продуманной системой типов просто смешно читать про то, что динамическая типизация — это отказ от продумывания пользовательских типов.
JS — не лучший и далеко не самый типичный пример ООП-языка с динамической типизацией.
б) «Паттерны — штука кросс-языковая». Эээ? Нет, паттерн — это всего-лишь типичное решение типичной проблемы. И в разных языках наборы задач и решений разные. Множество паттернов упростилось (вплоть до полного вырождения) с появлением в языках лямбда-функций, это же не повод поливать лямбды грязью и требовать возвращения всех тех сложностей, которые приходилось без них решать заведением специальных классов.
в) «Главный плюс статической типизации — гарантия. Если ты используешь ее в одном модуле, а в другом нет, ты просто потратил время и силы на описание и разработку типов, а никаких гарантий не получил.»
Часть модулей вашего кода получила гарантии согласованности. Если код настолько немодульный, что вы не получили никаких гарантий, то это просто плохой код. Статическая типизация его не исправит (скорее уж наоборот, законсервирует лишние взаимосвязи).
в') Ага, а потом ваши гарантии разбиваются о какую-нибудь ковариантность коллекций, хе-хе.
sshikov
>Множество паттернов упростилось (вплоть до полного вырождения) с появлением в языках лямбда-функций
Взамен появилось множество новых.
prijutme4ty
Это ли не аргумент против того, что "паттерны — штука кросс-языковая"?
sshikov
Ну… не уверен.
Ну т.е. скажем фабрика — это паттерн в тех языках, где функция не является примитивом 1 класса, а там где функции есть, и пишутся легко — фабрика как правило не нужна, потому что это не более чем функция, и паттерн совершенно вырождается.
С другой стороны, map/reduce/filter/etc — это вполне себе паттерн для многих языков, претендующих на функциональность, и выглядит практически одинаково в Scala и в JS, с точностью до типизации и ленивости (т.е. внешне одинаково, а работать конечно будет по-разному).
Free_ze
Не могли бы вы раскрыть тему фабрики? Паттерн состоит в том, чтобы порождать различные (по типу) объекты одного семейства, основываясь на некоторых на некоторых параметрах. Как это теряет актуальность, если появляются функции как объекты первого класса?
PsyHaSTe
Потому что функия с паттерн матчингом — совершенно типичная ситуация в ФП, и не требует выделения целого паттерна.
VolCh
Условие было " функция не является примитивом 1 класса". если функции есть, а паттерн-матчинга нет?
PsyHaSTe
ФП без паттерн матчинга это что-то из разряда фантастики.
Собственно, не бывает ФП языков, бывает ФП стиль написания кода. И паттерн матчинг является одним из ключевых кирпичиков фундамента, без которого остальное имеет мало смысла.
VolCh
Напомню, что мы не про ФП, а про языки где функция — примитив 1 класса.
sshikov
По большому счету любая фабрика — это вообще функция. С параметрами. Разве нет?
>теряет актуальность
Я немного не так сказал. Речь о том, что с появлением скажем лямбд, большинство паттернов, описанных в той самой книге GoF, становятся настолько тривиальными и очевидными, что по большей части уже не нужны. В частности, в виде отдельных классов. На примере Java 8 это очень ярко проявилось.
mayorovp
Паттерн называется не «фабрика», а «абстрактная фабрика», и состоит из как минимум двух классов: абстрактного класса фабрики и конкретной реализации.
Он теряет актуальность как только появляется возможность передавать вместо абстрактного класса обычную функцию.
sshikov
Вообще-то у GoF в книге две фабрики. И одна из них совсем не абстрактная.
mayorovp
Да, есть еще и Factory method. Но у него сильно сократилась область применимости, когда стали заменять наследование на композицию.
В любом случае, фабрика-функция — это отдельный паттерн.
Druu
Типы всегда находятся во главе. Динамическая типизация — это не отсутствие типизации, иначе бы вместо "динамическая" мы бы говорили "никакая".
Просто в статическом случае работать с типами проще и удобнее. В динамике — с-но, менее удобно и сложнее, требуется большая квалификация, большая дисциплина. Человек, который умеет писать на динамике — прекрасно понимает, как использовать статику. Если не понимает — ему в динамике не место.
Вы вот пишите, что у вас проблемы с js-подходом — они не от того, что вы начали со статики, а от того, что вы просто недостаточно квалифицированы, чтобы писать качественный динамический код. При этом достаточно квалифицированы, чтобы осознавать этот факт (что, кстати, для огромного числа людей, пишущий на js, не выполняется).
eugenk
Пожалуй самый интересный и (если так можно сказать) «обещающий» комментарий из всех сторонников динамики. Не могли бы Вы написать подобную статью? А желательно показать на практике, где динамическая типизация даёт серьёзное преимущество перед статической? Я не подколки ради, мне просто интересно в этом разобраться.
Druu
А вы точно на мой комментарий отвечали? Я не говорил, что динамика дает преимущества, я говорил, что на ней писать труднее, едва ли это можно назвать преимуществом :)
Разговор о преимуществах вообще не совсем верный, правильнее говорить о возможностях. Статическая типизация дает возможность на уровне типов фиксировать инварианты и автоматически их проверять.
Динамическая типизация сама по себе никаких возможностей не дает, т.к. это не фича, а ее отсутствие. Но косвенно она дает те возможности, которые плохо совместимы (или вообще несовместимы) с типами.
Например — макросы с типами плохо совместимы (по-этому самые годные макросы — в динамических лиспах), рефлексия — плохо совместима (по-этому самая годная рефлексия — в каких-нибудь рубях и тех же лиспах, вроде CL), нормальный репл и image-based разработка (смоллтолк и снова лиспы) — фактически в рамках статики невозможны.
eugenk
Прошу прощения, наверно не так выразился. Хотелось бы немного проникнуться методами, которые используются при таком подходе. Понять как это делается. Советовать мне что-либо написать самому, дело достаточно бесполезное. Ибо пишу уже очень давно и только на статически типизированных языках. Сами понимаете, что я напишу. С тем что Вы сказали, согласен лишь отчасти. Макросы например это те же шаблоны С++. Достаточно уродские. Но скажем в D эта штука уже вполне удобоваримая. Нормальный репл прекрасно живет в Scala или Haskell. И даже дружит с jupyter. Рефлексия хоть и не без бубнов, неплохо работает в java. Впрочем возможно опять Вас не понял. Честно говоря просто не могу себе представить концепций, невозможных в статике, но при этом практически полезных. Напоминаю однако, что это лишь моё мнение. Не претендующее на истину, а судя по популярности того же js от истины весьма далёкое. Вот и пытаюсь к истине как-то подобраться.
0xd34df00d
Это у вас просто макросов нормальных не было. Вроде хаскелевского Template Haskell, например.
Про скалу не знаю, а в хаскеле он так себе. Жмёшь
:reload
, а все локальные байндинги пропадают.Да, есть костыли вокруг этого, но они всё равно костыли.
Druu
Просто нет.
Что происходит в этом прекрасно живущем репле, когда вы сперва написали ф-ю f с типом Х, где-то ее поюзали в другой ф-и, а потом переопределили f так, что у нее тип стал Y?
Ну тут такая ситуация, что кому-то и плюсовые темплейты — макросы, кому-то в джава есть рефлексия, а кому-то — и кобыла невеста.
Все концепции, условно говоря, возможны в любом тьюринг-полном языке. Вопрос в том, как они реализованы, насколько широкие возможности дают и насколько удобно их использовать.
mayorovp
Ошибка происходит: функция уже определена.
Druu
То есть, ваш репл невозможно использовать для image-based разработки. О чем и шла речь.
0xd34df00d
Хаскелевский репл — это просто набор вложенных лет-байндингов, происходит что и ожидается:
А что надо?
Druu
Надо иметь возможность переопределения старых ф-й (то есть в вашем примере в g должна быть вызвана вторая f). С-но это (переопределение ф-и в репле) основная операция при image-based разработке.
0xd34df00d
Блин, аж жаль — не так давно видел какой-то пакет, плагин к ghc, что ли, который делает что-то очень похожее, но напрочь забыл, как называется.
Cerberuser
Скажите это разработчикам на Rust — авторам Helix, к примеру :)
Druu
И макросы в расте чекаются после раскрытия. Вопрос — что происходит, когда потом в этом раскрытии ошибка типов?
mayorovp
Рефлексия с типами плохо совместима? Это вообще как?
prijutme4ty
Полагаю, имелась в виду не рефлексия, а более широко — метапрограммирование.
Druu
Нормальная рефлексия должна предоставлять возможности свободно и удобно анализировать, изменять, конструировать типы в рантайме. Если вы конструируете тип в рантайме, то не можете потом как-то его статически чекать (если у вас не идрис офк), ну просто потому что к моменту чека этого типа еще нет.
То есть сама идеология рефлексии противоречит статической типизации, с-но хороший статический язык должен рефлексию ограничивать и делать ее использование болезненным, т.к. фрагмент приложения с рефлексией оказывается нетипизирован де-факто. Т.о. использование рефлексии в статике обычно позиционируется как некий специфический инструмент для специфических ситуаций, который дОлжно применять только при серьезной необходимости.
В динамике же использование рефлексии может быть стандартным приемом, использующимся свободно и повсеместно, т.к. добавить в рантайме метод классу — что значение к списку.
mayorovp
Почему-то я всегда считал, что рефлексия — это анализ структуры программы в рантайме, а не конструирование. Но даже если принять ваше определение…
Вот вы пишите: «добавить в рантайме метод классу — что значение к списку». Класс — уже не тип? Тип. Так почему это рефлексия с типами плохо совместима-то?
Druu
Здесь есть некая доля путаницы, ага. Я подразумеваю под метапрограммированием работу с АСТ программы (макросы и другие варианты кодогенерации), а, с-но, рефлексия — это работа с программными объектами, которые этот АСТ может представлять. Если вы генерируете АСТ, который описывает класс — это метапрограммирование, если вы создаете программно некий класс как сущность, добавляете ему методы (как сущности) — то это рефлексия. Метапрограммирование может происходить при компиляции (к слову, тайпчек — формально одна из разновидностей метапрограммирования), рефлексия — в рантайме.
С динамическими типами — прекрасно совместима (как я уже выше отмечал — динамическая типизация это не та, в которой типов нет, а та, в которой они в рантайме), со статическими — нет. Потому что добавление это предполагается в рантайме.
prijutme4ty
Попробуйте описать результат парсинга произвольного json в статически типизированном языке. Работая с такими объектами вы теряете все плюсы статически типизированного языка, ибо только в рантайме станет ясно, что вы ошиблись с типами входящих данных. В compile time вы не знаете, какого типа json[«name»], и даже не знаете, корректное ли это выражение.
Чем динамический язык тут лучше? Вам не придется заводить обертки над значениями примитивных типов и руками распаковать их значения (а вам придется, если только у вас в языке нет union-type). Библиотека для работы с json в статически типизированном языке по сути будет реализовывать свою «динамическую типизацию».
Кроме того, в динамическом языке вы спокойно можете писать литералы, соответствующие нужному вам объекту. Я не знаю, есть ли хоть один статически типизированный язык, который вам позволит такое.
А задача работы с пришедшим извне json — типичнейшая.
mayorovp
С действительно произвольным json можно сделать не очень много полезных вещей, обычно все-таки программист знает схему пришедших данных. Эту самую схему можно или представить в виде типа данных (статического!), или держать в голове, но приводить типы по месту:
В любом случае система типов сработает как дополнительный слой защиты от некорректных данных и снизит вероятность появления ошибок подобных вот этой: https://habr.com/company/poiskvps/blog/422077/
prijutme4ty
Да, схема обычно известна (или некоторый кусок схемы, который при обновлении стороннего API может дополняться). Представить его в виде статического типа можно, но для этого нужно быть очень неленивым.
Вы предлагаете (небезопасно) приводить типы по месту. Но где же тут хоть одно преимущество статической типизации реализуется?
На входе и на выходе обработки будут нужные типы (невелика задача и для динамической типизации), а в середине — как получится. Подвержено ошибкам в рантайме оно будет ничуть не меньше, чем код на динамическом языке. Единственная надежда — что компилятор заставит написать обработку исключения про неправильное приведение типов или заставит проверить тип перед приведением. Но компилятор так придирчив как минимум не во всех статически типизированных языках.
Не очень понимаю, как проверка типов помогла бы в том примере (не работал с монгой и не знаю, что за тип у этих операторов и чем такой словарик будет отличаться от обычного {string=>string})?
mayorovp
Преимущество тут в том, что компилятор не дает забыть привести тип перед использованием.
Очень просто: если привести
userInput.paramA
к строке, то для{$empty: false}
будет выброшено исключение, и никакой запрос к СУБД не уйдет.Druu
Лень нажать кнопку и запустить автоматическую генерацию типов? Ну знаете ли...
Ну вот кто-то поменял что-то на бекенде, например убрали поле из какого-то запроса. Жмакнули кнопку, перегенерили типы, жмакнули кнопку, запустили тайпчек на фронте — теперь все места в программе, где это поле (которого теперь нет) используется и в которых, с-но, ошибка — подсвечены. Нет тут преимущества?
prijutme4ty
Вы предлагаете кодогенерацию в качестве решения проблемы? Спасибо, но нет.
PsyHaSTe
А в чем проблема генерировать типы? А то получается, что сначала "типы обязательно нужно писать руками, а это долго", А когда оказывается, что не долго и можно их генерировать "вырвырвыр, у меня нет на это времени"
Для примера на F#
Просто, понятно, читаемо, валидируется во время компиляции. Если вместо
RetweetCount
случайно написатьRetwetCount
, компилятор подчеркнет ошибку и ругнется. Удобно? Вроде, весьма. И не нужна никакая документация, потому что код + IDE сами по себе лучшая документацияTheShock
Та даже если долго — лучше раз написать типы под API. Значительно дольше потом каждый раз стараться понять, что же в этой непонятной переменной `json`
Druu
А в чем проблема? Легко, надежно.
TheShock
Вы какие-то ужасы рассказываете. О каком-то отвратительном апи с непонятной схемой. Как с такой вообще работать то? Что в динамическом, что в статическом? Все хорошие апи, которые я знаю очень легко описываются в статике. Да API, когда подключаешься к серверу — описывается в первую очередь, даже на чистом JS через JSDOC по той простой причине, что значительно легче посмотреть, что приходит в ответ на какую-то функцию в коде, чем запускать, ставить дебагер и пытаться поймать, что же там пришло.
Cerberuser
С пришедшим извне json неизвестного формата? Сказал бы, что, напротив, совершенно нетипичная и нежелательная, если бы сам с ней сейчас не возился (в попытках единообразно обрабатывать данные из нескольких таблиц БД). Но в общем случае, это же совершенно нормальная практика — требовать от JSON-а соответствия опеределённой схеме и выбрасывать ошибку (контролируемо, конечно, не по NPE), если пришла какая-нибудь ерунда.
P.S. Минус не мой :)
prijutme4ty
Ок, у нас есть схема. И все равно, в чем профит-то по сравнению с динамически-типизированным языком? Вот вы потратили кучу сил на написание библиотеки, работающей с json, а дальше она не даёт вам никаких преимуществ статической типизации. И динамическая реализация, и статическая примерно одинаково выбросят ошибку, но на динамическую реализацию потрачено на два порядка меньше сил (на разработку библиотеки).
mayorovp
В случае динамической типизации можно запросто забыть выбросить ошибку.
Cerberuser
Это — одно проблемное место во всей кодовой базе. Объект, вышедший из-под
JSON.parse<someInterface>
, в дальнейшем обрабатывается с учётом всех статических типов. В то время как в динамически типизированном языке он так и останется "чем-то, определённым в рантайме".prijutme4ty
Вероятно, у нас просто различаются задачи. У меня основной рабочий инструмент — скрипты и не слишком большие числомолотилки. Половина кода там будет тем самым проблемным местом, где статическая типизация бессильна, но тратит много сил на реализацию. Другая половина кода не настолько велика, чтобы нельзя было за время работы с ней не забыть, какого же типа объект передается в переменную с именем matrix.
Cerberuser
В случае достаточно компактного кода — не спорю. Сам писал небольшую строкодробилку на Ruby (извлекал нужные мне данные из LaTeX-овского документа), после того, как задолбался обходить различные краевые случаи в коде на C, и от динамической типизации не сильно страдал. И соглашусь, что в названном вами случае статика действительно может быть излишней. Если же код достаточно большой, а внешняя среда в него попадает через ограниченный интерфейс — вполне логично будет всё вне этого интерфейса типизировать статически, а его самого — валидировать для соответствия этим типам.
KvanTTT
С неизвестным форматом пример не очень удачный. Более удачный — это когда по типу JSON нужно динамически создать определенный объект. Такое используется при обработке AST.
eugenk
Да. Пожалуй это единственный известный мне пример, когда можно говорить о каких-то удобствах динамики. Хотя на той же скажем яве я прекрасно это дело парсил. Но меня пожалуй скорее интересует другое. Не посмотреть отдельные примеры, а понять способ мышления, которым пользуются сильные js-разработчики. Он у них явно не такой как у меня. И при этом вполне продуктивен.
amarao
enum {
null,
bool,
int,
str,
float,
list,
object
}
В том же rust'е спокойно делается match по типу. И оно не динамически типизировано ни в один момент времени.
см github.com/serde-rs/json
prijutme4ty
Я не спорю, что сделать это возможно, но там, где мне достаточно обратиться к элементу, вам дополнительно приходится громоздить type matching.
PsyHaSTe
Просто десериализуйте в объект и все. У вас же есть хоть какая-то схема? Вряд ли вы лупитесь по случайным именам вложенных хэшмап в надежде получить что-нибудь, наверное вы все же предполагаете, что у вас есть объект юзер, у него есть поле мессадж, а у него есть поле мессадж_айди. Почему бы это не зафиксировать в виде типа? Кому от этого станет хуже?
prijutme4ty
Я немного выше написал, что большая часть кода, с которым мне приходится работать — скрипты. Описания всех схем и промежуточных типов, к которым мне приходится обращаться, увеличит размер кода раза в два, не давая взамен почти никакого профита. Охотно верю, что для крупных проектов статическая типизация имеет смысл, но для мелких проектов она даёт только проблемы.
amarao
Так в этом весь смысл. Если вы обращаетесь к элементу, которого не ожидаете? Например, вы умеете str, object, list, num, а прилетает null.
prijutme4ty
Я понимаю, что в этом весь смысл, но во многих случаях я точно знаю, что у меня в аргументе, и не хочу тратить силы на убеждение компилятора в том, что точно знаю, что делаю
PsyHaSTe
Обычно это "точно знаю" заканчивается "блин, какой ДУРАК послал мне на вход ЭТО"? и "Почему у меня тут вместо строки [object Object]"
0xd34df00d
Да хоть D-Bus.
0xd34df00d
Тут собака порылась, потому что непонятно, как можно это умение конструктивно определять и выявлять.
elmm
Не в квалификации проблема в данном случае, как я считаю.
Я 95% времени пишу на статически типизированных языках.
Но когда пишу что-то на динамически меня постоянно мучает вопрос — а вдруг мне кто-то, передаст в функцию не то, что ожидается на входе, и всё пойдёт не так, как предпологалось.
Со статически типизированным мышлением ты чуствуешь себя спокойно хотя бы в этом вопросе — если я пишу что здесь у меня на входе данный класс/структура, то я могу быть уверен что публично описанные поля у него есть. А в языке с динамической типизацией меня каждый раз при обращение к полям входящего обьекта меня терзает вопрос — а не упадёт ли он при этом обращение, от того, что на входе будет не тот обьект, который я ожидаю?
Это не значит, что в том же питоне, я не думаю типами и обьектами, но там ощущение, что всё может быть совсем не так, как ты предпологаешь, является более навязчивым.
Статичиская типизация отодвигает момент такого беспокойства — тут ты уже не сомневаешься будет ли у тебя на входе нужный обьект, а задаёшься вопросами будет ли обьект в правильном состоянии, не поменяет ли его кто-то асинхроннно в процессе и т.п. Статическая типизация уменьшает количество причин переживать.
Есть языки которые и эти вопросы снимают по максимуму, но в моей сфере они не рапостранены.
Возможно, если долго писать на языках с динамической типизацией, то об этом не будешь задумываться так часто — просто привыкаешь к этому виду «опасности». Как в C++ особо не паришься, что любая подключённая к проекту библиотека могла затереть случайнными данными кусок памяти с твоим обьектом.
lgorSL
Даже хуже. Если разбираться в исходниках какого-нибудь tensorflow, то там у функции может быть 50 строчек проверок входных параметров (потому что там, например, в качестве входных данных может прийти и тензор, и список тензоров, и кортеж списков тензоров, а ещё есть куча параметры по-умолчанию, некоторые сочетания которых могут быть невалидными), и только потом будет вызываться функция с примерно таким же именем, в которой проверки уже не делаются.
Документация местами тоже божественная, например, "loss_func is loss function" — а с какими аргументами она будет вызвана, я сам должен угадать. При том что в нормальном языке просто в типах уже была бы информация о типах аргументов коллбека.
И самое противное — мой изначально простой код типа load_image() обрастает такой же кучей проверок (каждая была добавлена после очередного падения в рантайме и поиска бага), потому что часть png картинок грузятся как трёхмерные массивы numpy с тремя или четырьмя каналами, иногда чёрно-белые картинки вместо этого грузятся как двухмерные массивы, а ещё иногда тип данных в массиве оказывается не uint8 с интервалом 0..255, а float с интервалом 0..1.
EchoStan
Да, на интерпретируемых языках можно писать сложные системы. Заметил, что корреляция между использованием всех возможностей динамической системы типов в языке и вероятностью превращения проекта в кашу стремится к единице.
При этом использование JS или TS в итоге гораздо слабее влияет на результат (результатом считаем, естественно, поддерживаемость кода, а не производительность etc.), чем привычки архитектора. Кроме TS в арсенале программиста-хипстера есть статическая типизация в ODM и даже пропсы (во фронт-компонентах) с фиксированным типом.
Основная идея в том, чтобы не пользоваться языком как динамическим и делать основной упор в коде на читаемость. А если всё же пользоваться, как динамическим — уже через неделю будь готов потратить n от дневного запаса когнитивного ресурса на ковыряния. Впрочем, бывают кейсы в которых это вполне нормик.
sairus777
Julia — оптимальный вариант динамической + статической типизации. Можно типизировать только там, где надо.
gatoazul
И Perl6
Tiendil
istepan
Это вопрос скорее архитектурного проектирования при разработке.
Изначально JS, PHP решали небольшие сиюминутные задачи, где система сама по себе не усложнялась.
Крупные задачи решались на языках посерьезней.
Сейчас задачи другие, и соответственно меняется подход.
Можно прибраться в комнате сложив все игрушки под кровать, а можно разложить их по местам.
i360u
Вообще не вижу никакого непреодолимого противоречия: сам часто сначала прикидываю как все должно работать в общем (без типов) а потом описываю типы и прорабатываю детали. Имею одновременно и строгий порядок там где это нужно и необходимый уровень свободы там где важна скорость. По моему, виды типизации не стыкуются в головах только у людей с квадратно-гнездовым мышлением. Вообще вопрос типов очень близок к вопросу вариативности данных, и если вы впадаете в ступор от того, что окружающий мир не всегда вписывается в вашу систему строгих классификаций — проблема в вас.
jetcar
все время писал на с# и после продолжительной работы с ангуляром, я в с# чуствовал что меня иногда заставляют создавать врапперы для классов когда мне нужно всеголишь один флажок вставить, в яваскрипте такой проблемы нет, надо вставил и всё побежало дальше код не усложнился, насчёт проверки во время компиляции то на самом деле это довольно тупая проверка которая только совсем явные ошибки заметит после неё всё равно остаётся куча ошибок типов которые только в рантайме вылезут, достаточно в с# Automapper вставить и всё больше компилятор вам не помощник, так что для проверки всего нужны тесты, а если есть тесты то они выполнят всю валидацию которую мог бы компилятор выполнить и вот вам динамическая типизация больше не проблема и не надо всякие извращения типа иммутабельности приделывать иммутабельность вовсе не для этого придумана
kagetoki
А для чего придуманы знаки препинания и заглавные буквы?
Собственно, ваша манера написания текста очень показательна, боюсь представить, как выглядит ваш код, где всеголишь один флажок вставлен то тут то там.
Free_ze
Вас никто не заставляет использовать AutoMapper. Проблема ведь не в языке.
slonopotamus
Вброшу немного: единственная, на мой взгляд, веская причина почему JS вообще говоря используется в современном мире — это то, что он является единственным языком, работающим в браузерах. А дальше уже стокгольмский синдром.
gatoazul
Так оно и есть. Но к динамической типизации это не относится, и языком JS она не ограничивается.
VolCh
А PHP, который во многом похож на JS по отношению к типам, почему в современном мире используется?
amironov
Потому что бытует мнение (ошибочное), что PHP — это простой язык для начинающих.
Mabusius
Может я чего-то не понимаю, но я всегда думал, что динамическая типизация это когда 0 == '0' == false == null, а то о чем пишет автор это стили программирования.
Cerberuser
То, о чём пишете вы, — не динамическая, а слабая типизация. Динамическая — это когда можно написать
let x = 1; processNumber(x); x = 'string'; processString(x);
и не выворачивать себе мозг из-за того, что переменнаяx
должна иметь разный тип в разных местах кода.mayorovp
То, что вы написали — это слабая типизация. А динамическая типизация — это когда, условно, можно все 4 приведенных вами значения записать в одну и ту же переменную без специальных приспособлений, просто по умолчанию.
Mabusius
Ок, виноват, нужно было залезть погуглить теорию прежде чем коммент писать.
kagetoki
После многих споров на тему статика vs динамика, у меня создалось устойчивое впечатление, что разработчики делятся на 2 лагеря:
Я безнадежно далек от второго подхода, меня ужасно раздражает постоянное натягивание заплаток и расходищийся багфиксинг. Но миллион раз встречал людей, которые предпочитают с места в карьер нырять, им важнее дать супер быстрый старт, принося в жертву последующую скорость развития продукта.
Kroid
Неудивительно, что вы «безнадежно далеки от второго подхода», с учетом того, как предвзято вы его описали. Я, как человек, использующий в основном второй подход, поясню, почему он хорош.
Когда мы сталкиваемся с новой проблемой, мы некоторое время ее обдумываем, потом решаем. Но дефект нашего мышления в том, что мы думаем, что поняли, в чем именно проблема и как ее правильно решить. Это не так. По настоящему понять можно лишь погрузившись в проблему на практике, а не просто потыкав её теоретическими щупами. Потому что часть важной информации от нас обычно скрыта на старте.
Если вы с самого начала все обдумываете, строите полную архитектуру проекта, а потом ее реализовываете, то это не только замедлит вас в начале, но и не даст вам желаемого ускорения «в середине». Потому что через месяц-другой или вы увидите что-то, что упустили, или требования изменятся. В худшем случае потребуется переписывание крупной части проекта, если не всего целиком.
Гораздо быстрее будет осознанно в первый (а иногда и во второй, и в третий) раз писать быстро и некачественно (прототип), наткнуться на всевозможные стены лбом, переосмыслить чуть ли не полностью, а потом уже написать правильно и качественно.
PS Разумеется, это не относится к критически важным вещам, вроде системы управления полетом или там медицинского оборудования. Хотя даже в этих случаях есть этап исследования и быстрой проверки гипотез.
kagetoki
Если плохо понять природу проекта или плохо уметь проектировать, конечно же, получится все ровно так, как вы описываете. Сначала будет трата времени на бойлерплейт, потом на борьбу с ним и болезненное натягивание совы на глобус.
Кстати, именно так и происходит, когда за архитектуру берется мидл, даже сообразительный — просто опыта не хватает.
Что касается неряшливого прототипа — тысячу раз видел, как такой прототип внедряется, девелоперы заранее предупреждают, что он дерьмовый, и бизнес говорит «дадада, обязательно перепишем, и не раз». Спустя 2 года это дерьмо все еще в продакшне, обвешано миллионом костылей, и чем дальше, тем хуже и тем больше все ссут его переписывать, потому что уже никто не знает, как оно работает и что отвалится, если убрать.
В сухом остатке имеем: если проектом управляют сознательные люди, которые умеют в долгосрочную перспективу, то статическая типизация будет сильным преимуществом.
Если бизнесом управляют незрелые люди, то динамическая типизация на старте поможет этот факт сгладить, но агония неизбежна, и чем дальше, тем будет хуже.
Как тут уже не один раз заметили, типы в первую очередь рождаются в голове, и только потом переносятся в код. И если они родились убогими, то там уже ни динамика, ни статика не спасут.
Отдельно добавлю, что во многих мейнстримных языках (C#, Java, Go etc.) система типов слабая, не хватает банальных типов-сумм, я уже молчу про HKT. Тем не менее, это уже лучше, чем ничего.
faiwer
Мне кажется так происходит вообще у всех, если задача не сильно очевидная/привычная. Сколько не проектируй, жизнь всё равно расставляет свои корректировки. Мы, человеки, достаточно ограничены в своих умственных способностях и удержать большую ещё несуществующую систему в голове не способны.
kagetoki
Нет, не у всех, я видел контрпримеры, как живьем, так и просто очень качественный код чужих проектов. Большинство факапов, которые я видел, вообще можно было бы избежать, тупо следуя самым базовым принципам DDD. Да что там ДДД, достаточно было просто сделать нормальную слабосвязанную систему, правильно разграничив ответственности. Но нет, об этом думать нам слишком долго, нам поформошлепить надо побыстрее, пользователи уже 10 минут новых фич не получали.
faiwer
Ну ок, значит где-то живут небожители, с IQ > 200, бесконечной памятью, и которые не совершают ошибок.
kagetoki
Они не небожители, и совершают ошибки иногда. Они просто умеют проектировать, потому что в свое время потратили много сил на это, в то время как другие срались в коментах про плюсы и минусы динамической типизации)
Они понимают, какие правила в проекте являются ключевыми и меняться не будут, а потенциально изменчивые специально проектируют так, чтобы было легко расширять или вообще переписать при случае.
В общем, не рокет сайнс это никакой, думать надо головой и мат модели строить правильно, требования зарания выяснять, опять же. А не "там походу разберемся".
faiwer
Ну у вас на одной стороне "там походу разберёмся", "не думать головой", "не выяснять заранее требования". А с другой те, кто набравшись опыта, и соответственно умея проектировать, пишут всё с первого раза на чистую. Ерунда какая-то. Чёрное и белое. Раздолбаи и гении.
Я веду речь о том, что, имхо, такое возможно только с хорошо знакомыми предметными областями. Либо с хорошо формализованными. А типовая ситуация как раз другая, и ключевые "неизменные" правила встают на уши, бизнес решает развернуться на 90 градусов посреди дороги, меняются приоритеты, неразумные сроки, и прочая чертовщина. Многие вещи становятся очевидными только уже в процессе реализации, когда все вынужденно погружаются в мельчайшие детали. И появляются все эти и многие другие проблемы. И гора legacy к тому же. Масштабный рефакторинг это вообще-то нормальное явление.
Т.е. так бывает не когда middle берётся за проектирование, это в целом рядовое явление. Чем опытнее архитектор и co, тем меньше будет граблей и серьёзных изменений\корректировок. Но они будут.
Kroid
Ну знаете ли, если вы сталкиваетесь только с теми задачами, которые уже решали ранее, ничего не мешает писать сразу правильно и быстро — тут даже прототип будет вполне качественный уже с первой версии.
Только вот это скучно, а программисты в большинстве своем — народ, который не любит несколько раз делать одно и то же, а любит делать что-то новое. Поэтому, к примеру, и меняют места работы каждую пару лет — перерос существующее, пора искать более сложное. Вот и получается, что почти в каждом проекте есть неизвестный кусок — новая технология, новый подход, новые требования, и тд.
gatoazul
Само название как бы намекает, что динамическая типизация нужна там, где есть динамика — вещи, которые в принципе невозможно проверить на этапе компиляции, например, имеется непредсказуемый поток данных или требуется гибкость в перестройке самой программы.
Другое ее применение — там, где поток требований к программе быстро и непредсказуемо меняется, и расходы на статические описания элементов программы (никто не будет спорить, что это расходы?) себя не оправдывают. Пример — веб-разработка.
Соответственно, самым удобным для универсальных приложений является язык, в котором типизация динамическая, но может быть ограничена для определенных полей, например, с целью оптимизации или дополнительных статических проверок.
Поэтому весь спор напоминает дискуссии о том, что лучше — пиво или мороженое. Смотря кому и для чего.
VolCh
С другой стороны, вроде во многих популярных языках со статической типизацией есть или вводятся типы а-ля variant, any и т. п., а для динамических языков есть и улучшаются инструменты статического анализа.
В общем предмет спора практически исчезает.
gatoazul
И неявный вывод типов, позволяющий не обвешиваться многострочными аннотациями.
kagetoki
В любой программе есть непредсказуемая и предсказуемая части. Даже если вы обрабатываете "непредсказуемый" поток данных, эти данные, во-первых, имеют какие-то общие черты (иначе почему вы вообще их обрабатываете вместе?), а во-вторых, эта программа имеет другую часть — собственно часть, отвечающую за саму обработку, и эту вторую часть можно спроектировать в устойчивых абстракциях. Кстати, даже в статической типизации непредсказуемую структуру можно всегда описать хэштаблицей.
Тут два варианта: либо требования меняются совершенно непредсказуемо (например, сегодня вы делаете интернет-магазин, а завтра вы его хотите проапргрейдить до социальной сети), либо все-таки есть общая канва продукта, и остальные требования на нее более менее гармонично можно наложить.
В первом варианте не поможет ничто, весь жизненный цикл проекта будет агонией, во втором варианте статическая типизация в умелых руках тащит будь здоров.
Что такое универсальные приложения? Это когда и мессенджер, и календарь, и ядро ОС немношк?
Статическая типизация — это инструмент, который помогает сделать приложение стабильным.
Если в замере скорости разработки учитывать стабилизацию фич, а не просто выкатывание хоть сколько-нибудь рабочего прототипа, то, соответственно, этот инструмент и скорость разработки увеличивает.
Конечно же, любой инструмент хорош только в умелых руках. Если разработчик умеет строить мат. модели и проектировать системы, а не просто набрасывать код на компилятор, то статическая типизация, отнимая немного времени на стадии написания кода, экономит огромное количество времени на всех последующтх этапах разработки.
amarao
Извините, если у вас непредсказуемый поток данных, и ваша программа работает со всеми входными данными, то у вас непредсказуемая программа. Удачи в эксплуатации, так сказать.
Правильно — у нас есть много вариантов типов. И современные языки с алгебраическими типами данных не смотря на статическую типизацию отлично справляются. Более того, эти языки больно бьют по рукам того, кто забыл, что float — частично упорядочен, а в json'е может быть null.
KvanTTT
Вспомнил про еще один нетривиальное достоинство динамической типизации — всякие квайны и другие эзотерические вещи на таких языках получаются более изящными и лаконичными.
amarao
Есть такое. Я не могу сказать про JS, но питон (который динамически типизированный) предлагает свои best practices как с этим жить. Де-факто статическая типизация питона происходит на этапе написания тестов, именно они проверяют, что у нас не будет «TypeError: cannot concatenate 'str' and 'NoneType' objects». И тесты на питоне из-за этого получаются очень избыточными, потому что приходится проверять буквально всё.
С другой стороны, я понимаю ценителей динамической типизации. До момента появления типажей прошло много времени, а до этого нас много раз заставляли думать про наследование и другие ужасы ООПа, что превращает статическую типизацию в большее зло, чем её отсутствие.
ApeCoder
Даже программируя на динамическом языке люди используют неявно статическую типизацию, так как для того, чтобы написать x.y(z) надо знать что у объекта x есть метод y, который принимает параметр z, а это и есть тип.
amarao
Питон говорит, что это называется "утиная типизация". У объекта может быть вообще враппер над rpc, который все вызовы всех методов тупо передаёт дальше. Не разбираясь, "есть такое или нет". Это, кстати, яркий пример динамической типизации — код не знает, но делает.
ApeCoder
Я совершенно с вами согласен. Утиная типизация фактически это структурная типизация. Она может быть статической и динамической. Даже если она динамическая чтобы ей пользоваться надо статически о ней рассуждать. Просто без поддержи компилятора.
> У объекта может быть вообще враппер над rpc, который все вызовы всех методов тупо передаёт дальше.
Внутри враппера он не зависит от конкретного типа. Чтобы пользоваться враппером, надо понимать, какие методы там могут быть, а какие нет. То есть вывести в мозгу тип.
amarao
На питоне не надо «знать». Просто получаешь getattr с именем, возвращаешь объект. Этот объект в себе уже содержит строку со своим названием, и все пришедшие аргументы вместе с этим названием пакуются в json — и в requests.
Получается класс-прослойка, который готов выполнить и foo.len(), и foo.p_equal_np(), и foo.transpose(), абсолютно не зная про эти имена в момент написания кода.
ApeCoder
Я и говорю что прослойка не знает. Но в том месте где вы пишете foo.len() вам надо знать что при выполнении туда попадет что-то у чего есть метод len.
amarao
А, я понял вашу смысль. Мы ожидаем, что у «этого» есть длина. Это не типизация, это трейты, или, в контексте питона — «утиная типизация». Что угодно с ".len()" сойдёт в этом месте.
… Но есть код, который даже не знает что вызывать. Например, многие фреймворки тестирования просто вызывают все функции по маске, не думая о том, что это за функция.
mayorovp
… и у этой функции тоже есть тип.
amarao
Наличие __call__, да. Но в этом случае, какой тип у mock.MagicMock? Оно может всё. Вообще всё.
mayorovp
Указанный вами.
amarao
Мне кажется, где-то тут проходит граница приличия. Например, если мы возьмём нетипизированную память (уровня ассемблера), то мы можем:
— Прочитать её как флоат.
— Прочитать её как инт.
— Перейти на неё как на код.
И всё это не делает происходящее сколь-либо типизируемым. Типы — это ограничения. Когда у нас нет ограничений, то нет и типов.
mayorovp
Память — и правда не типизирована. А mock.MagicMock — типизирован, но он типизирован тем типом, который вы подразумеваете. Моки же используются не потому что это круто, а в качестве замены для зависимостей. А у зависимостей есть типы.
Когда вы используете mock.MagicMock для замены foo.Bar — ваш mock.MagicMock как бы имеет тип foo.Bar. Код, которому вы передали свой мок, ожидает именно foo.Bar.
amarao
mock.MagicMock максимальное приближение к нетипизированному объекту, которое можно придумать. Он не "имеет тип", он не "вызывает ошибок". Ровно так же, как
jmp $RANDOM
не вызывает ошибок типизации в машинном коде (даже если это ахинея).Давайте договоримся, что такое типы. Это ограничения на данные, которые накладывает программист для того, чтобы программа (компилятор) за него помнила и контролировала что можно а что нет. (На самом деле типы — это не ограничения, а разрешения, потому что полиси по умолчанию для языков с сильной типизацией — ничего нельзя).
ApeCoder
у него тип mock.MagicMock :) или mock.MagicMock — я не знаю питона глубоко
amarao
С точки зрения системы типов у него типа нет, потому что он ничего не ограничивает.
ApeCoder
Насколько я понял, он гарантировано предоставляет. Нет?
amarao
Он их предоставляет в том смысле, что ничего не происходит. Ни хорошего, ни плохого.
ApeCoder
Вот это наверное и есть гарантия — какой-нибудь int упадет с ошибкой
amarao
Тыг это не гарантия, это анти-гарантия. Мы ожидаем, что на попытку str+None нас пошлют нафиг, а mock всё жрёт и причмокивает. Падают только негативные тесты (которые ожидают ошибку).
Повторю, magicMock, это такой метод отключить типизацию в питоне.
ApeCoder
Почему антригарантия. Для тех целей, для которых его создают, он гарантирует жрать и причмокивать. Другое дело, что мне данная практика не нравится.
Т.е. если его создают, то для того. чтобы результат обладал какими-то свойствами. Совокупность этих свойств про которые мы знаем и есть тип. Если мы знаем это в дизайн тайм, то тип статический. Концептуально.
amarao
Что такое «свойство»? Пропуская низкоуровневые ответы, высокоуровневый — обещание интерфейса, что такая-то операция имеет смысл. Ошибка уровня типизации — это защита языка от выполнения операции, для которой нет реализации (т.е. нет смысла).
А теперь у нас нечто, что не реализует смысл, а всего лишь выключает ошибки типизации. Внутри там ничего не происходит (я не про счётчики вызова, я про «смысл» — len и т.д.), и в этом отношении он эквивалентен просто коду.
Мы можем сделать jmp куда угодно — нас никто не остановит, и никакого соглашения об ограничениях нет.
Система типов — это соглашение об обозначении смыслов, плюс машинный энфорсинг этих соглашений. mock всё это отключает.
ApeCoder
Утиная типизация это тоже типизация. Это просто не номинативная типизация а структурная.
ApeCoder
"функция" это уже тип.
amarao
Ну, я могу ещё раз показать на волшебный тип mock.MagicMock из питона, который соответствует любой сигнатуре и любому применению. Строка, число, функция, класс, модуль, синглтон, итератор, контекстный менеджер, etc.
ApeCoder
Я думаю, когда вы его вызываете, вы расчитываете на то, что он поддерживает какой-то протокол. Все равно внутри себя вы думаете о типах или похожих вещах.
VolCh
Или проверить наличие этого метода в рантайме, Или проверить тип в рантайме, когда значение придёт. Разве можно назвать статической типизацию параметра функции если пишешь код типа
if (param instanceof SomeClass) throw new TypeError(), пускай он даже спрятан под сахаром типа
function someFunction(SomeClass param)`?paratagas
Я в последнее время пишу в основном на Javascript. Так что меня можно назвать сторонником слабой динамической типизации. Перечислю ее плюсы лично для себя, исходя из своего опыта…
1. К примеру, у меня есть функция, которая парсит массив объектов, например Employee, и что-то с ними делает. В JS я могу описать логику этой функции, и затем применять ее к любому массиву объектов, чья внутреняя структура одинакова. Мне не нужно заботится о типе объектов, которые содержатся в массиве. В Typescript, насколько я понимаю, мне пришлось бы внутри моей функции явно указать тип объектов, которые содержатся в массиве. Что-то вроде ...<Employee[]> (прошу прощения, если ошибся в синтаксисе, никогда промышленно не писал на TS). При попытке передать туда массив объектов другого типа компилятор будет ругаться. Мне надо идти в мою функцию и переписывать ее, добавляя новый тип, или делая переключение типов, или создавая свой тип. Точно не помню, но суть — в усложнении возможностей повторного использования. Можно вообще присвоить параметру значение по умолчанию и пускай приходит любой тип. Если он будет расценен, как false, функция возьмет мое значение по умолчанию.
Это же касается и возвращаемого функцией значения. В JS я могу вернуть в зависимости от логики функции, например, false, число или массив чисел. Мне не нужно прописывать 3 типа или выбирать только один их них. Причем возвращаемый результат я могу записывать с одну переменную. Например, присвоив ей при инициализации false, а затем число или массив чисел в зависимости, скажем, от количества возвращаемых данных. В других ЯП мне пришлось бы создавать 3 функции с одинаковыми именами, но с разной сигнатурой.
2. Возможность быстрого сравнения данных разных типов. В частности, приведение к логическому типу. Например, если в метод пришла пустая строка, число 0, значение null или undefined, то все они логически определяются как false. Я могу просто проверить if(!value) и не делать ничего с пришедшими данными. Это позволяет быстро определять значения, непригодные для работы: undefined (если значение не было установлено), null (если явно установлено пустое значение) и какое-то из пустых числовых и строковых значений, трактующихся как false, не прибегая к определению типа этого значение. Мне просто не нужен тип в данным случае. Это также облегчает использование примитивов в параметрах функции. Я ожидаю в пераметре функции массив или строку, а приходит null. Я могу сделать быстрый возврат при получении null. Мне не нужно описывать тип каждого аргумента.
3. Объединение значений разного типа. В JS я могу с легкостью сложить число со строкой. Это удобно при работе в WEB, например при получении данных их формы и произведения с ними каких-то вычислений. Например, я могу объединить имя (строка) и возраст (число) пользователя и вывести для него приветствие, например, «Здравствуйте, Петр. Сумма вашего заказа 30 руб.» Я как-то сталкивался с C# и искренне не мог понять, почему он не дает мне сложить число со строкой. Значения из полей форм в HTML приходят в string, и такая возможность бывает удобной. Это не камень в огород C#, просто я привык мыслить по-другому.
4. Отдельно отмечу возможность логического сравнения в операторах && и ||. Например, один из излюбленных приемов среди JS-программистов:
Позволяет вернуть, например, в шаблон второе значение, только если первое истинно.
а
одно из истинных значений. Плюс в том, что мне не нужно думать, какой тип придет в них (undefined, null или пустая строка), для меня главное, как значение будет оцениваться логически.
5. Не уверен точно, но мне кажется, что для создания массива в ЯП со строгой статической типизацией нужно, чтобы данные в массиве были одного типа. Часто это неудобно, особенно если нужно потом эти данные в качестве параметров функции в виде массива. В JS можно запихнуть в массив данные разных типов.
6. Автоматическое приведение к числу. Это одна из моих любимых вещей. Посколько данные из HTML-форм приходят в программу в виде строк, даже числа, нет ничего странного, что в JS я могу проверить правильность Например, когда у меня есть выпадающий список с перечнем годов и мне нужно сравнить 2 значения. В других ЯП часто нужно делать что-то вроде или
7. Устойчивость программы. Javascript позволяет гибко производить вычисления с данными разных типов. Да, эти вычисления могут быть ошибочными, но программа продолжит работу. Разумееется, это неприемлемо в ПО, предназначенном, скажем, для медицины и космоса, но вполне допустимо для показа на карте 10 ближайших баров в округе. Да, может быть, где-то будут ошибки с точными координатами или в названии одного из баров затесается "[object Object]", но приложение продолжит работу, что в данной ситуации, возможно, более важно.
Возможно, такое использование языка несколько усложняет жизнь и на нем бывает тяжелее писать, но мне нравится его гибкость. Кстати, я всегда считал и продолжаю считать, что программирование на JS не легче, как это принято считать, а наоборот тяжелее, чем на языках со строгой статической типизацией. Есть еще и другие вещи которые мне нравятся в JS, но я уже много написал, да и не все сразу вспоминается.
Aingis
'a' > 'b'
. К числу здесь будет приводится, только в случае если один из аргументов — число ('2' > 11
— false, но'2' > '11'
— true).В остальном полностью согласен.
mayorovp
Ответил вам ниже: https://habr.com/post/431250/#comment_19426588
Druu
Нет, не пришлось бы. Есть генерики, то есть пишите что-то вроде: parse(arr: T[]) {...} и теперь оно работает для любого Т.
Прекрасно работает:
тип res: number | boolean | string[]
Да не проблема:
И не думайте!
yoba: (number | boolean | string)[]
yoba: boolean
типы не меняют семантику рантайма
paratagas
Не уверен, что приведенный вами пример обладает хорошей читаемостью
Все-таки воспринимается куда легче
Это также код и на JS. Не думаю, что C# или Java позволят такое напрямую без создания объектных оболочек.
mayorovp
В вашем ecmascript любое число уже изначально обладает своей объектной оболочкой! Так что неявное их создание компиляторов никак не может быть записано в недостатки C#.
paratagas
mayorovp
… и что? Вопрос-то был про массив из разнородных данных.
paratagas
Возможно, я немного запутался в ветках комментариев, поэтому, чтобы не разводить лишних споров, я просто попрошу вас ниже написать для кода на JS:
аналог кода на C# в виде массива из разнородных данных.
mayorovp
paratagas
А «yoba» в вашем случае будет объектом или массивом?
mayorovp
habr.com/post/431250/#comment_19427152
Druu
В C# массивы являются объектами.
Druu
С-но из вашего описания было не совсем понятно, что требуется. Если речь просто о дефолтном значении, то можно точно так же и написать:
yoba: string? -> string
Это ts, он статически типизирован, все ок. Да, в c# или java так нельзя, потому что система типов в этих языках проектировалась несколько с другим прицелом.
mayorovp
Да все можно же:
var yoba = new object[] { 1, true, "yoba" };
Druu
Ну да, с кастом до object можно, но это наверное все же несколько другой кейс. С-но, в тс можно типизировать и как const x: [number, boolean, string] = [1, true, '']; в c# будут таплы ближайшим аналогом (с тем исключением что таплы не являются подтипами массивов), есть ли что-то похожее в джаве — хз.
paratagas
А «yoba» в вашем случае будет объектом или массивом?
mayorovp
В первую очередь массивом. Объектом тоже будет, ведь любой массив — объект.
paratagas
Простите, но простое гугление выдало мне такой способ создания массива в C#:
Ваш пример больше похож на создание именно объекта.
mayorovp
Простите, но вы несете чушь. Признак массива —
[]
(квадратные скобки), а вовсе неint
. Вместоint
можно написать любой тип данных. В том числеobject
. В моем коде написаноobject[]
, что определяет массив объектов.paratagas
mayorovp
Ну так в массиве объектов как раз и содержатся "несколько значений с примитивных типом данных". Что вам не нравится-то?
paratagas
Тем, что внутри вашего массива лежит объект, который при итерации выдаст в качестве item именно объект. В моем примере на JS при итерации, скажем, с использованием «forEach» вы получаете доступ сразу к нужному значению массива. Также мне хотелось бы узнать, сможете ли вы передать созданный вами массив в функцию в качестве параметра?
mayorovp
В js вы при итерации точно так же получаете не-пойми-что, и чтобы обработать его как число — вам надо сделать проверку типа. Единственная операция, которую вы можете сделать без проверки типа — передать это не-пойми-что куда-то еще.
А что, собственно, может этому помешать?
Druu
В его примере точно так же будет доступ к нужному значению массива.
paratagas
И этим значением будет объект, а не примитив. И уже из объекта нужно доставать значение, обращаясь к его свойству. Это усложняет структуру и понимание кода.
mayorovp
Ну и где вы тут видите обращение к свойству объекта?
Free_ze
Массив содержит данные одного типа (и его подтипов). То, о чем вы говорите, является кортежем.
paratagas
Спасибо! Вот то, что я имел ввиду. Сказываются различия в терминологии в ЯП)) Значит мое утверждение под номером 5 выше, по крайней мере, для C# неактуально.
paratagas
Вам для того, что в TS это заработало, пришлось перечислить все типы для x, которые могут прийти в функцию:
Я лишь подчеркнул, что JS этого не требует.
Druu
Так это два разных кода. Если вы на js напишете function yoba(x = 'Default') { то обрабатывать дефолтным образом оно будет только undefined, в точности как вариант на TS, null, false и все остальное потребуется описать руками.
Ну а если вам просто лень писать | null | undefined… то можете просто сделать:
и использовать везде.
paratagas
А чем это тогда отличается от того, что есть в JS по умолчанию?
mayorovp
Неужели тем, что компилятор проверит корректность типов в вашем коде статически?
paratagas
Но если там перечислены практически все типы данных, какой вообще тогда смысл в такой проверке?
Druu
Статика поможет тем, что вы для аргумента x типа Falsey гарантированно не забудете сделать проверку if (x) {} else {} и обработать граничный случай. А вот если типов нет — можете и забыть.
areht
1) То есть функция принимает массив чего-то, делает что-то и возвращает то ли строку, то ли массив, то ли bool? Как вы вообще понимаете что у вас в коде происходит?
paratagas
Да, например, если нужное значение не вычислено или данных не хватает, то можно вернуть «false». Если нужно вернуть одно число, то оно и возвращается, если чисел больше одного, что можно вернуть массив.
Free_ze
Очень сложно придумать кейс, когда действительно нужна такая вариативность и нет нарушения SRP.
«Because I can»
Druu
Вообще тут вопрос в том, как следует обрабатывать данные. Если все кейзы обрабатываются однородно, то имеет смысл возвращать пустой массив вместо null и массив из одного элемента вместо числа. С другой стороны, если эти случаи на call site требуется обработать специфически — лучше как раз сделать тип number[] | null | number — тогда мы гарантированно не забудем обработать кейзы с пустым и одинарным значением, в то время как в случае возвращаемого типа number[] их можно прощелкать.
Free_ze
Специфика использования лежит вне зоны ответственности библиотечной функции. Сейчас это так, завтра — иначе, послезавтра появляется другой «клиент» с иной спецификой. Абстракция же.
Druu
Способ обработки зависит от данных. А тип — фиксирует соответствующие инварианты
Если данные логически предполагают определенный способ обработки — то они предполагают. Если перестают предполагать — это уже другие данные другого типа, что логично.
Free_ze
Способ обработки зависит от данных, а не наоборот. Как-то противоречиво звучат первое и третье предложения.
Druu
Так а я о чем? У вас есть данные, эти данные предполагают определенный способ обработки (например, что нужно отдельно обрабатывать кейзы с одним значением и без значений), это указано в типе.
paratagas
Free_ze
К сожалению не удивит, это популярный подход в JS.
paratagas
Почему «к сожалению»? Что в этом подходе не так? Он позволяет создавать исключительно гибкие функции, код в которых не будет падать с ошибкой при неправильных или недостаточных входных данных.
ApeCoder
Вопрос, что она делает вместо того, чтобы упасть с ошибкой. И насколько очевидно то, что она делает. В некоторых случаях лучше уж упасть.
Free_ze
Гибкие функции — это нарушение SRP. Функции должны быть однозначны, а их интерфейс — интуитивным. Если в разных контекстах функция ведет себя по-разному, то стоит выделить отдельную сущность.
У меня не выходит найти иного оправдания этому, кроме экономии букв.
paratagas
Так SRP это подход объектно-ориентированного, но не функционального программирования.
Free_ze
Функциональное программирование поощряет божественные сущности?)
paratagas
Почему «божественные сущности»? Можно, например, в зависимости от переданных параметров вернуть из функции «а» созданную функцию «b», можно вернуть функцию «с», а можно вернуть «false», если что-то пошло не так.
Free_ze
Потому что слишком много ответственности на себя берут. «Вокруг них все крутится».
Функции могут быть с одинаковой сигнатурой (иначе как потом узнать способ их вызова?), вместо false возвращать null, например.
paratagas
mayorovp
Вот как раз статическая типизация и помогает избегать рисков в подобных случаях не превращая код в ребус.
lega
Надо вбросить, как она помогает?
отсюда.mayorovp
Так то в готовых проектах. А я говорю про ситуацию, когда хочется заменить одну функцию на другую, но страшно, потому что фиг знает что в нее на самом деле передается и что из нее ожидается. Вот именно в этой ситуации статическая типизация и помогает.
lega
Это не рулетка, если вы хотите заменить функцию, значит вы уже в теме и знаете зачем и что на входе/выходе.
Да и вообще наличие типов не значит, что вы передадите на вход правильные данные.
Free_ze
Всегда есть соблазн добавить сделать стрёмный ход. Поддержание кодовой базы в надлежащем виде требует усилий (которые необходимо закладывать в оценку, иначе скупой оплатит еще и дополнительный таймбокс на QA при стабилизации плохокода).
Но есть в жизни и хорошие вещи:
IvanNochnoy
Функциональное программирование не против Discriminated unions, а это почти то же, что в вышеуказанном примере.
zagayevskiy
SRP это общий принцип программирования. Может(и должен) применяться в любой парадигме.
VolCh
вы реально хотите с десяток вариаций console.log?
Free_ze
О каких вариациях речь?
VolCh
для каждого примитивного типа данных. для number, для string и т. п.
Free_ze
Зачем? Алгоритм
console.log
остается одним и тем же для разных типов данных. Но если он захочет для number варить кофе и возвращать промис готовности — вот это мы оттуда должны убрать.PsyHaSTe
Не обязательно, можно будет просто сделать тип-сумму.
Хотя запашок у подобной потребности определнный есть.
Free_ze
SRP говорит, что не стоит так делать. Но чувствительность к запахам — штука индивидуальная)
PsyHaSTe
Я верю, что так делать никогда не надо. Вопрос про "можем"/"не можем"))
Во-первых можем.
Во-вторых хотя можем, лучше никогда так не делать)
Free_ze
Ну в том, что мы можем, я не сомневаюсь. Обсуждение шло именно о том, что такая возможность позволяет писать лишь плохокод без объективных преимуществ.
VolCh
Не, алгоритм разный. Разные алгоритмы преобразования к строке у строк, чисел, объектов и т. п.
PsyHaSTe
динамическая диспетчеризация в помощь.
VolCh
То есть проверка типов в рантайме?
PsyHaSTe
С чего бы это?
VolCh
Ну а как реализуются разные ветви в зависимости от типа аргумента функции?
PsyHaSTe
С помощью таблицы виртуальных методов. Метод по смещению 10 от начала vtable — нужная нам версия foo.
VolCh
А как определить 10 или 0, если мы не знаем какого типа будет аргумент?
PsyHaSTe
Потому что vtable всегда имеет одни и те же методы по одному и тому же смещению. Почитайте как ООП работает, пожалуйста.
VolCh
Виртуальные таблицы не являются частью "как работает ООП" — это деталь конкретной реализации. И, если вы вдруг о параметрическом полиморфизме, то ООП тоже о нём не упоминает.
PsyHaSTe
Виртуальные таблицы являются едтинственной на сегодня реализацией динамической диспетчеризации в ООП. Когда-то была динамическая таблица методов еще, но это дела давно минувших дней.
И это не отменяет сказанного мной ни в какой мере.
Free_ze
Если член не обнаружен в текущем объекте, рантайм пытается получить объект прототипа (по ссылке, которая есть почти в каждом JS-объекте) и искать на нем, проходя так всю цепочку, пока либо не найдет искомое, либо не встретит
null
на месте ссылки.Free_ze
Преобразование сущностей к строке обеспечивает полиморфизм
Object.toString
, а console.log лишь отправляет результат на консоль.vintage
console.log не приводит значение к строке, а использует рефлексию, для формирования разного интерфейса для разных типов.
PsyHaSTe
А потом сидишь с коллстеком в 50 "супер-гибких функций", и пытаешься понять, кто данные попортил, а возможно и не один раз. Каждая ведь решила "гибко" адаптироваться. И дебажишь 4 часа, чтобы понять, что в от в этом параметре число нужно в строковом представлении передавать, а не в числовом.
zagayevskiy
Ага, а неправильные или недостаточные входные данные туда попали именно из-за такого подхода. Круто, чо. Сами проблему создали, сами и решили.
VolCh
Аналог стандартного console.log
areht
«Можно вернуть» — это замечательно. Я даже не спрашиваю «зачем?» — пусть будет.
Вопрос то был в том как вообще понять что оно принимает и что возвращает? Ну, потом, когда протрезвеешь.
paratagas
Я понимаю вашу иронию, но в реальной жизни очень часто приходится иметь дело с кодом функции, который до тебя модифицировали в разное время 5 разных разработчиков, и не все это делали хорошо. В такой ситуации проще модифицировать уже имеющуюся функцию. Это еще больше загрязнит код, но позволит выполнить задачу, сделать это, если сроки горят и не уронить всё приложение. Я просто реально смотрю на вещи, поэтому не уверен, что языки со статической сильной типизацией позволили бы так свободно обращаться с доставшейся в наследство функцией.
ApeCoder
Для того, чтобы сделать это ответственно, надо проанализировать все места где она используется и понять, что они расчитаны на новый тип результата.
При статической типизации вам это поможет сделать анализ типов и рефакторинги.
areht
То есть преимущество JS в том, что он позволяет говнокод многослойно писать? Ну, такое себе…
> и не уронить всё приложение.
Вы просто не заметите, ибо п.7
Kroid
Допустим, вам надо отправить сообщение юзеру, для этого нужно получить канал отправки по id юзера, а потом отправить это сообщение по каналу. У вас есть функция, которая принимает на вход user_id, на выходе отдает один из нескольких типов данных: или экземпляр класса SMS, или экземпляр класса Email, или экземпляр класса Websocket, или вообще null. Вы вызываете эту функцию, получаете объект, а потом через него отправляете сообщение object.send(«hello»).
Или же вы функцией берете из нереляционной базы какие-то данные — так уж получилось, что не вы её заполняете и в ответ может прийти и число, и масив, и даже фотография. Потом, в зависимости от типа, передаете эти данные нужному обработчику.
khim
Выглядит как попытка пристроить поверх трёх рядов костылей ешё один, четвёртый. По крайней мере если судить по описанию.
Если вы получает из «нереляционной базы какие-то данные» — причём то фотографию, то вообще песенку, то какой у всего этого действия физический смысл? Что пользователь-то должен увидеть?
Kroid
Допустим, есть опросник. На некоторые пункты юзер отвечает текстом, на другие — числом, еще один — выбор нескольких вариантов из перечисленных, и вдогонку еще и фотографии может прикрепить.
Вам нужно сделать фронт-админку для этого. Вы отправляете на бек запрос с айдишником опроса, в ответ от присылает вам всё что заполнил юзер — массив из данных разных типов. Вы каждый раз получаете следующий элемент этого массива — цикл по сути, в js это можно сделать функцией forEach() — и оно возвращает каждый раз данные разного типа. В зависимости от типа данных вы рендерите по-разному: где-то добавляете тег img, где-то checkbox, где-то просто вставляете строку.
Вот и получается что-то такое странное:
Разумеется, этот код можно улучшить. К примеру, подойти к беку и сказать, чтобы он отдавал мне все данные в виде массива хешей с двумя полями: type и value. Либо самому написать прослойку которая нормализует полученные данные, а потом передает их дальше. Но это уже совсем другая история :)
khim
Если у меня фиксированное количество вариантов, то я либо должен сделать либо std::variant, либо банальный union с селектором, как в Pascal 1970го года. Либо можно сделать интерфейс, который будет с этим типом-содержащим-невесть-что работать (и потом его реализовать).
При этом либо среда, либо даже компилятор за вас проконтролируют, чтобы вы все варианты правильно обработали (а сколько раз я видел, что кто-то один из вараинтов пропускает в языке с динамической типизацией — не в сказке сказать, ни пером описать).
Так что этот случай ничуть статической типизации не противоречит и, более того, является классическим случаем для демонстрации достоинств статической типизации.
А вот если мы имеем заранее неизвестный набор типов и понятия ни имеем — что с ними делать… зачем это? Куда это? Как это?
0xd34df00d
Ну, вы знаете, что для каждого из них реализован какой-то там
Render
. Поэтому можно сделать экзистенциальный тип данных, как я чуть ниже написал.Cerberuser
В Rust для этого есть
impl Trait
иBox<dyn Trait>
. Как раз для примера из коммента от 0xd34df00d, когда мы не знаем, что за сущность нам пришлют, но знаем, что она будет уметь некоторую нужную нам функциональность.Ogra
Да, только на деле потом оказывается, что программист в качестве типа ответа прописал String, в базе оно лежит как VARCHAR(255), и для того, чтобы добавить в варианты картинку, придется переписать десять классов и три шаблона. А сделать это надо буквально вчера.
mayorovp
То есть лучше было бы начать переписывать не глядя на типы, и словить баги в 10 классах и 3х шаблонах? :-)
Druu
Так может эти баги никто и не заметит, чего вы волнуетесь сразу! :)
В итоге силы — сэкономлены, заказчик — доволен. Пока… :)
mayorovp
Угу. У нас вот однажды выяснилось, что какой-то сервис не работает уже 2 года. Причем как выяснилось? Сдох сервер, пришлось переезжать на новый. И в процессе проверки успешности переезда обнаружилось, что сервис не работает. Начали разбираться — а он и раньше не работал!
С тех пор прошло уже 2 года. До сих пор не работает, уже 4 года в сумме. Возможно, это и правда кому-то кажется нормальным…
Ogra
Ну если у кого-то сервис 2 года не работает, и никто не заметил, то тут проблемы явно не в ЯП, и уж точно не в типизации.
mayorovp
Дело в подходе. Нормально ли оставлять недоделки на потом. Если нормально — то динамическая типизация и правда может помочь сделать что-то быстрее и уложиться в сроки (первые несколько раз).
Если ненормально — то вам все равно придется менять весь код, который использует эту самую строку. И статическая типизация просто поможет его найти.
KostaArnorsky
А он вообще нужен? Что за сервис такой, который четыре года не работает, а никто не чешется?
mayorovp
А фиг его знает. Его другой подрядчик делал…
Ogra
Если переписывать на динамическом языке, то вполне можно переписать два шаблона и три класса. А чем меньше переписываешь, тем меньше шанс наделать ошибок, тем проще тем, кто делает code review, ну и так далее.
Druu
А за счет какого волшебства у вас в динамическом ЯП количество классов уменьшится?
Ogra
Ну а как иначе? Вот был у нас ответ с типом String, а теперь должен быть и String, и Image. Начинаем переписывать!
Для начала введем базовый класс, AbstractAnswer, и от него сделаем двух наследников: AnswerString и AnswerImage. Потом добавим к этому фабрику AnswerFactory — она будет брать сериализованный JSON, пришедший от API, и порождать нужный класс. Конечно же, у каждого из классов будет свой метод render, каждому нужен свой шаблон…
В динамическом языке все будет попроще, менять будем только рендер:
Это только фронтэнд, про бэкэнд я молчу.
Речь на самом деле не про статическую/динамическую типизацию, а про номинальную/структурную. Просто с динамическим рантаймом структурная типизация дается проще.
Druu
Ничего не начинаем, просто пишем в типе возврата string | image, а потом тот самый ваш код выше.
Ogra
Для того, чтобы у вас можно было вернуть или string, или image, вам понадобится рантайм с динамической типизацией, или рефлексией. Это к стат.анализу flow или транспилеру TypeScript можно приписать string | image.
mayorovp
Нет. Для того чтобы передавать
string | image
, совершенно необязательно иметь в рантайме полную информацию о типах. Достаточно чтобы разные типы были различимыми.И, кстати, у рантайма не может быть динамической типизации. Динамическая типизация может быть характеристикой только языка, но не рантайма.
VolCh
Рантайм динамического языка должен обеспечивать гарантии, даваемые системой типов языка. Если я написал function a(int b) на динамическом языке, то рантайм должен дать TypeError при попытке объект, например, передать.
mayorovp
Да, а рантайм статического языка такую гарантию обеспечивать не обязан. Но какое отношение это имеет к типам-объединениям или типам-суммам?
В том же С++, например, рантайм в общем случае ничего не знает про типы передаваемых параметров. Но это ничуть не мешает существованию
std::variant
или дажеstd::any
VolCh
А какое отношение эти типы имеют к, например, PHP? )
mayorovp
И правда, какое же? И при чем тут PHP?
VolCh
Язык с динамической типизацией, дающий в рантайме гарантии своей системы типов. В последнее время всё более строгие гарантии.
mayorovp
И что? Как это подтверждает тезис что динамическая типизация нужна для типов-объединений?
VolCh
Никак, поскольку тезиса такого у меня не было. И статическая, и динамическая типизации ортогональны типам-объединениям.
mayorovp
Тогда зачем вы это пишите в этой ветке?
VolCh
mayorovp
Хорошо, но при чем тут PHP?
VolCh
У него есть динамическая типизация в рантайме, именно рантайм PHP гарантирует утверждения системы типов языка PHP.
mayorovp
Во-первых, вы писали про "переписать 10 классов", а не про "написать 10 классов".
Во-вторых, не выдавайте свои фантазии на тему ООП за недостатки статической типизации. Нахрена тут фабрика, да еще и абстрактная?
В-третьих, исходно предлагалось, вообще-то, использование готовых языковых либо библиотечных механизмов вместо наследования. Вот я пишу:
std::variant<std::string, Image>
— где тут наследование и лишние классы?areht
Так у вас на входе юзер, на выходе наличие .send() — вопросов нет.
0xd34df00d
И в чём вопрос?
Хотя я бы в таком случае возвращал функцию, которая выполняет отправку. Непонятно, зачем все эти экзистенциальные обёртки.
ApeCoder
То есть вы думаете что результат является каналом отправки, и SMS, Email и Websocket являются каналами. Это похоже на описание типов
Kroid
Ну как бы это разные каналы. Если мы объединяем в один тип SMS, Email и Websocket, то с тем же успехом можно объединить в один тип строку, число и булево значение — всё это данные. Только зачем тогда вообще типы нужны, если мы их все приводим к одному супертипу?
UPDATE Мне в этом смысле golang нравится — есть структуры (типы), а есть интерфейсы (поведение). В итоге разные структуры (SMS, Email и Websocket) реализовывают одно и то же поведение — отправку сообщения.
KostaArnorsky
Вы не объединяете в один тип, вы описываете интерфейс канала и получаете разные имплементации. Но у вас не падает все внезапно, когда channel.send({ duck: «Я маленькая уточка и знать не знаю про никакие контракты.» });
ApeCoder
> один тип строку, число и булево значение — всё это данные.
Все это объекты. Да, есть типы есть подтипы.
> олько зачем тогда вообще типы нужны, если мы их все приводим к одному супертипу?
Там где мы приводим, мы не используем их особенности, а там где не приводим используем. В результате мы знаем какой код полагается на какие фичи и это контроллируется автоматически.
VolCh
В целом вы описываете преимущества слабой типизации перед сильной, а не динамической перед статической.
mayorovp
Typescript использует структурную типизацию. Это, в частности, означает что компилятор не будет ругаться если у объектов другого типа есть все нужные поля.
Не является особенностью динамической типизации.
Вранье, C# это разрешает делать.
В том же C# любое значение может быть приведено к
object
и положено вobject[]
.Это одна из ужаснейших вещей. Простота перевода между строками и числами заставляет программистов забывать об отличиях между ними, равно как и о региональных особенностях.
Вам, как русскоязычному программисту, должно быть известно что иногда в качестве десятичного разделителя выступает запятая, а не точка, и про связанные с этим проблемы.
Чтобы программа продолжала работу при ошибках — нужно не забывать ставить try/catch. А продолжать вычисления после ошибки типизации — бессмысленно.
paratagas
В JS для этого не нужно создавать объект. Можно положить в массив как примитивы, так и объекты:
Для этого придется обертывать в try/catch практически весь код, а не предположительно наиболее уязвимые участки кода, как обычно делается.
Для условно «целых» чисел, не содержащих разделители, эта проблема неактуальна.
Эта особенность наоборот заставляет не забывать, а помнить об этих отличиях. Насчет «ужаснейшей», как говорится, это дело вкуса.
mayorovp
Так заведите интерфейс, где будут перечислены именно те поля, которые требуются этой функцией. Вам же все равно придется где-то эти поля перечислить если вы собираетесь использовать функцию с произвольными объектами через два года.
Почему приведение типа к object вы называете "созданием" объекта?
paratagas
Насколько, я знаю, в C# массивы не могут иметь разные типы данных. Это принцип проектирования массивов в этом языке. Поэтому я предполагаю, что вы имели ввиду создание объектной оболочки над примитивом. В отличие от моего примера, где используется сам примитив.
mayorovp
Вполне себе применимо. Программист, когда писал функцию, рассчитывал что у переданных ему объектов есть вполне конкретные поля. Без этих полей функция будет работать не так, как задумывалось ее автором. Это и есть "требуется".
Вас что, вручную заставляют создавать эту самую объектную оболочку? Так нет же, компилятор автоматически приводит любое значение к
object
! Что же до эффективности такой операции — не js-нику жаловаться на производительность упакованных объектов в c#...paratagas
mayorovp
… и все равно у этого подстраивания есть какие-то пределы.
kagetoki
Точность, достойная тру-разработчика. Просто восхитительно. Надеюсь, вас в жизни не допустят до написания кода систем, имеющих хоть какое-то отношение к человеческому здоровью/жизни.
"Наши самолеты в целом более менее скорее летают, чем падают. Предположительно большинство из них долетит до места назначения, наиболее похожие на критически уязвимые места в коде мы с большой вероятностью обернули в
try/catch
".paratagas
int03e
Софт для самолетов никто на JS не пишет. Для разных типов задач существуют разные подходы и инструменты, это понятно. Писать софт для марсохода — это одно дело, нафигачить MVP за месяц для заказчика (а иначе он уйдет) — это совсем другое.
NN1
В современных языках динамическая типизация или статическая определяется исключительно разработчиком.
TypeScript:
C#:
ApeCoder
разница в умолчаниях, удобстве и быстродействии в результате кодовая база разная
eugenk
Небольшое лирическое отступление. Прочитал комменты и в очередной раз убедился что прогеры народ всё-таки (как бы это помягче сказать...) совершенно особый. Как то с трудом могу себе представить подобный спор об инструментах (а ведь говорим мы именно о них !) в среде например сантехников. Ещё большое спасибо любимому хабру, что тут всё-таки атмосфера научного диспута, а не бардака при пивном ларьке! А то в начале 2000-х на соответствующих конфах помню такое…
И да. Очень хотелось бы чтобы подобную статью написал кто-то из сильных js разработчиков со своей колокольни. Надеюсь эта дискуссия кого-нибудь из них сподвигнет. Буду каждый день просматривать хаб по джаваскрипту :))
musuk
>среде например сантехников
Да ладно.
Метапол против полипропилена.
А если наберёте Festool, то откроете ворота в ад.
sshikov
Именно. Совсем недавно на youtube наблюдал, как «спорили» два ремонтника (ну, не совсем спорили, и не совсем сантехники, а скорее сборщики мебели).
Один показал свой набор инструмента, состоявший из кажется 4 плотно упакованных тех самых систейнеров Festool, а второй его тут же раскритиковал, и заявил, что ничего из этого инструмента на монтаже мебели не нужно. И в следующем видео продемонстрировал свой набор инструмента из Китая (на самом деле кажется из Польши, но такого же примерно уровня). Это было весьма эпично.
Ogra
Да проще. Лён против фума =)
Free_ze
Актуально уже скорее фум против унилока)
Ogra
Мне две недели назад со льном делали, и я так думаю, про унилок они даже не слышали.
Free_ze
Кошмар сантехника-хипстера: пластиковые трубы, лён и сверху красочкой…
wert_lex
Давайте я попробую. У меня есть интересный опыт вида Scala -> JavaScript (и сервер и фронт) -> Typescript -> Scala.
Вообще, безотносительно самого JS мне очень нравится связка JS + TS из-за gradual typing. Хочется писать быстро — пишешь быстро на JS и надеешься на лучшее. Хочется писать надежнее — развешиваешь типы и спишь по ночам крепче.
Если кому-то интересно, я с радостью могу поотвечать на волнующие вопросы :)
PsyHaSTe
Немного не в тему, но как вообще со скалой, с вакансиями и т.п.? Мне многие знакомые советуют с C# свичнутся, для большей продуктивности, но я не вижу ни вакансий, ни потребностей рынка… С удовольствием почитал бы статью, но сойдет и комментарий.
wert_lex
Они есть, но если сравнивать с C#, Java и тем более JS — значительно меньше.
Вообще на мой взгляд Scala сейчас переживает не лучшие времена. Волна хайпа прошла, scala-only фичи потихоньку подвозят в мейнстримовые языке (те же лямбды в Java заставили задуматься очень многих о том действительно ли им всё это надо или и джавы хватит). Kotlin, опять же появился.
Вообще по своему Scala-опыту могу сказать, что использовать Scala как Better Java не очень хорошо выйдет. Оно хоть и достаточно интероперабельно, но не так всё гладко. Плюс многие Scala-библиотеки делают вещи scala-way (чем и хороши). В этом плане Kotlin выглядит сильно лучше.
Но и нельзя сказать что рынок совсем глух. Даже на неактивный в данный момент профиль на upwork раз в пару недель уж точно инвайт на Scala-интервью прилетает.
Ну и еще такое дело… чтобы понимать хайповые Scala-штуки, нужно хоть немного уметь Haskell. Не то, чтобы обязательно, но это сильно упрощает жизнь и даёт понимание того, что коллектив авторов там не совсем упоролся, а просто решает функциональным образом некоторые проблемы.
PsyHaSTe
Просто хочется нормальный паттерн-матчинг, нормальные атд (и гадт при должной фантазии), типы высших порядков, literal типы, path-dependent типы и т.п., человеческие трейты, импликиты, макросы, развитая и популярная ФП экосистема… Беттер джава не сильно инетерсует, скорее интересует вариация "раста без борроу чекера, на гц, но с хкт". Просто у меня выбор как раз между ними. С одной стороны не очень популярная скала. С другой — раст, которым не очень легко заниматься в мейнстрим разработке, даже коллеги-растовчане на нетривиальные вопросы предпочитают отвечать в духе "а нафига ты пишешь это на расте?". Хаскель сильно академичный + мне не нравятся многие вещи (вроде специального синтаксиса для
filter
, странные операторы вроде++
и т.п.). А больше и нет альтернатив, где можно писать типичные-задачи более удобно и красиво.Cidevant_Von_Goethe
A что плохого в функции filter в Haskell?
IMHO, это само совершенство!
PsyHaSTe
Я про
[x | x <- [50..100], x 'mod' 7 == 3]
. Как по мне, странноватое решение.Cidevant_Von_Goethe
это не filter а list comprehension
PsyHaSTe
Ну вот часть
x 'mod' 7 == 3
это чистоfilter
, зачем её выносить в синтаксис если можно было бы просто вызвать как функцию как в вашем примере не сильно понятно.Cidevant_Von_Goethe
В самом описании list comprehension в Haskell написано, что это «syntax sugar».
тоже самое что и0xd34df00d
Это вы ещё SQL-like list comprehensions не пробовали. Ну и следующий раздел про monad comprehensions заодно.
0xd34df00d
Да не такой уж хаскель академичный. Академичность — это какой-нибудь Idris или там, не знаю, Zombie.
А ++ мне тоже не нравится. Но есть же
<>
, как и для всех остальных моноидов.Cerberuser
Я ж надеюсь, Вы не про этот Zombie?..
0xd34df00d
Нет, про этот.
PsyHaSTe
Idris это вообще уже удел оторванных чистых математиков, которые еще и ХМ без ограничений юзают :)
Хотя я, конечно же, могу ошибаться. Для меня это 'Known fact', без какого-то конкретного достоверного источника.
0xd34df00d
Гы, а математики считают, что идрис — это вполне себе попытка сделать язык общего назначения с завтипами, на котором можно не только теоремки доказывать.
Druu
Кек. Ну математики пусть считают, они умеют иногда :)
sshikov
Со скалой вас без вопросов возьмут в мир BigData (Spark и т.п.). Это та еще потребность рынка.
PsyHaSTe
Ну вот я смотрю на тот же хх и грустно становится. Не, с текущей ситуацией с рублем, скорее всего, нужно уже смотреть на линкедин и аутсорс на запад, но это пока только перспектива. И если на шарпы я могу в течение недели найти пару хороших оферов, то на скалу всего 12 вакансий в принципе было, когда я последний раз смотрел, из которых с котами только половина.
sshikov
Для начала, включите в поиск Hadoop и Spark, и оцените разницу в числе вакансий. У нас вот де факто Scala == Java, на чем хочешь, на том и пишешь.
Хороший оффер это сколько?
PsyHaSTe
Хороший я больше считаю по вакансии, нежели по деньгам. А то последний год я проработал в подвале, вот там было не очень :D
Если по деньгам, мне кажеся от 3к белыми на сениор позицию это хорошие деньги.
sshikov
Это в рублях в пределах 180к? Так это совсем не фокус для сеньорских вакансий. Но только реально сеньорских, конечно…
PsyHaSTe
Это 198 600,30 по текущему курсу, как подсказывает гугл.
Это моё личное мнение. Знаю людей, которые больше 250 получают и им мало, но я пока не дорос. Но стремлюсь достаточно активно.
creker
Только скала это наверное наименьшая из экспертиз, которая нужная для работы со спарком. Я на нем успешно писал, не зная ничего ни о скала, ни о JVM языках вообще.
sshikov
Тут зависимость обратная — нужны spark-разработчики. Человека с экспертизой в скале взяли бы с удовольствием — и не потому, что скала нужна сама по себе, а потому что спарк ему будет достаточно близок.
А то что можно писать скажем на питоне или на R — это понятно.
eugenk
Спасибо, действительно интересный коммент. Извините, немного не в тему, а с scala.js Вы работали? По-моему вообще самое лучшее что сейчас существует для фронтэнда. К сожалению вынужден был от этого отказаться из-за совершенно безобразной поддержки в IDE. Сейчас работаю на ts + php в рамках единого проекта под vs code. Но если бы была нормальная поддержка, с огромным удовольствием ушел бы с ts на scala.
P.S. Да, на всякий случай ко всем. Если хотите изучить функциональщину, играться со scala будет далеко не лучшей идеей. Слишком многое scala позволяет и Вы ничему не научитесь. Начните с Haskell. Он просто не позволит Вам писать не идеоматично. Сам сейчас именно его потихоньку изучаю.
wert_lex
Со scala.js не работал сам, но на одном из проектов работал с фронтендерами, которые как раз выкидывали всё написанное ими же на scala.js в пользу более джаваскриптовых фреймворков.
Тогда меня фронтенд не интересовал совсем, но эти ребята говорили, что:
а) реальной гетерогенности нет
б) тулинг ужасный
в) готовые фронтендовые либы очень грустно интегрируются
г) и если всё правильно помню, то на выходе у них получался огромный бандл
И, честно говоря, ощутив на себе проблемы на стыке JS и TS, при том что TS надмножество JS, могу сказать, что я бы подумал несколько раз крепко, прежде чем использовать что-то такое развесистое поверх JS.
sshikov
У нас в отделе был такой опыт. Очень любили scala.js, на мой взгляд — незаслуженно. Результаты того не стоят.
>вообще самое лучшее что сейчас существует для фронтэнда.
Не соглашусь. Безобразной поддержки IDE уже было бы достаточно. Впрочем, расскажите, почему вы так думаете?
FRiMN
Мне кажется, что автор неоправданно сравнивает C# с JS, противопоставляя статическую типизацию динамической. JS слабо типизирован, но не все языки с динамической типизацией слабо типизированы. Далеко не во всех языках с динамической типизацией можно к строке прибавить число.
Кроме того, не с проста во многих языках со статической типизацией есть тип any. Даже в C# есть утиная типизация.
Это я к тому, что вероятно, неверно относить язык к тому или иному виду типизации, не говоря уже о том, чтобы выбирать язык только по этому параметру. Да, есть языки которые сильно тяготеют к какой-то одной стороне. Но есть и такие, которые "плавают" где-то близко к середине.
Как бы вы ответили на вопрос: какой язык лучше, со слабой статической типизацией, или сильной динамической?
kagetoki
Язык либо динамически типизирован, либо статически. Он не может "плавать" где-то посередине. В языке могут быть различные средства для того, чтобы что-то почерпнуть из другого мира, например
dynamic
в C#. Но от того, что в сишарпе естьdynamic
он не стал динамически типизированным.Еще, например, в С# есть неявное приведение типов. Но оно работает, только если заранее описать, как оно должно работать, оно не работает для всего и всегда. Да и делается обычно это для каких-то совсем очевидных случаев, вроде
int -> long
.Между тем, утиная статическая типизация это все равно жесткая типизация, абсолютно не важно, что она не номинальная. Она все равно жестко следит за соблюдением объявленного контракта, и если этот контракт нарушить, то ошибка проявит себя еще до запуска приложения: код не скомпилируется.
Я не знаю ни одного языка со слабой статической типизацией, и не представляю, кому это могло бы понадобиться. Если уж взялся делать в языке статику — нахера делать ее слабой, в чем тогда смысл?
Ogra
Язык: Си, зачем: fast inverse square root =)
0xd34df00d
Его никто не мешает сделать в языке со строгой статической типизацией, если там есть, например, coerce. Заодно компилятор проверит, что типы действительно имеют одинаковое представление.
KvanTTT
Корректней сказать для примитивных типов и при отсутствии потери информации int (4 байта) -> long (8 байт). Обратное преобразование породит ошибку компиляции.
FRiMN
Возможно, я что-то упускаю, т.к. не знаком с C#, но разве dynamic не использует DLR? Это же не библиотека, а часть языка.
Здесь соглашусь. Я имел в виду, что те вещи, которые ассоциируют в основном со статической типизацией, характерны и для многих языков с динамической типизацией. И наоборот. Те же object/any, например.
По поводу статьи:
Статическая типизация не панацея. Например, статическая типизация в Java не помогает отловить NullPointerException.
Вот, чуваки утверждают:
И действительно, как часто, по вашему, у программистов на языках с сильной динамической типизацией возникает несогласование типов (none/null и т.п. не в счёт)? А в статически типизированных?
И каким образом компилятор статически типизированного языка поможет мне защититься от внешних данных с неверным типом?
Нашел на просторах интернета текст, с которым по большей части солидарен, и дабы не пересказывать, оставлю цитату:
И ещё от туда же:
PsyHaSTe
Потому что в жабе достаточно слабая типизация. В любом современном языке у вас есть
Maybe T
/Option<T>
/..., где вы явно говорите, что тут может быть нулл, и вы не забудете его проверить, и не будете проверять там, где не надо. Так что компилятор именно что помогает отловить нулл. Более того, в C# 8.0 это ключевая фича новой версии языка.Практически любую ошибку можно выразить на уровне типов. Это не всегда полезно, и можно упороться тем, чтобы сделать классы IntOne, IntTwo и т.п., описывая все их свойства (такие завтипы для бедных), но в моей практике скорее все наоборот: типы позволяют найти почти все ошибки, кроме совсем дурацких, вроде написали 1 вместо 10. Которые легко чинятся, потому что совершенно попиюще кричат из кода "Я НЕВЕРНОЕ ЗНАЧЕНИЕ" :)
По-поводу первого комментария: человек пришел из С++ в питон и там все было зашибись. Окей, только типы никакого отношения к JIT и дополнительным оптимизациям не имеют отношения, типы это про статический анализ на предмет ошибок. Типы могут эффективно полность заменить все тесты в программе, при должном старании. Хотя выгоднее оставить 10% тестов, чтобы упростить типы, но все равно это позволяет в 10 раз сократить их количество, с одновременным увеличением качества и упрощением дальнейших доработок.
Посмотрите на ORM в каких-нибудь шарпах. Как по мне, во-первых сильно проще их писать (потому что ОРМ сама по себе сложная штука, а все сложные штуки, как я уже выше писал, проще писать с поддержкой от компилятора), во-вторых их проще использовать, потому что компилятор помогает понять, что можно делатЬ, что нет, и т.п.
Попробуйте на любом динамическом языке попробовать разобраться в любой неизвестной библиотеке с отключенным интернетом, сразу станет понятно, о чем я говорю.
Интерфейсы и экзистенциальные типы в помощь.
Зато в динамике легко забыть, какой именно набор полей может быть у сущности, и начать передавать еще один, но не во всех местах, а потом радоваться взлетающим
undefined is not a function
.Надо имплементировать только тот интерфейс, которым пользуется вызывающий код. То есть тот, которым вы и так неявно пользуетесь, когда надеетесь, что у объекта есть метод
.foo()
. Только в другом случае вы не надеетесь, а точно знаете, что он будет.В C# тоже так можно, только так не делают. И не потому, что медленно, а потому что ненадежно. Не говоря про то, что визитор ортогонален hasattr'у.
Free_ze
Никто не говорит, что это панацея. Но целый пласт ошибок убирает.
При поддержке крупных кодовых баз, рефакторинге — это очень частое явление. Причем, изменчивость софта на динамических языках при этом играет не в те ворота.
Разница в том, что со статической типизацией большинство ошибок отлавливается на этапе компиляции, а с динамической — в рантайме, причем лишь тогда, когда кусок плохого кода сработает.
PsyHaSTe
Да люди не знают. что в нормальном проекте даже 500 файлов перелопатить (например, у меня такое было с миграции с монго драйвера версии 1.0 на 2.0) не вызывает проблем в статическом языке, а в динамике обычно дернуться боишьсЯ, чтобы где-то не взорвалось, файлики меняются аккуратно, по одному, и желательно с фоллбеком. Поменять треть проекта так, чтобы сохранить работоспособность, это для людей совершенно непонятно :)
Free_ze
Когда-нибудь в браузерах у JS отрастет родная типизация и евангелисты расскажут нам про очередную революцию, которая изменила мир
веб-программирования. Конечно, если раньше сюда не заползет PHP-рантайм via WASM.sentyaev
В этом случае статическая типизация вам только ошибки компиляции покажет если какое-то свойство/метод/класс изменился.
Это и погрепать можно, конечно не так эффективно, но все же не такая уж и проблема.
Остальное только тесты. Я и на Python и на C# и на JS пишу, сильно не страдают от динамической типизации, хотя я и согласен с тем, что статическая типизация дает немного разгрузить мозг, ибо не нужно весь стек вызовов вместе с тем как по этому стеку данные трансформируются в голове держать постоянно.
Но, хорошее тестовое покрытие считаю более важным, чем наличие/отсутствие типизации.
Free_ze
Это и есть баги типизации, которые без этого этапа останутся незамеченными и начнут всплывать гораздо позже (+ к временным затратам на стабилизацию), а то еще и «дрейфовать» в случайные стороны, если типизация будет не только динамической, но и слабой.
Нельзя. Греп не видит контекста. Да и вы это в 2к18 руками будете делать?!
Более/не более — это софизм. Зачем теплое с мягким сравнивать? Если будет и то, и другое — это только лучше.
sentyaev
Тум можно скатиться в разговор о проектировании, о том что интерфейсы модулей менять нужно аккуратно, а лучше сразу делать стабильными.
Сильное утверждение. PyCharm кстати очень помогает, когда можно использую его, когда нет — grep. Соседи вообще в vim'e сидят и все у них получается.
И то и другое — лучше, я согласен. С другой стороны если есть типизация, то ее одной недостаточно для того чтобы гарантировать отсутствие ошибок, а одних тестов — достаточно.
Free_ze
Ага, есть время покрывать тестами, но нет писать типы?) Не укладывается это в парадигму динамичной разработки в условиях изменяющихся требований, которая и дает смысл скриптовым языкам.
Не буду спорить, до определенного размера кодовой базы это имеет смысл. Но вокруг нас приложения все больше, приходит время что-то менять в подходах.
Статическая типизация — это тесты, которые уже написаны за вас и запускаются при каждом изменении. Это взаимодополняющая штука с юнит-тестами.
sentyaev
Я такого не говорил.
И с этим я не спорю, а согласен на все 100%
VolCh
Смысл скриптовым языкам даёт прежде всего отсутствие этапа компиляции. Скриптовый язык может иметь статическую строгую типизацию и, наверноё, смысла в нём будет больше чем в любом другом. Особенно, если в нём будет JIT.
lega
mayorovp
От того, что вы повторите эту цитату еще несколько раз, правдой она не станет.
kozar
Из последнего абзаца получается интересный вывод:
И тогда, как и автор, через несколько лет сможете про себя сказать —
Whiteha
Я понял что вы друг друга не поняли, но не понял в чем же автор увидел причину конфликта, как-то очень размыто про разные подходы, примеры кода бы помогли проснить ситуацию.
botyaslonim
Не очень понятно, с чего вы решили, что статическая типизация уберегает от ошибок.
Пишу на JS давно и много, по опыту моему и моих коллег, собственно на ошибки типов приходится мизер от всех ошибок, которые бывают на фронтенде.
PsyHaSTe
Зависит от того, как эти типы использовать.
Если создать они огромный
byte[] Memory
и дальше с ним по указателям работать, то толку будет мало. А можно сделать типIsOddNumberLargerThanTen
и во время компиляции получить ошибку, при попытке проверить, является ли число простым.Strain
Отличная тема для знатного вброса для холиваров. Мои поздравления.
random
Разногласия возникают не из-за разницы взглядов на решаемую проблему.
Причина — в психике и, вероятно, нейрофизиологии.
Если послушать аргументы сторонников строгой типизации, в них звучит одна эмоция — страх. Страх допустить ошибку. Страх, что код станет неуправляемым — со временем или при использовании другими людьми. У автора:
Тем, кто в разработке давно, очевидно — компилятор не спасает от ошибок, которые встречаются в реальном мире, и не делает код делает код лучше структурированным.
Но страх — очень сильная эмоция. Кто-то с ним (и заодно со сложностью) справляется лучше, а у кого-то мозг просто устроен по-другому. И в дело вступает любимая психологическая защита людей с высоким интеллектом — рационализация.
eugenk
Честно говоря про страх подумал только после Ваших слов. Да, и это имеет место быть. Хотя я бы называл это немного по-другому — защита от совсем уж глупых ошибок. Но для меня лично главное пожалуй это хорошая поддержка IDE. Автокомплит, показ структуры и т.п. Возможно крутые перцы тут меня засмеют, мол православный и духоскрепный прогер обязан уметь писать код хоть в notepad-е, но моя практика показывает, что писать код для современных окружений (ОС, броузер и т.п.) можно только с хорошей поддержкой IDE. Иначе слишком много деталей приходится держать в голове и процесс превращается в мучение. Для динамических же языков поддержка IDE увы, но просто не может быть хорошей.
gatoazul
Мне кажется, необходимоcть IDE для работы с современным окружением говорит о том, что это окружение уже перешло порог сложности, доступный человеку.
Требуются абстракции следующего уровня, и в окружении, и в языках.
Использование IDE несколько тормозит этот процесс, но он неизбежен.
PsyHaSTe
Скорее нежелание. Да, я не хочу допустить ошибку. Я хочу, чтобы код был управляемым при использовании другими людьми. Я хочу, чтобы если у них нет интернета, они все равно могли понять, Какие данные в какие функции они могут передать, и на что могут рассчитывать в ответ. Это плохо?
Компилятор, очевидно, спасает от ошибок, которые встречаются в реальном мире, и делает код лучше структурированным.
random
Нежелание — не эмоция. Это реакция на эмоцию.
PsyHaSTe
Допустим вы правы (я не согласен, но в качестве гипотезы примем). Что дальше? Человек, в страхе совершить ошибку, ошибается еще больше? Отнюдь. Он стремиться ошибаться меньше (что у него неплохо получается), от чего он только в выигрыше. Нормально бояться ходить без страховки на 50 этаже просто по приколу. Нормально бояться залезать в пасть тигру, который неделю не ел. И так далее.
random
Строго говоря да. Человек в страхе более подвержен ошибкам мышления.
Читаю в сердцах, что тема вам не безразлична. Если так, рекомендую книгу «Укрощение амигдалы»
ApeCoder
Этот демагогический прием называется "чтение в сердцах". Если вы откроете поисковик и напишете "why static typing" вы легко найдете другие доводы за. И почему это может быть не эмоция, а расчет?
А так же "IDE мне подсказывает" — уже не только про ошибки.
"Аргументация демагога начинается с выражений типа «каждому известно, что…», «очевидно…», «наука давно доказала…», «все успешные бизнесмены знают, что…» и так далее. При этом эксплуатируется ложная гордость слушателя (если что-то подаётся как «общеизвестное», то ему бывает сложно признаться даже себе, что он об этом никогда не слышал) либо приводятся мнимые авторитеты, согласные с высказыванием, что действует на людей, склонных доверять авторитетам."
Когда вы говорите "не спасает от ошибок" вы имеете ввиду не спасает от всех ошибок или нет вообще никаких ошибок, от которых он спасает?
И опять "чтение в сердцах"
PsyHaSTe
Почитал ваш ответ, понял, что зря вообще начинал отвечать данному товарищу.
random
В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами.
PsyHaSTe
Давайте повторим
— компилятор не спасает от всех ошибок или нет вообще никаких ошибок, от которых он спасает?
— В реальном мире — это там, где код начинает взаимодействовать с чем-то, что находится за его пределами.
Простите, но это не похоже на ответ на поставленный вопрос, а скорее псевдофилософское словоблудие.
random
Давайте повторим. Вот утверждения:
Привести примеры взаимодействия?
Отвалившееся соединение с базой данных. Ответ от API с некорректными данными в неожиданном формате. Пользователь сделал что-то не то.
Надеюсь, так понятней.
Если вопрос был в том, спасает ли компилятор вообще от каких-то ошибок, то… Риторические вопросы ж не требуют ответа?
Druu
А вопрос был не риторическим и ответа требовал.
ApeCoder
Я процитировал ту фразу на которую отвечал. Она относится не к самой статической типизации, а к тому, что именно говорят ее сторонники. Вы не полностью выразили их доводы вот и все.
Тогда у меня большинство ошибок происходит не в "реальном мире", а в коде, который неправильно взаимодействует с другим кодом (библиотек, чужих и собственных, операционной системы и т.д.).
sentyaev
Это не страх, это Уверенность. Когда у вас статическая типизация вы просто уверены, что все стыкуется. Плюс получаем поддержку IDE для рефакторинга.
А он и не должен, это делают тесты.
IvanNochnoy
Проблема с тестами, в том, что они не проверяют правильность выполнения программы, а лишь то, что программа соответствует некоторой спецификации, которую программист считает правильной. Но то, что сама спецификация может быть ошибочна — никак не проверяют. Проще говоря — тесты могут найти ошибку в коде, но не ошибку в голове. Посмотрите на любой проект на GitHub со 100% покрытием тестами, а затем посмотрите на количество issue в этих проектах. Вопрос: откуда берутся баги при 100% покрытии? В то же время, Type Driven Development очень часто находит ошибки именно в логике работы программы. Так что лично мне ясно, что, хотя тесты и нужны, но они никак не могут заменить хорошей типизации.
sentyaev
Не только программист, но еще и QA и продакты, и бизнес. В мире вообще много чего случается из-за ошибок. Это не проблема тестов.
Вы правильно заметили, тесты помогают найти ошибки типа: если мы считаем, что 1+1=2, то функция сложения с аргументами 1 и 1 вернет 2.
Я имею ввиду другое, ошибки которые помогает выявить компилятор, и ошибки которые помогают выявить тесты — они разного класса.
Грубо говоря, компилятор говорит о синтаксически корректной программе, а тесты о логически корректной программе (в том, что мы считаем корректным на этапе разработки).
С другой стороны, я считаю, что по важности, статическая типизация это 10%, а тесты это 90%.
Могли бы вы пример привести, я не очень понимаю как типы помогают находить ошибки в логике.
IvanNochnoy
Пример 1
Допустим у Вас есть объекты Person, Security и GentlemanClub. Клуб мужской и туда запрещен доступ женщинам, для этого есть объект Security, который проверяет свойство Sex объекта Person, и пропускает в клуб только если Sex == Male. Вы пишите тесты и все зашибись. Но внезапно Вы обнаруживаете, что в клубе есть женщины! Откуда? Дело в том что свойство Sex объекта Person изменяемое, и кто-то из клиентов изменил это свойство уже после того, как прошел проверку Security! Ваши тесты были зелеными, но, увы!, вы не подумали о том, что свойство может быть изменено, до тех пор, пока не получили багрепорт. А вот если бы, вместо того, чтобы писать тесты, которые мышей не ловят, вы бы сделали так, что свойство Sex устанавливалось только в конструкторе класса, то такая ситуация была бы невозможна в принципе.
Пример 2
Военные летчики чуть не убили себя во время вылета. Их истребители внезапно перестали подчинятся штурвалу и перевернулись вверх шасси. Все тесты были норм. Но дело в том, что при пересечении экватора значение широты стало отрицательным, и это привело к проблемам в каком-то приборе. А вот если бы программист вместо типа int использовал uint (беззнаковое целое), то компилятор не отстал бы от него, пока он не взял бы модуль от значения перед тем, как передать его в метод. Да, и, конечно, программеру не пришло в голову, что есть два полушария на земле.
Пример 3
Вы разрабатывете калькулятор индекса массы тела, есть метод Calculate(int height, int age, int weight). Вы считаете себя довольно стройным, но вызов функции с вашими данными приводит к тому, что у вас ожирение 3-й степени. Как так? Оказывается, данные нужно было указывать в фунтах и дюймах, а вы указали в метрах и килограммах. А вот если бы вы воспользовались такой штукой, как Units of measure, которая присутствует в F# то такой ошибки не возникло бы, так как без явного указания единиц измерения (70<inch>, 20<year>, 25<pound>) программа просто не откомпилировалась бы.
Глубокое заблуждение, как видите, типы бывают умнее программиста и его тестов.
sentyaev
Первый пример надуманный, в динамически типизированных языках тоже есть immutability, например в Elixir. Да и в том же JS можно сделать приватные переменные через замыкание. Это больше относится к проектированию, чем к типизации.
Второй пример тоже надуман. Широта и долгота может быть представлена как отрицательное число. Если еще добавить высоту, то можно летать ниже уровня моря.
Из вики Форматы_записи_географических_координат:
Третий пример хорош. Мне очень нравится эта фича F#. К сожалению в мэинстрим языках этого пока не наблюдаю.
И я вообще ЗА статическую типизацию. Но, в рамках этой статьи и комментариях к ней, у меня складывается впечатление, что большинство сильно переоценивает вклад этой самой статической типизации в конечное качество продукта.
VolCh
Типы если и несут какие-то знания, которыми программист не обладает, то лишь случайно. А вероятность того, что программист решил поставить где-то any чтобы не выдумывать сложный граф типов, я оцениваю, субъективно, куда выше, чем вероятность того, что ему будет лень написать три строчки теста.
ilja903
Я перешел от статики к динамике, наиболее полный ответ почему:
www.infoq.com/presentations/Simple-Made-Easy
www.youtube.com/watch?v=rI8tNMsozo0
gist.github.com/oakes/c82cd08821ce444be6bf
Deranged
Да, я тоже в первый раз, когда попробовал написать что-то на QML малость прифигел.
До сих пор интересует вопрос, какому гению пришла в голову идея в качестве базы для QML выбрать JS? И это во фреймворке на C++, где типизацией пронизано буквально всё, а в QML её особо нет, даже если захочется. Порог вхождения во фреймворк по этому получился просто астрономическим. Нужно не только уметь мыслить «по ООП-шному», но еще и по JS-ному. При чем и то и другое нужно уметь делать одинаково хорошо…
SergeyKhorn
Вообще, дело же в первую очередь в мышлении, в привычках. Совершенно не вижу причин для негодования или пренебрежительного отношения к тем, кто пишет на динамически типизированных языках.
Огромная часть frontend'а — это landing pages и небольшие сайты, например, портфолио или промо. Можно, конечно, спорить frontend-программист ли вообще человек, встраивающий slider-widget на сайт, но так называются вакансии и позиции, на которых они работают. Годами. Суть же в том, что для выполнения этой работы, которой много, типизация просто-напросто избыточна, читай не окупает себя.
Как только задача перестает быть тривиальной, она начинает требовать декомпозиции. И как следствие для ее решения выстраивается архитектура и подбираются паттерны. Да, трудно спорить о том, что ООП может поспособствовать развитию системного мышления, это то, что Вы описывали: системные объекты и их взаимодействия, но это вовсе не обязательно. Более того, не всегда опыт и владение языком со статической типизацией ведут к тому, что у человека появляются способности к системному мышлению и проектированию. Ровно как и то, что человек пишет, скажем, на JavaScript, не лишает его этих способностей.
Кстати, качественное тестирование помогает решить не проблему TypeError, а logical errors.
В любом случае, судя по Вами же описанному случаю, у Вас проблема заключалась не в разных языковых предпочтениях, а в том, что Ваш друг и коллега отказывался от этапа проектирования. Совсем ли он отказывался или настаивал на поэтапном проектировании, призывая выбрать agile-подход, не понятно, а может быть он вообще хотел сначала сделать прототип (как частенько бывает во frontend), но суть вашей проблемы именно в этом.
А к написанию этой статьи Вас подвигло то, что вы распространили отдельно взятый частный случай, когда, пишущий на динамически типизированном языке, отказался от проектирования, на всех программистов использующих такие языки.
Не все такие.
Вы знаете, кажется, что Вы раздули из мухи слона. К сожалению, проблемы проектирования и взаимопонимания (а может и лидерства, кто вас знает, может подоплекой разногласий было выяснение того, кто из вас у руля и принимает окончательное решение) решаются не через изучение статически типизированных языков, а только через улучшение владением естественными языками, те самые пресловутые soft skills.
misha_shar53
<Мой код отвечает на вопрос «Как с этим работать», а как код разрабов, привыкших к динамической типизации — «Как это работает». Оба подхода имеют право на жизнь, и есть инструменты для обоих, но только один может стоять во главе угла.>
Но это не все типизация в языке может вообще отсутствовать.
<Статическая хороша для больших проектов, которые годами делают сотни разрабов, а динамическая — для маленьких команд, для задач, где часто нужен write-only код. С динамической ты экономишь время и силы в начале разработки, а со статической — в конце.>
Сомнительное утверждение. Связь не просматривается.
misha_shar53
<Идея ставить во главу типы серьезно повлияла на мое мышление разработчика.
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю. Увидев любую задачу, я пытаюсь представить ее решение как набор типов и принципов их взаимодействия. Когда я разрабатываю модуль, первым делом определяю, какими типами он оперирует и какими взаимодействует со своим окружением. Я даже не помню, как я подходил к решению задач раньше. >
В этом вся соль. Проектирование приложения отталкиваясь от данных. Абсолютно с вами согласен в этом. Только типизация тут вообще не причем. Данные могут быть спроектированы любыми способами способами, хоть в комментариях. Типы только один из способов их спроектировать, и возможно не самый лучший. То что вы к этому пришли через типы, ну прекрасно. Утверждать что проектирование данных вытекает только из статической типизации я бы не стал. Это все таки разные категории.
misha_shar53
<Поэтому я не хочу искать компромиссов, а говорю прямо. Если только начинаете изучать разработку, начинайте со статической типизации.>
А еще лучше вообще забудьте о типизации как о дурном сне.
LexLiven
Есть подозрение, что вы не только из-за типизации не поняли друг друга. Есть два метода разработки — нисходящий и восходящий. Вы — приверженец восходящего подхода. Это когда сначала пишутся все функции, методы и прочие взаимодействия составляющих «кирпичиков», а потом из них собирается проект. А ваш друг — приверженец нисходящего. То есть ставится задача, которая постепенно дробится на все более мелкие части. То есть пишем функцию «MakeBest()», в ней пишем вызов «MakeGood()», и только потом создаем эту самую MakeGood(), прописывая что именно она должна сделать.
Ваш подход позволяет реализовать более продуманную в техническом плане задачу, но требует держать в голове весь проект сразу. Подход вашего друга позволяет десятку джуниоров работать над проектом, вообще не представляя, зачем это все пишется. Как результат — получается, что функция на верхнем уровне выполняется год с небольшим и теряет память, потому что один из джуниоров написал рекурсивный вызов своей функции, которая использует функция другого джуниора с временем выполнения O(n^3)/
ApeCoder
В современных IDE так можно вполне себе и со статической типизацией. Они умеют создавать методы по примеру использования.
ApeCoder
dzsysop
Судя по факту что количество коментов продолжает нарастать день ото дня автор 100% прав!
«Приверженцы» таки никогда не поймут друг друга и топик явно тянет на холиварный.
VolCh
Понять оппонента не сложно. Сложно убедить его, что он не прав, или убедить себя, что ты не прав.