LINUX.ORG.RU

Зачем нужна статическая типизация?, или Вы всё врете!

 ,


1

4

В теме "Питонячьи радости " на последних страницах между мной и @rtxtxtrx внезапно разгорелся спор, из которого я понял, что есть еще люди, которые не считают динамическую типизацию (в том виде, в котором она представлена в Питоне, а именно строгая динамическая типизация) серьезным недостатком при работе с большим объемом кода, особенно при рефакторинге. Вообще изначально разговор завязался вокруг назначения type hints введенных в Питон 3: я утверждал, что они нужны для создания семантических связей в коде, которые будут препятствовать внесению деструктивных изменений в код в результате опечатки или иной ошибки кодера (изменил код, в результате которого какое-либо выражение получило некорректное значение, которое тем не менее обладает схожим с корректным значением типовым контрактом, поэтому при запуске код не «упадет» сразу, указав на проблему); оппонент заявил, что они нужны для (само)документации и не более того.
Но потом выяснилось, что и царь-то ненастоящий (читай, статическая типизация). Не нужна она, просто именуй сущности понятно и уповай на строгую типизацию. А если типизация не строгая, то сами виноваты, у нас в Питоне всё ОК.
Поскольку тема большая и вкусная, я предлагаю всем обсудить этот очень важный вопрос в меру скромных сил и познаний каждого желающего. Обсуждение вторичных вопросов, как-то «статическая типизация нужна для генерации эффективного кода», «при динамической типизации тип только один, object» etc. не предусмотрено — спорим только о том, дает ли статическая типизация выигрыш, если надо перекраивать несметные тыщи kloc. Если есть вообще о чем спорить 😅.

★★★★★

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

Такое есть в лиспе, в Smalltalk, и в Factor, например.

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

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

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

lovesan ★★★
()
Ответ на: комментарий от no-such-file

На самом деле даже в хаскеле это работает херовато.

Работать может разве что в Idris/Adga и прочих theorem provers с зависимыми типами, но этими языками для разработки чего-то серьезного пользоваться нереально, слишком неудобно.

lovesan ★★★
()
Ответ на: комментарий от Nervous

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

lovesan ★★★
()
Ответ на: комментарий от cobold

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

На самом деле, не помогает, отчего Microsoft придумали в свое время венгерскую нотацию

lovesan ★★★
()

Зачем нужен питон? Вы все врете!

slackwarrior ★★★★★
()
Ответ на: комментарий от alysnix

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

slackwarrior ★★★★★
()
Ответ на: комментарий от crutch_master

ну вот как в яве это делается. Один раз получаешь свою жсонину, кидаешь в генератор «json - POJO», получаешь примерно похожие на правду классы. Обрабатываешь напильником по вкусу, юзаешь в проекте. В случае косяков либо смотришь лог, куда десериализатор выплюнет стектрейс, чего там и почему он не смог создать объекты нужного типа из того, что ему пришло, либо - если с типами все нормально и косяк в кривых данных - обмазываешь дополнительными логами или берешь какую-нибудь тулзу-дебаггер и цепляешься по JMX к запущенному инстансу твоей аппликухи. Даже простой visualvm из комплекта opеnjdk может достаточно наглядно показать тебе всю требуху работающей jvm.

arkhnchul ★★★
()
Ответ на: комментарий от crutch_master

Само собой, что мапа будет сливать по скорости доступа к «полю» по сравнению с норм. объектом.

а ты знаешь, как у тех же питона и жс объекты внутри устроены? Внезапно там атрибуты хранятся в обыкновенной мапе, а доступ instanceName.attributeName - просто сахар сверху.

arkhnchul ★★★
()

Вообще пора перестать называть JS, и, например, пыхоплеяду (Perl, PHP, Python, Ruby), динамическими языками. От этого у людей потом неверные впечатления о динамической типизации и прочем.

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

lovesan ★★★
()
Ответ на: комментарий от lovesan

От этого у людей потом неверные впечатления о динамической типизации и прочем.

просто расскажи о верной динамической типизации и чем она поможет несчастному человечеству.

alysnix ★★★
()
Ответ на: комментарий от crutch_master

я даже спрашивать не буду, как в таком случае выглядеть аналогиx?.y?.z?.find?.(...)или что-то сложнее.

с jackson-ом, например, можешь ходить по рандомной говне его методами. Смотришь, есть ли у тебя в распаршеном результате rootNode.path(«/x/y/z[Array]») и теребишь ему find.

arkhnchul ★★★
()
Ответ на: комментарий от lovesan

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

rtxtxtrx ★★
()
Ответ на: комментарий от alysnix

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

