Компания
1 056,70
рейтинг
4 ноября 2014 в 22:27

Разработка → Типы данных наносят ответный удар перевод

Это вторая часть моих размышлений на тему «Python, каким бы я хотел его видеть», и в ней мы более подробно рассмотрим систему типов. Для этого нам снова придётся углубиться в особенности реализации языка Python и его интерпретатора CPython.

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

Python всегда гордился своей реализацией системы типов. Я помню, как много лет назад читал документацию, в которой был целый раздел о преимуществах утиной типизации. Давайте начистоту: да, в практических целях утиная типизация — хорошее решение. Если вы ничем не ограничены и нет нужды бороться с типами данных по причине их отсутствия, вы можете создавать очень красивые API. Особенно легко на Python получается решать повседневные задачи.

Практически все API, которые я реализовывал на Python, не работали в других языках программирования. Даже такая простая вещь, как интерфейс для работы с командной строкой (библиотека click) просто не работает в других языках, и основная причина в том, что вам приходится беспрестанно бороться с типами данных.

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

Что такое «cистема типов»?

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

Я не буду слишком углубляться в систему типов по двум причинам. Во-первых, я сам не до конца понимаю эту область, а во-вторых, на самом деле совсем необязательно понимать всё, чтобы «почувствовать» взаимосвязи между типами данных. Для меня важно учитывать их поведение потому, что это влияет на архитектуру интерфейсов, и я буду рассказывать о типизации не как теоретик, а как практик (на примере построения красивого API).

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

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

Однако есть одна вещь, которая отсутствует в Python: составные типы данных. Все типы данных в Python примитивны, и это означает, что в определённый момент времени вы можете работать только с одним из них, в отличие от составных типов.

Самый простой составной тип данных, который есть в большинстве языков программирования — структуры. В Python их, как таковых, нет, однако во многих случаях библиотекам необходимо определять собственные структуры, например, модели ORM в Django и SQLAlchemy. Каждый столбец в базе данных представлен через дескриптор Python, который соответствует полю в структуре, и когда вы говорите, что primary key называется id, и это IntegerField(), вы определяете модель как составной тип данных.

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

Словосочетание «список целочисленных» всегда имеет больший смысл, чем просто список. Вы можете с этим поспорить, ведь всегда можно пройтись по списку и посмотреть тип каждого элемента, однако что делать с пустым списком? Когда в Python у вас есть пустой список, вы не можете определить тип его данных.

Такая же проблема возникает при использовании значения None. Допустим, у вас есть функция, которая принимает аргумент “Пользователь”. Если вы передаёте в неё параметр None, вы никогда не узнаете, что это должен был быть объект типа «Пользователь».

Какое же решение этой проблемы? Не иметь нулевых указателей и иметь массивы с явно указанными типами элементов. Всем известно, что в Haskell всё так и есть, однако есть другие, менее враждебные к разработчикам языки. Например, Rust — язык программирования, более близкий и понятный нам, поскольку он очень похож на C++. И в Rust есть очень мощная система типов.

Как же можно передать значение «пользователь не задан», если отсутствуют нулевые указатели? Например, в Rust для этого существуют опциональные типы. Так, выражение Option представляет собой помеченное перечисление, которое оборачивает значение (конкретного пользователя в данном случае), и оно означает, что может быть передан либо какой-либо пользователь Some(user), либо None. Поскольку теперь переменная может либо иметь значение, либо не иметь его, весь код, работающий с этой переменной, должен уметь корректно обрабатывать случаи передачи значения None, иначе он просто не скомпилируется.

Серое будущее

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

Первым признаком того, что мы ступаем на неизведанную территорию, стало появление языка C#. Это компилируемый язык со статической типизацией, и поначалу он был очень похож на Java. По мере развития языка C#, в его системе типов стали появляться новые возможности. Самым важным событием стало появление обобщённых типов, что позволило строго типизировать не обрабатываемые компилятором коллекции (списки и словари). Дальше — больше: создатели языка внедрили возможность отказываться от статической типизации переменных для целых блоков кода. Это очень удобно, особенно при работе с данными, предоставляемыми веб-сервисами (JSON, XML и т.д.), поскольку позволяет совершать потенциально небезопасные операции, ловить исключения от системы типов и сообщать пользователям о некорректных данных.

