LINUX.ORG.RU

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

 ,


1

4

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

★★★★★
Ответ на: комментарий от unC0Rr

Язык, который позволяет быстро получить некорректный результат - кому такое нужно?

Если программист внимательно всё проверит, то результат будет корректен. А так, Си++ до сих пор живее всех живых, несмотря на обилие языков без неопределённого поведения.

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

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

char, int, float, double, … тоже можно считать, что просто размер

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

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

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

anonymous
()
Ответ на: комментарий от wandrien

А чё, нормас)

main() 
  {
     auto a, b, c, sum;
     a = 1; b = 2; c = 3;
     sum = a+b+c;
     putnumb(sum); 
  }
alex1101
()
Ответ на: комментарий от monk

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

А если нет разницы, то зачем платить больше? Насколько я понял предложеніе @no-dashi-v2, предлагается сделать класс IPaddr, у него функцию-проверяльщик isValid? :: int -> bool, которая проверит полученный IP. Ну так я и в Питоне могу реализовать то же самое с точностью до синтаксиса. И в баше, да!

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

В Си++ статическая, но от очень большого объёма тестирования это не спасает.

Спасает от тестирования тех ошибок, которые отсекаются компиляцией.

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

Давай для начала 5. Покажешь их несостоятельность, я еще пять тебе найду.

Ну погнали что-ли по стандартной библиотеке питона. Питон, потому что с него начал ТС. Стандартную библиотеку берем, что бы ты не начал разводить демагогию на тему «я неправильный проект выбрал им никто не пользуется, вот правильный»:

  1. Хочешь понять, что вообще можно сериализовать json-ом. Оказываешься тут какого типу тут может быть «o»? какие ограничения?
  2. У тебя не работают емейлы, что-то не то передал в функцию отправки. Дебаггер приводит сюда. Вот тут msg чем может быть? Как ты это выяснил?
  3. Набрасываешь интерфейс на ткинтере. Как из когда понять, какие вооще тут options возможны без обращения к документации?
  4. Чем может быть locator? Как выяснил?
  5. Чем может быть internalstate в state?
Aswed ★★★★★
()

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

Оппонент, видимо, недалек разумом

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

class Delivery(BaseModel):
    timestamp: datetime
    dimensions: Tuple[int, int]

казалось бы, Tuple[int, int] - совершенно опциональная вещь (для документации, ага), но она используется в рантайме, позволяя библиотеке на основании знания о типе валидировать DTOшки. Ранее подобное достигалось перанально кодом наподобие:

class Delivery(BaseModel):
    timestamp = Field(validator=DatetimeValidator)
    dimensions = Field(validator=MyTupleTwoIntValidator)

что, конечно же, гавно

FishHook
()
Ответ на: комментарий от alex1101

тип данных имеет ещё и внутренний формат, и перечень допустимых операций

В ассемблере тоже. У разных типов данных разный внутренний формат (целый, вещественный, указатель, …) и разные операции (add, fadd, addss, jmp).

monk ★★★★★
()

Могу поделиться своим опытом. Я, как пользователь Питона для скриптов, максимум пару тысяч строк, обычно пару сотен, в питонском коммьюнити не общаюсь, PEP’ы не читаю. Когда в первый раз решил попробовать подсказки типов, я ещё не осознал, что они именно подсказки, и даже непонятно, кому, человеку или утилитам.

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

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

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

В питоне8 каком нить асилят, еще три раза сломав обратную совместимость.

AntonI ★★★★★
()
Ответ на: комментарий от Aswed
  1. там все логично: скалярные типы + список и словарь, хочешь свой используй default=.
  2. msg может быть только строкой и инстансом *Message из библиотеки email. тайпхинты как раз и нужны чтобы это указать
class Options(typing.TypedDict):
  foo: str
  bar: int

def f(**options: Options):
  ...

но там и по _show видно.

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

  2. Random тоже явно никто никогда не вызывает. И да можно через тайп-хинты указать


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

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

pyright в vscode специально придумали для всего этого. а он в свою очередь использует https://github.com/python/typeshed — коллекцию стабов для стандартной либы. вскодененавистники сами себя обрекают на страдания, сидят в консольном блокноте дрочат аля вим, а потом когда их затрахивает невозможность даже тип подсмотреть, начинают писать пистон говно. ну если говно и не пиши на нем, почему-то вы все считаете, что он легкий, благодаря анскилбоксу и другим инфоцыганам