crutch_master ★★★★★
()
Ответ на: комментарий от arkhnchul

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

Ну это всё уже. Тут обсуждать нечего.

crutch_master ★★★★★
()
Ответ на: комментарий от arkhnchul

ты знаешь, как у тех же питона и жс объекты внутри устроены? Внезапно там атрибуты хранятся в обыкновенной мапе

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

crutch_master ★★★★★
()
Ответ на: комментарий от ugoday

Например, мне нужно, чтобы getIP() вернула корректный IP адрес

Отлично, вот тебе нужна функция с заголовком def getIP() -> IPAddress: …

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

Если класс IPAddress «неправильный» - напиши свой класс и свою функцию

И ваша статика никак мне не поможет решить эту проблему.

Статика как раз и решает твою проблему. Функция getIP() возвращает тебе валидный IP или райзится, других вариантов нет - ты можешь спокойно полагаться на это поведение.

При этом что такое правильный адрес — вопрос нетривиальный

Ясно. «Я программист и мне нужно чтобы синус был [-1 … 1], но если мне нужно то он должен быть [-3 … 3]»

no-dashi-v2 ★★★
()
Ответ на: комментарий от crutch_master

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

Суть в том, что в статической типизации ты всегда оперируешь объектами известной структуры. Поэтому ты не можешь просто взять всратый JSONObject и вернуть его как объект другого класса (точнее взять и вернуть то конечно сможешь - но это придется делать явно). Даже если «данные приходят во время работы»

А это значит, что ты напишешь код явного создания экземпляра объекта из JSONObject (и будешь обязан выполнить все необходимые проверки). И на выходе из парсера у тебя будет не «непонятно что» а объект достоверно известного типа с достоверно известным интерфейсом.

no-dashi-v2 ★★★
()
Ответ на: комментарий от monk

А внутри checkIP проверяется входящий параметр и возвращается или CorrectIP, если тот корректный или Nothing

Неет, из checkIP возвращается только CorrectIP, никакого Nothing. Тупо райзится эксепшн.

no-dashi-v2 ★★★
()
Ответ на: комментарий от no-dashi-v2

Тупо райзится эксепшн.

Вот это плохая идея. Эксепшн не видно в сигнатуре. И из-за эксепшнов написать надёжную программу на Си++ нереально, так как в try/catch надо заворачивать практически каждую строку, потому что неожиданный эксепшн может быть вызван откуда угодно.

monk ★★★★★
()
Ответ на: комментарий от monk

Эксепшн не видно в сигнатуре.

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

в try/catch надо заворачивать практически каждую строку

Если у вас надо заворачивать каждую строку - у вас что-то пошло не так. В нормальной ситуации вы заворачивает в try/catch эндпоинт и всё. Просто стройте нормально логику освобождения ресурсов, и всё будет ровно.

no-dashi-v2 ★★★
()
Ответ на: комментарий от no-dashi-v2

напиши свой класс и свою функцию

Которые будут работать во время исполнения кода. Т.е. «корректный у меня IP или нет» я буду знать запустив программу. Эдакая динамическая типизация на классах решит ограничения статической.

ugoday ★★★★★
()
Ответ на: комментарий от arkhnchul

давай предложи еще руками парсить

Так это не я. Это жабакодерки переводят руками из структуры в структуру. Давай, сбацай аналог на джаксоне

let fieldMap = {
  "a" : "x",
  "b" : "y",
  "c" : "z"
}

let trObject = function(obj, map) {
  return Object.entries(obj).reduce((s, [k, v])=>{
    let targetKey = map[k];
    if (targetKey) {
      s[targetKey] = v;
    }
    return s;
  }, {});
}

...//validate jsonShit...
let myCoolObj = trObject(jsonShit, fieldMap);
...

Вот функция на 5 строк, которая преобразует объект в объект. Добавь 2 строки оно еще преобразовывать значение будет по функции из мапы. Добавь сюда еще одну функцию, пару строчек кода + нотацию и оно будет распихивать по вложенным объектам/массивам. Это - рядовые возможности языка. А в жабке джаксон и все ручками, ручками, вот как ты предлагаешь.

crutch_master ★★★★★
()

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

def listen(self, address, ...):

Без хинтов ничего непонятно что туда передавать, а так понятно:

def listen(self, address: tuple[str, int],

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

Когда аргумент называется uuid непонятно что передавать инстанс UUID, число, строку. Если строку, то hex, или хххх-хх… Такая же хрень с датами, непонятно что передавать, если заходит речь о деньгах, потому как можно Decimal передать, можно сумму представить как флоат, можно вообще целым типа 1 рубль = 1000. Я в чье-то апишке видел такое. Проверка типов не нужна совсем. Вот возьмем реальное прилржение. И это будет сайт ес-но. Данные которые ты передаешь используются в запросах. И там уже помсгрес либа монга, если схемы используются, кинет ошибку о невозможности конвертировать тип/несоответствии типа. Но напоямую с базами тож никто не работает, есть еще прослойка в виде сериализатора, который занимается валидацией входных данных и конвертацией типов между базой и питоном. Например, старое marshmallow, более новое pydantic. Да это ничем не отличается от написания портянков на go или java чтобы распарсить json’ину, но там тоже нельзя просто тип описать, потому если ваодишь номер телефона, то там опр формат и тп. Итого преимущества скриптухи: кода писать раза в три меньше (факт), пролцесс его отладки - это просто сказка: ты можешь буквально по шагам вперед-назад ходить, смотреть как изменяются переменные и тп. Ну тут тоже писали уже, что в отличии от сисярпа не нужно ждать по 10 минут пока код скомпилиться (я писал на шарпе - мне этот опыт не понравился). Это касается так называемой продуктовой разработки, которая вся сводится к клепанию апишек и формошлепству, причем апишки простые: обработчик роута, сериализатор, модель для представления таблицы в бд, логики самой мало, ее можно вообще в базу перенести, всяких тригеров и процедур насоздавать… Чтобы глупых тем не создавать нужно просто в теме покрутиться

rtxtxtrx ★★
()
Ответ на: комментарий от rtxtxtrx

Долговато ты на ответ сподабливался, аж к 5-ой странице смог. Но естественно, не убедил 😊.

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

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

Без хинтов ничего непонятно что туда передавать, а так понятно:

def listen(self, address: tuple[str, int],

Если у тебя здесь str это не питонячий стринг, а domain-specific сущность, и int не питонячье целое, а упомянутый UUID, то ничего тебе не понятно. Проблема скриптописателей в том, что они всюду тащат встроенные типы данных, которые вроде как везде подходят, но почти всегда имеют «чуть-чуть» более широкий контракт на использование, чем нужный тип. В точности, как ты описал:

если заходит речь о деньгах, потому как можно Decimal передать, можно сумму представить как флоат, можно вообще целым типа 1 рубль = 1000

Ну и какой тут int? Ты уже бомбу закладываешь, пытаясь запихать тут встроенный тип данных. В твоём примере по уму будет def listen(self, address: tuple[URI, UUID], (не уверен, что угадал, что там должно быть правда) — вот это уже попытка в статическую типизацию, а твоя версия это такая же скриптодрисня, что и без сигнатуры вообще.

Virtuos86 ★★★★★
() автор топика
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от lovesan

https://godbolt.org/z/77nooo4ao

union a_union { int x; };
union b_union { int y; };

int main() {
    a_union a{ 1 };
// error: conversion from 'a_union' to non-scalar type 'b_union' requested
//    8 |     b_union b = a;
//      |                 ^
// Compiler returned: 1
    b_union b = a;
}

Да, особенно в union.

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

Siborgium ★★★★★
()
Ответ на: комментарий от monk

Я и пишу:

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

Siborgium ★★★★★
()
Ответ на: комментарий от Virtuos86

address, который нужно listen - это (хост/ip, порт). если ты не понимаешь о чем тебе толдычат, то и не пиши на пистоне, раз и не писал, и не надо лезть в него с твоими контрактами как лезут джавабои с доменной логикой и ddd. это как пытаться строить предложения на китайском по правилам русского языка. у скриптописателей единственная проблема - это то что ide не может тип подсказать, если нет аннотаций или стаба, и тогда приходится лезть в документацию либо код смотреть, который не всегда можно посмотреть, так как ide не может распознать магию. и тут бесплатный vscode и всякие платные и тормозные (java) pycharm в одинаковом положении

Ну и какой тут int

Расскажи это go-разрабам, которые писали апи для перекрестка или магнита, я не помню где оно. Может расскажут тебе про 0.1+0.2 и пр приколы

rtxtxtrx ★★
()

есть еще люди, которые не считают динамическую типизацию […] серьезным недостатком при работе с большим объемом кода, особенно при рефакторинге

Да неужели.

я предлагаю всем обсудить этот очень важный вопрос

Наобсуждался уже. Питон – это диагноз.

dimgel ★★★★★
()
Последнее исправление: dimgel (всего исправлений: 1)
Ответ на: комментарий от dimgel

Марк Твен – это Джейсон Стейтем до эпохи цитат из пабликов

rtxtxtrx ★★
()
Ответ на: комментарий от no-dashi-v2

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

Просто стройте нормально логику освобождения ресурсов, и всё будет ровно.

Что делать с инвариантами? Как обеспечить атомарность?

Например, есть функция swap, меняющая два элемента в стеке. Если я постулирую, что pop() не бросает исключения при пустом стеке, push() не бросает исключения никогда и operator= не бросает исключения никогда, то пишу просто:

void swap(std::stack<T> s)
{
   if(s.size() < 2) return;
   T e1 = s.pop();
   T e2 = s.pop();
   s.push(e1);
   s.push(e2);
}

Если этой гарантии нет, то надо писать что-то вроде

void swap(std::stack<T> s)
{
   if(s.size() < 2) return;
   std::stack<T> copy = s;
   try {
     T e1 = s.pop();
     T e2 = s.pop();
     s.push(e1);
     s.push(e2);
   } catch (...) {
     s = copy;
     throw;
   }
}

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

Программа, которая падает из-за некорректного IP, конечно, может быть, но крайне неудобна. Крейсер «Йорктаун» из-за подобного почти три часа болтался в океане с неработающим двигателем. Ресурсы при падении освобождались, но двигатель было не включить.

monk ★★★★★
()
Ответ на: комментарий от Siborgium

Вы же сами доказали, что «assigning a value of a particular type to a differently typed variable causes a compilation error» по Вашей ссылке является ложью.

Потому что в Вашем коде можно написать и

pub fn main(){    
    let b = B{};
    let c1 : &A = &b;
    let c2 : &B = &b;
    fun(c1);
    fun(c2)
}

с1 и с2 имеют разные типы, но им обоим можно присвоить одно и то же значение.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

И так на каждую операцию, имеющую транзакционность.

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

monk ★★★★★
()
Ответ на: комментарий от ugoday

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

Вот в C++ сделали, что pop() от пустого стека UB. А был бы не ООП, можно было бы сделать

pop(NonEmptyStack<T> s);

optional<NonEmptyStack<T>> NonEmpty(Stack<T> s) { if(s.size() > 0) return NonEmptyStack<T>(s); else return {}; } 
NonEmptyStack<T> UnsafeNonEmpty(Stack<T> s) { return NonEmptyStack<T>(s); }

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

monk ★★★★★
()
Ответ на: комментарий от lovesan

вот тут авторы были неграмотные, и поэтому все сделали неправильно

И с волчьим билетом из профессии.

Hertz ★★★★★
()
Ответ на: комментарий от rtxtxtrx

в отличии от чих-пыха

Дурачок чтоле? Пых также компилируется с незапамятных времён. Даже jit завезли недавно уже давно.

no-such-file ★★★★★
()
Ответ на: комментарий от rtxtxtrx

Нет, дружище, пожалуй, с тобой я общение прекращаю на эту тему 😊. Я всё-таки не святой.

Virtuos86 ★★★★★
() автор топика
Ответ на: комментарий от no-such-file

Это не канон (с) Среди пхпшников было мало негров, поэтому hiphop так и оказался никому не нужон. Да и там куцые языки. Тот же kphp - это php образца 2003 без классов.

rtxtxtrx ★★
()
Ответ на: комментарий от arkhnchul

В ноде ты просто передашь req.body в конструктор класса, а затем вызовешь save, который запустит валидацию (часть orm типа sequilize). Этот coolobject aka dto и не нужен в нормальных языках

rtxtxtrx ★★
()
Ответ на: комментарий от rtxtxtrx

И вообще тема ниочем. Лучше бы доказал как, например, mypy, где есть проверка типов, облегчит разработку на том же FastApi при нормальной разработке, где типы никем не проверяются ага. Тут я лукавлю потому как pydantic все проверяет, но проверяются только входные данные, но внутри можно че угодно делать и строки с числом складывать и со словарем и со списком. Если вы так делаете, то не расскажите зачем перемененную price складывать, например с datetime? Что нужно сделать???

rtxtxtrx ★★
()
Ответ на: комментарий от lovesan

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

Forth это единственный язык где такое применение можно оправдать, он как gdb для голого железа. Но в Factor это уже бесполезно, он не запустится без ОС.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от rtxtxtrx

Да и проблема такая больше в js, где ошибку не кинет, а просто в итоге будет строка либо NaN какой, но опять же кто и почему должен пытаться бульдога с носорогом скрестить. И почему никто не в шоке от растоманского let, гошного var, сишного auto? Да и вообще зачем они нужны компилятор и так типы мог бы расставлять: все числа, например, тупо int64.

rtxtxtrx ★★
()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)