В наши дни система типов языка C# очень мощная и поддерживает обобщённые типы с ковариантными и контрвариантными спецификациями. Ещё она поддерживает работу с типами, допускающими нулевые указатели. Например, для того, чтобы определять значения по умолчанию для объектов, представленных как null, был добавлен оператор объединения со значением null ("??"). Хотя C# уже зашёл слишком далеко, чтобы избавиться от null, все узкие места находятся под контролем.

Другие компилируемые языки со статической типизацией также пробуют новые подходы. Так, в C++ всегда был языком со статической типизацией, однако его разработчики начали эксперименты с выведением типов на многих уровнях. Дни итераторов вида MyType<X, Y>::const_iterator ушли в прошлое, и теперь практически во всех случаях можно использовать автотипы, а компилятор подставит нужный тип данных за вас.

В языке программирования Rust выведение типов тоже реализовано очень хорошо, и это позволяет вам писать программы со статической типизацией, вообще не указывая типов переменных:
use std::collections::HashMap;

fn main() {
    let mut m = HashMap::new();
    m.insert("foo", vec!["some", "tags", "here"]);
    m.insert("bar", vec!["more", "here"]);

    for (key, values) in m.iter() {
        println!("{} = {}", key, values.connect("; "));
    }
}

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

Python и явная типизация

Некоторое время назад на одной из конференций кто-то убеждённо доказывал, что статическая типизация — это здорово, и языку Python это крайне необходимо. Я точно не помню, чем эта дискуссия закончилось, однако в результате появился проект mypy, который в сочетании с синтаксисом аннотаций был предложен как золотой стандарт типизации в Python 3.

На случай, если вы не видели эту рекомендацию, она предлагает следующее решение:
from typing import List

def print_all_usernames(users: List[User]) -> None:
    for user in users:
        print(user.username)

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

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

Семантика типов Python

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

В Python попросту нет «фундаментальных» типов, однако есть целая группа типов данных, реализованных на C. И это не только примитивы и фундаментальные типы, это может быть всё, что угодно, безо всякой логики. Например, класс collections.OrderedDict написан на Python, а класс collections.defaultdict из того же модуля написан на C.

Это доставляет множество проблем интерпретатору PyPy, которому важно эмулировать оригинальные типы настолько хорошо, насколько это вообще возможно. Это нужно для того, чтобы получить хороший API, в котором любые различия с CPython не будут заметны. Очень важно понимать, в чём основная разница между уровнем интерпретатора, написанном на языке C, и всем остальным языком.

Ещё один пример — модуль re в версиях Python до 2.7. В более поздних версиях он был полностью переписан, однако основная проблема по-прежнему актуальна: интерпретатор работает не так, как язык программирования.

В модуле re есть функция compile для компилирования регулярного выражения в паттерн. Эта функция берёт строку и возвращает объект паттерна. Выглядит это примерно так:
>>> re.compile('foobar')
<_sre.SRE_Pattern object at 0x1089926b8>

Мы видим, что объект паттерна задан в модуле _sre, который является внутренним модулем, и тем не менее он доступен нам:
>>> type(re.compile('foobar'))
<type '_sre.SRE_Pattern'>

К сожалению, это не так, потому что модуль _sre на самом деле не содержит этот объект:
>>> import _sre
>>> _sre.SRE_Pattern
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'SRE_Pattern'

Ну хорошо, это не первый и не единственный раз, когда тип обманывает нас о своём местоположении, да и в любом случае это внутренний тип. Двигаемся дальше. Мы знаем тип паттерна (_sre.SRE_Pattern), и это наследник класса object:
>>> isinstance(re.compile(''), object)
True

Также мы знаем, что все объекты имплементируют некоторые самые общие методы. Например, у экземпляров таких классов есть метод __repr__:
>>> re.compile('').__repr__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __repr__

Что же происходит? Ответ достаточно неожиданный. По причинам, мне неизвестным, в Python до версии 2.7 объект паттерна SRE имел свой собственный слот tp_getattr. В этом слоте была реализована своя логика по поиску аттрибутов, которая предоставляла доступ к своим собственным аттрибутам и методам. Если вы изучите этот объект с помощью метода dir(), вы обратите внимание, что многие вещи просто отсутствуют:
>>> dir(re.compile(''))
['__copy__', '__deepcopy__', 'findall', 'finditer', 'match',
 'scanner', 'search', 'split', 'sub', 'subn']

Это маленькое исследование поведения объекта паттерна приводит нас к довольно неожиданным результатам. Вот что на самом деле происходит.

Тип данных заявляет, что он наследуется от object. Это так в CPython, но в самом Python это не так. На уровне Python этот тип не связан с интерфейсом типа object. Каждый вызов, который проходит через интерпретатор будет работать, в отличие от вызовов, проходящих через язык Python. Так, например, type(x) будет работать, а x.__class__ — нет.