rtxtxtrx ★★
()

Во время компиляции имеется ли возможность в https://docs.python.org/3/library/ast.html, использовать расширения с целью дополнительных проверок синтаксиса?

Весьма интересные URL при поиске в inet «Python AST расширения проверок синтаксиса»

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

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

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

А если нет разницы, то зачем платить больше?

Разница в том, что можно сделать тип «адрес, который кто-то уже проверил» (например, CorrectIP). А в динамическом языке либо проверяешь на входе каждой функции, либо пишешь текстом что надо и программист должен сам не забыть проверить.

monk ★★★★★
()

Вся критика статической типизации выглядит как-то так:

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

«Всего лишь».

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

Для этого лучше контракты, а не статические типы. Контракты гибче. Типом не укажешь, что функция принимает стороны треугольника, а контрактом легко.

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

Эти проверки несут накладные расходы. Транспилированный в js код с typescript всегда медленее нативного, не содержащего проверок типов

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

Ага, ежели я int x объявлю, то «не смогу» ему присвоить 3.14 или false, NULL и получить совсем не то чего ожидаю. Такие проблемы только у клинических дегенератов возникают.

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

тип «адрес, который кто-то уже проверил»

import random

class CorrectIP(object):
    def __init__(self):
        self._valid = False
        self._address = None

    def is_valid(self):
        self._valid = bool(random.getrandbits(1))
        return self._valid

    @property
    def address(self):
        return self._address

    @address.setter
    def address(self, address):
        if self.is_valid():
            self._address = address
        else:
            self._address = None

Вот и всё. Пока я работаю с этим классом я работаю с типом «адрес, который кто-то уже проверил».

>>>ip = CorrectIP()
>>> ip.address = 0xFFFFFF00
>>> ip._valid
True
>>> ip.address
4294967040
>>> ip.address = 0xFFFFFF00
>>> ip._valid
False
>>> ip.address
>>>
ugoday ★★★★★
()
Ответ на: комментарий от wandrien

Вброшу немножко.

У меня переменные, ... могут иметь семантику любой сложности.
Поэтому non problem добавить например свойство «Длина стороны треугольника» и range на размер каждой стороны.

Шутка

Почему у меня так?

Потому, что Python не знаю.

Меньше знаешь, крепче спишь.

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

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

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

Для начала, даже если бы просто интерпретатор вываливался при вызове функции с каким-нибудь ArgTypeError,

Вы готовы за это платить процессорным временем? У питона другая идеология, она гласит «We are all adults here». Есть статический чекер типов mypy, который вы подключаете к своим пре-коммит хукам, и он ругнется если увидит неправильный тип. Но если вам надо (а это иногда надо), вы можете не указывать никаких типов и не делать эту проверку, если знаете, что делаете. Например, это скрипт, который я наваял на коленке для каких-то сиюминутных задач, да насрать мне какие там типы

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

Вы готовы за это платить процессорным временем?

Использование питона - это и так плата процессорным временем)

У питона другая идеология, она гласит «We are all adults here».

А.

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

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

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

Неа.

Суть статики в том, что в любом месте ты достоверно знаешь какой объект (с каким контрактом) у тебя в любом месте твоего кода.

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

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

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

Вот и всё. Пока я работаю с этим классом я работаю с типом «адрес, который кто-то уже проверил».

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

FishHook
()
Ответ на: комментарий от wandrien

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

ну бывает, сходи к психотерапевту, мож отпустит

FishHook
()
Ответ на: комментарий от wandrien

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

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

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

Да вообще он не нужен. Твоя прога свалится, когда ты попросишь операционку «соединить» тебя по неверному адресу. В сишных прогах тапк же забивают на эти проверки, там регулярок встроенных нет, а в цикле побайтово проверять все эти ::1 и ::ffff:192.0.2.1 - это уже наркомания. так же и в шарпах/джавах, даже вот на этом сайте предпочитают свалиться вместо того чтобы проверять что-то, а вот любители «скриптодрисни» любят все-все проверять https://stackoverflow.com/questions/23483855/javascript-regex-to-validate-ipv4-and-ipv6-address-no-hostnames

хотя я может и превеличиваю:

https://fanserials.vip/117-rick-and-morty-5/7-season'%22/6-episode.html