Что такое сабкласс

Приведённый выше пример показывает нам, что в Python может быть класс, который наследуется от другого класса, но при этом его поведение не будет соответствовать базовому классу. И это важная проблема, если мы говорим о статической типизации. Так, в Python 3 вы не можете реализовать интерфейс для типа dict до тех пор, пока не напишете его на C. Причина такого ограничения в том, что этот тип диктует видимым объектам поведение, которое попросту не может быть реализовано. Это невозможно.

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

Неопределённое поведение

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

Очень странное поведение внутренностей интерпретатора второй версии Python можно увидеть при сравнении типов экземпляров классов. В третьей версии интерфейсы были изменены, и такое поведение больше неактуально для неё, однако фундаментальная проблема до сих пор может быть обнаружена на многих уровнях.

Давайте возьмём в качестве примера сортировку множеств (set). Множества в Python — очень полезный тип данных, однако при сравнении они ведут себя очень странно. В Python 2 у нас есть функция cmp(), которая в качестве аргументов принимает два объекта и возвращает числовое значение, показывающее, какой из переданных аргументов больше.

Вот что произойдёт, если вы попытаетесь сравнить два экземпляра объекта set:
>>> cmp(set(), set())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot compare sets using cmp()

Почему так? Если честно, я без понятия. Возможно, причина заключается в том, как операторы сравнения работают с множествами, и это не работает в cmp(). И в то же время экземпляры объектов frozensets замечательно сравниваются:
>>> cmp(frozenset(), frozenset())
0

За исключением тех случаев, когда одно из этих множеств не пустое, — тогда мы снова получим исключение. Почему? Ответ прост: это оптимизация интерпретатора CPython, а не поведение языка Python. Пустой frozenset всегда имеет одно и то же значение (это неизменяемый тип и мы не можем добавлять в него элементы), поэтому это всегда один и тот же объект. Когда два объекта имеют один и тот же адрес в памяти, функция cmp() сразу же возвращает 0. Почему так происходит я не смог сходу разобраться, поскольку код функции сравнения в Python 2 слишком сложный и запутанный, однако у этой функции есть несколько путей, которые могут привести к такому результату.

Смысл не только в том, что это баг. Смысл в том, что в Python нет чёткого понимания принципов взаимодействия типов друг с другом. Вместо этого на все особенности поведения системы типов в Python всегда был один ответ: «так работает CPython».

Сложно переоценить объём работы, который был сделан в PyPy для реконструирования поведения CPython. Учитывая, что PyPy написан на Python, вырисовывается интересная проблема. Если бы язык программирования Python был описан так, как реализована текущая Python-часть языка, у PyPy было бы гораздо меньше проблем.

Поведение на уровне экземпляров

Теперь давайте представим себе, что у нас, гипотетически, есть такая версия Python, в которой все описанные проблемы исправлены. И даже в этом случае мы не сможем добавить в язык статические типы. Причина в том, что на уровне Python типы не играют значимую роль, гораздо более важно то, как объекты взаимодействуют друг с другом.

Например, объекты datetime, в общем случае, можно сравнивать с другими объектами. Но если вы хотите сравнить два объекта datetime друг с другом, то это можно сделать только в случае совместимости их таймзон. Так же результат многих операций может быть непредсказуемым до тех пор, пока вы внимательно не изучите объекты, участвующие в них. Результат конкатенации двух строк в Python 2 может быть как unicode, так и bytestring. Различные API кодирования или декодирования из системы кодеков могут возвращать разные объекты.

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

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

Багаж и семантика

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

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

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

Поддержание стройной и хорошо документированной архитектуры языка позволяет избежать многих проблем. Архитекторам будущих языков программирования определённо следует избегать всех ошибок, которые были совершены разработчиками языков PHP, Python и Ruby, когда поведение языка в конце концов объясняется поведением интерпретатора.

Я считаю, что Python уже вряд ли поменяется в лучшую сторону. На избавление языка от всего этого тяжёлого наследия требуется слишком много времени и сил.

Перевёл Dreadatour, текст читал %username%.
Автор: @Dreadatour Armin Ronacher