Hacking attempt!

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

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

  1. Что такое «стороны треугольника», которые вместе с тем не заданы однозначно самим треугольником?
  2. Обоснуйте потерю производительности.
wandrien ★★
()
Ответ на: комментарий от no-dashi-v2

у тебя в коде не будет объекта типа ValidIPAdress у тебя там будет char[] и в любой непонятной ситуации твое говно просто свалится. тут поведение 1 в 1. разница лишь в скорости набрасывания говна и времени необходимом чтобы ты начал быдлокодить. на питоне три года и ты синьор, а на C++ лет 10 нужно, может еще поэтому тут такая боль, что соевый обрыган через 3 года получает сопоставимую зарплату и не знает как пользоваться битовыми флагами, но кто им мешал перейти на «скрипты»

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

предлагается сделать класс IPaddr, у него функцию-проверяльщик isValid? :: int -> bool

Даже не рядом.

Я предлагаю сделать

class ValidIPAddr(IPAddr):
    def __init__(self, *args, **kwargs) -> None:
        ... проверяй что хочешь ...
        super().__init__(*args, **kwargs)

    @staticmethod
    def create(ipaddr: IPAddr) -> ValidIPAddr:
        return ValidIPAddr(ipaddr.addr, ipaddr.port)

и потом во всех местах где ты использовал IPAddr требуй ValidIPAddr. То есть где у тебя было

connect(target: IPAddr) -> Connection:

меняешь декларацию на

connect(target: ValidIPAddr) -> Connection:

В результате, тебе линтер зарежет любую попытку использовать непроверенный IP где бы то ни было. Потому как ValidIPAddr гарантировано проверен и валиден. А где проверка не нужна - туда все равно можно передавать ValidIPAddr, ибо он унаследован от IPAddr и совместим по интерфейсу.

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

Твоя прога свалится, когда ты попросишь операционку «соединить» тебя по неверному адресу.

Если проверяльщик с ошибкой или вдруг реальность разъехалась с нашими представлениями о ней — тут уж ничего не поможет. Статическая типизация тут выглядит как «на всякий случай ходить всю жизнь в бронежилете → помереть от отравления шаурмой».

ugoday ★★★★★
()
Ответ на: комментарий от no-dashi-v2
class IPAddress(typing.NamedTuple):
  host: str
  port: int


def connect(address: IPAdress):
  ...

И передавай что хочешь. Это pythonic-way. А дальше нехай падает. Главное, что твой коллега поймет что передавать в функцию, написанную тобой

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

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

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

Проблема в том, что ты не можешь писать

# ip : CorrectIP
def netaddr(ip):
  return ip.address & 0xFFFFFF00

Надо обязательно добавить проверку на тип ip и на _valid. А в статически типизированном достаточно написать тип и ты точно знаешь, что плохие данные прийти не могут (проверка была где-то раньше).

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

Если мы говорим про питон (мы же говорим про питон?) то там пока не до контрактов КМК. Туда очевидно надо втаскивать какую то проверку типов при компиляции, но не только на аргументы функций а вообще по всему коду. Для этого компилятор должен развернуть все возможные трассы выполнения и по ним отследить что нигде ничего не ломается. Что не будет вот такого например

A = (1,2,3)
...
A[0] = 4
AntonI ★★★★★
()
Ответ на: комментарий от wandrien

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

f(x, y, z)

с контрактом

(->i ([x number?]
      [y number?]
      [z (x y) number? 
         (and/c (</c (+ x y)) 
                (>/c (- x y)) 
                (>/c (- y x)))])

Обоснуйте потерю производительности.

f(3, 4, 5) явно отработает быстрее, чем

struct Triangle t;
t.x = 3;
t.y = 4;
t.z = 5;
f(t);
monk ★★★★★
()
Ответ на: комментарий от monk

f(3, 4, 5) явно отработает быстрее

Не очевидно. И вообще, в питоне говорить про производительность смешно - если важна производительность то питон надо менять на что то еще.

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

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

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

Это не к надёжности, а к документируемости.

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

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

Я бы даже сказал, что вариант со структурой скорее будет работать быстрее - структура Трегоульник в конструкторе может не только проверить неравенство, но и посчитать площадь треугольника, найти угды треугольника, слазить во вселенскую Базу Треугольников и понять что это за треугольник - прямоугольный, правильный, Кармана, масонский и т.д. А всяким f(2,3,4) все это придется каждый раз проделывать;-)

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