Комментарии (38)

  • +7
    Народ, кто ещё угадал Армина после первого же предложения? =)
    • +3
      Вообще вторая часть понравилась больше первой. О каких-то моментах совершенно не задумываешься, пока их тебе вот таким безпардонным образом не швырают в лицо :)
      • +2
        Совершенно согласен, и ведь многие вещи сам замечал, но просто проходил мимо, считая, что «так и надо», и исходники CPython изучал. Глаз замыливается, увы. А такие статьи заставляют задуматься и расти в профессиональном плане =)
  • –3
    я не очень внимательно читал, но по-моему, тут как-то в кучу все свалено

    Типизацию можно проклассифицировать по нескольким ортогональным категориям:

    1. строгая/нестрогая. То есть можно ли преобразовать 1.0 -> 1 -> «1» -> true
    2. Явная/неявная. То есть нужно ли везде писать int double и т.д. при объявлении переменных
    3. Времени компиляции/исполнения. Собственно, когда мы типы будем проверять.

    У python строгая неявная типизация. Поскольку python не компилируется, то типы все равно будут проверяться при запуске, и ничего особо явная типизация не даст (ха ха!)
    • +1
      Если мы говорим про CPython, а про него речь и идет в статье, то проверять типы можно в момент компиляции в байткод.
      • 0
        Для этого нужно понимать, кто какую функцию и с какими параметрами вызывает. В большом числе случаев в python это невозможно

        • 0
          Теоретически — возможно. Я бы всё-таки посоветовал для начала изучить вот эти ссылки (о них пишет автор):
          www.mypy-lang.org/
          legacy.python.org/dev/peps/pep-3107/
          На практике всё не так радужно.
          • 0
            mypy это эксперимент, а не попытка навязать какую-то догму. отнюдь не первый в своем роде, и вряд-ли последний.

            в pep-3107 речь идет только о синтаксисе. к статической типизации эти аннотации не имеют ни какого отношения.

            на практике статический анализ (pylint тот же) решает большую часть проблем.
            • +2
              MyPy — просто левый проект, PEP — просто рекомендации. Это понятно и так :)

              Pylint, конечно, хорошо, однако не панацея ни разу. Да, часть проблем (в основном, результат обычной невнимательности) он решает, однако взамен он привносит огромное количество геморроя. Вариант с явной или статической типизацией ведь всегда будет лучше, чем гадание на кофейной гуще, коей является pylint?
    • +1
      С явной/неявной типизацией не всё так просто и однозначно, хорошие примеры есть в статье.

      Проблема несколько глубже, проблема не в классификации языка по системам типизации (тут всё просто), а в особенностях (читать как «недостатках») существующей реализации Python. Статья, по большей части, об этом.
      • 0
        А что не однозначно? Я просто наблюдаю путаницу у автора явной и статической типизации, которые никак не связаны (auto C++, var C# — неявная статическая типизация), что уде меня самого сбивает с толку
        • –1
          Автор так и пишет, например, что auto C++ — неявная статическая типизация. Да и у меня в переводе, вроде, так и написано. Если я где-то ошибся, прошу поправить.
          • +2
            C++ в большинстве случаев все-таки явная статическая типизация. А автор вообще пишет что статитеческая типизация имела место быть, а теперь авто от нее избавляет. Что не верно — статическая типизация осталась.

            Вот тут переходы совсем не ясны:
            Python и явная типизация
            Некоторое время назад на одной из конференций кто-то убеждённо доказывал, что статическая типизация — это здорово, и языку Python это крайне необходимо.
            И затем пример на явную типизацию:
            def print_all_usernames(users: List[User]) -> None:
            
            • 0
              С местом про C++ и статическую типизацию с дальнейшим переходом на описание автотипов согласен, можно придраться, хотя стоит ли? :)

              А вот про Python и явную типизацию не соглашусь — там всё правильно написано. Обсуждалась статическая типизация, да, а в итоге предлагают реализовать явную (и MyPy и PEP-3107), там так и написано же, разве нет?
              • +2
                Ну так что ему не нравится-то? Аннотации типов? Так кому же они могут нравиться? В 21 веке явная типизация точно должна уйти в прошлое. А вот как особенности типов будут мешать статической типизации, я не понимаю, если честно. Интерпретатор по хорошему должен учесть все особенности типов, он-то ведь их знает!
                • –1
                  Мне всегда казалось, что для того, чтобы участвовать в дискуссии вокруг какой-либо статьи и вести конструктивный диалог, первым делом неплохо было бы прочитать эту самую статью. Иначе получается разговор ни о чём. Без обид и ничего личного :)

                  Отвечаю на вопрос: да. Собственно, примерно обо всём об этом и написана (и переведена) статья.
                  • +3
                    мы кажется разбирали довольно узкий вопрос, плюс я сразу предупреждал.
                    Тем не менее по главному (второму) вопросу я ответа как раз и не нашел.
                    • –1
                      То есть того времени, которое я потратил на перевод недостаточно, нужно ещё персонально вам ответить на все те вопросы, которые возникли после того, как вы не прочитали перевод, с учётом того факта, что ответы на все эти вопросы есть в статье? Ну давайте, чего уж там, мне всё равно в два часа ночи перед началом рабочей недели делать нечего. Всегда мечтал побыть в шкуре репетитора :)

                      Повторите, пожалуйста, ещё раз второй (главный) вопрос? Мне почему-то показалось, что я на всё ответил.
                      • +2
                        как особенности типов будут мешать статической типизации, я не понимаю, если честно. Интерпретатор по хорошему должен учесть все особенности типов, он-то ведь их знает!

                        доп.: разница с точки зрения вызова функции между статической и динамической типизацией в том, что на момент вызова при статической типизации известно, какие типы аргументов может принимать функция, а при динамической — нет. я вижу проблемы с переходом на статическую типизацию в питоне, но мне совершенно не ясно, какое это имеет отношение к внутренностям типов, то есть к тому, что описывает автор. Пусть часть типов реализовано одним способом, а другая — другим. И что?
                        • 0
                          Вообще, речь в статье именно про Python. А можно я просто отвечу цитатой из статьи?
                          Например, объекты datetime, в общем случае, можно сравнивать с другими объектами. Но если вы хотите сравнить два объекта datetime друг с другом, то это можно сделать только в случае совместимости их таймзон.
                          • +3
                            то же самое, что вы процитировали, можно сказать про любой язык программирования. В том числе со статической типизацией. Это runtime и не имеет к типизации никакого отношения.
                • +3
                  Мне, например, нравятся явные аннотации типов. Это отличная документация для публичного API. Я в основном пишу на JS и код типа
                  profile.auth = function (credentials) {
                    // some magic here
                  }
                  

                  заставит меня читать тело функции, иначе я так и не пойму что же туда передавать надо и что она вернет
                  В то же время
                  /**
                   * @param {{username: string, password: string}} credentials
                   * @return {IThenable.<boolean>}
                   */
                  profile.auth = function (credentials) {
                    // some magic here
                  }
                  

                  очень даже понятен.

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

                    Если вы попробуете писать что-то на С++, то поймете сколько мусора на самом деле добавляют обязательные аннотации.
                    • +1
                      На современном C++14 не так уж и много мусора. Собственно, в обычном коде явно указывать нужно только возвращаемый тип в тех случаях, когда он не может вывестись из контекста, например, когда вы возвращаете из функции что-то, инициализируемое прямо в return-statement из uniform initalization syntax:
                      auto foo(auto&& arg)
                      {
                          // ...
                          return SomeStruct { field1, field2, ... };
                      }
                      

                      или
                      [] (auto&& arg) -> SomeObj
                      {
                          // ...
                          return { arg1, arg2, ... };
                      }
                      


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

                      Естественно, явные аннотации типов будут вам нужны, если вы захотите делать всякие интересные шаблонные вещи, требующие этакого шаблонного паттерн-матчинга и «разворачивания» типов, но так это ж и хорошо.
                  • 0
                    Да, примерно так же пишутся (хорошими программистами) докстринги в питоне. Вот тут хорошо расписано: http://stackoverflow.com/questions/3898572/what-is-the-standard-python-docstring-format.

                    Другое дело, что не существует общепринятого стандарта, ну и докстринги — они ни на что не влияют в чистом Python, это просто документация для разработчика.
              • +3
                Про хаскель автором написана заведомая чушь, опровергаемая еще до первого знакомства с Maybe.
                • 0
                  Почитал про maybe, справедливо на первый взгляд. Пойду чинить сломанный после чтения кода на хаскеле код :)
                  • 0
                    ^сломанный мозг :)
  • +3
    Даже такая простая вещь, как интерфейс для работы с командной строкой (библиотека click) просто не работает в других языках, и основная причина в том, что вам приходится беспрестанно бороться с типами данных.

    Есть аналог для C# — cmd. Для подобных прикольных вещей (XML/JSON DOM, AR ORM, упрощение паттернов) и для взаимодействия с другими языками динамическая типизация рулит.
    • +1
      Хороший пример, спасибо!
  • +1
    Архитекторам будущих языков программирования определённо следует избегать всех ошибок, которые были совершены разработчиками языков PHP, Python и Ruby, когда поведение языка в конце концов объясняется поведением интерпретатора.

    Жаль что это перевод и не могу донести моё мнение до автора… В случае PHP существует только три проблемы, связанные с поведением интерпретатора — вложенные тернарные операторы, регистрация костыльной функции в глобальном пространстве при создании лямбды через create_function и невозможность вызвать функцию из объекта, если объект создавался вот так:
    $any = (object)['some' => function(){}];
    $any->some(); // error

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

    Хотя, может я просто привык и не нахожу бревно в глазу…
    • 0
      Я никогда не думал, что в Python так много спорных мест, однако же конструктивная критика Армина позволяет трезво взглянуть на вещи со стороны. К сожалению, я уже позабыл всё то, что когда-то знал про PHP, поэтому не могу оценить его или Ruby. Что касается JS, автор его «любит»: lucumr.pocoo.org/2013/12/9/stop-being-clever/ =)
      • 0
        JS в нынешнем виде — это прям почти PHP4 14-ти летней давности (можно даже подискутировать на эту тему, если кто хочет), т.к. пока я не вижу особой разницы (кроме наличия безымянных функций в первом, которые появились в php всего 5 лет назад).

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

        По поводу же руби — есть очень много мест, которые совершенно взрывают мозг своим поведением, но зачастую это оправдано тем, что сабжевый язык (по моему мнению) просто избыточно гибкий, слишком много синтаксических конструкций и позволяет\предоставляет слишком много возможностей, в отличие от того же пыха, где синтаксис очень простой и привычный любому плюсовику\шарписту\проч., а расширение происходит за счёт функций.
    • +5
      Извините, что в топике про python, но

      $x = 0;
      $a = array(&$x);
      $b = $a;
      $x = 2;
      unset($x);
      $b[1]++;
      $b[0]++;
      echo $a[0], ' ', $b[0];

      For the example above, a conforming implementation could output “2 1”, “2 3”, or “3 3” depending on how it implements value assignment for arrays. © github.com/php/php-langspec/blob/master/spec/04-basic-concepts.md

      Ну и всякие квантовые эффекты вроде 3v4l.org/ShFpG и stackoverflow.com/q/26258550/2815355
      • 0
        Согласен, не предсказуемо, можно добавить в список проблем. Я бы назвал результат 3 3, хотя в реальности происходит 2 3.

        В оправдание могу сказать, что подобного безумия никто в здравом уме не напишет =)
  • +3
    Соласен со всем высказанным. Программирую на питоне не слишком много, но с с упомянутой проблемой с «непонятным типом скомпилированного рег. выражения» уже сталкивался.

    Вообще несмотря на то, что питон лично мне оче нравится синтаксисом и идеями, всё-таки избегаю писать на нём что-то большее, чем «скрипт на несколько сотен строк». Всё из-за типов. Python декларируется как «язык для быстрого кодинга», но вот у меня до сих пор немалая часть времени разработки уходит на постоянные запуски скрипта, ловле эксепшенов на некорректных операциях с типами и обвязка этих мест кода в try-except'ы или if'ы, так что особой экономии время как-то и не чувствуется.

    С интересом поглядываю в сторону Cython (не путать со стандартным интерпретатором CPython) — там типы можно задавать явно, но не уверен, что он будет работать со всеми сторонними питоновскими «батарейками».

    Наилучшая система типов из всех испробованных мною языков — в C#. Можно указать тип явно (int, string), можно неявно (var) — логика программы не меняется, просто тип будет выведен автоматически на этапе компиляции, а можно динамически (dynamic), тогда все проверки будут на этапе выполнения.
    Да и вообще благодаря чудесному Intellisense, 99% ошибок с типами отлавливается ещё на этапе набивания кода, что, в отличие от питона, как раз таки способствует быстрой разработке.
    • +2
      Cython вообще для другого. Cython позволяет писать модули расширения (pyd-файлы, что по сути динамические библиотеки), на python-подобном языке со статической типизацией. Это нужно, когда требуется высокая производительность, так как такой код транслируется в Си. Писать всё подряд на Cython неудобно, и никто обычно этим не занимается. На Cython пишут только узкие места, критичные к производительности, чтобы не связываться с C Python API или C++ (Boost.Python).
  • +1
    Я раньше просто обожал Python. Но чем больше я его узнавал, тем меньше мне хотелось его использовать. А сейчас я думаю куда бы соскочить совсем.

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

Самое читаемое Разработка