Как стать автором
Обновить

Комментарии 28

Бенчмарки - это конечно хорошо, но как на счёт аннотации типов?

Самая неудобная и странная реализация в мире. При этом ни на что не влияет т.к. при cast-е игнорируется.

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

Самая неудобная и странная реализация в мире.

Это вкусовщина. Мне она наоборот больше нравится, так как не "засоряет" код. Можно в IDE настроить, что бы выводились в редакторе. Никаких неудобств это не вызывает.

При этом ни на что не влияет т.к. при cast-е игнорируется.

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

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

Питон с вами не согласится. И Dart тоже. А выше вспоминали TS. Зачем нужны типы которые не на что не влияют?

Поэтому и получается так, что патерн с DTO реализуется криво, DI как пятое колесо, а прикрутить Swagger к rails api невыполнимая задача.

И не надо рассказывать про Grape. Это просто пародия на FastApi.

А выше вспоминали TS

TS тоже никак на рантайм не влияет, это один из его явно заданных принципов.

TS тоже никак на рантайм не влияет, это один из его явно заданных принципов.

Это в JS на рантайм не влияет, а вот TS успешно добавляет методанные которые можно использовать в рантайме(и которые успешно используются например в NestJs). Да, при компиляции в JS теряется проверка при cast-е, но информации о типе остаётся. При этом TS типы в JS не становяться "голыми" т.к. остаётся Type Guards(instanceof)

Соглашусь что ограничений много. Питон в этом плане сильно мощнее, но на безрыбье...

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

По умолчанию, просто от факта наличия типа, - нет, это отдельное действие, причём, насколько я помню, действие, за которое отвечает не сам TS, а использующие его тулзы.

TS типы в JS не становяться "голыми" т.к. остаётся Type Guards(instanceof)

Он ничего сверх имеющегося в JS не может.

По умолчанию, просто от факта наличия типа, - нет, это отдельное действие, причём, насколько я помню, действие, за которое отвечает не сам TS, а использующие его тулзы.

Возможно. Я не очень глубоко погружён в TS. У NestJs это из коробки.

Он ничего сверх имеющегося в JS не может.

Да. Без методанных не может.

При чём тут питон и dart, и почему они не согласятся? Речь ведь была про ruby. И я же написал зачем нужны типы.

И не надо рассказывать про Grape. Это просто пародия на FastApi.

Пародия из прошлого? Он же на 8 лет старше )

При чём тут питон и dart, и почему они не согласятся? Речь ведь была про ruby. И я же написал зачем нужны типы.

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

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

Пародия из прошлого? Он же на 8 лет старше )

Да. Он изначально не очень удачным получился.

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

Так если динамическая типизация никуда не делась, значит и в них типы "ни на что не влияют". И Dart, если не ошибаюсь, изначально был по типу TS, просто транспилировался в JS. Но сейчас то он уже может компилироваться в машинный код и поддерживает статическую типизацию.

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

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

И Dart, если не ошибаюсь, изначально был по типу TS, просто транспилировался в JS. Но сейчас то он уже может компилироваться в машинный код и поддерживает статическую типизацию.

Угу. Эволюция. Дарт стал сильно строже и потерял в гибкости, но он всё ещё язык с динамической типизацией. Пример:

void main() {
  var x;
  x = 42;
  print(x.runtimeType); // int

  x = 'hello';
  print(x.runtimeType); // String

  x = [1, 2, 3];
  print(x.runtimeType); // List<int>
}

Или вот так:

multiply(a, b) {
    return a * b;
}

void main() {
  print(multiply(3, 4)); // 12
  print(multiply(2.5, 3)); // 7.5
  print(multiply("Hi", 3)); // HiHiHi (строка повторяется)
}

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

Нет. Покажу на примерах:

def greet(name: str) -> str:
    return f"Hello, {name}!"

print(greet.__annotations__)  # {'name': <class 'str'>, 'return': <class 'str'>}

И всё это в рантайме.

В Python соответствие типов тоже проверяется на этапе компиляции, но при этом типы инстрансов не теряются в рантайме. Вот пример:

class Duck:
    def quack(self):
        print("Quack!")

class Dog:
    def quack(self):
        print("Woof! But I can quack too.")

def make_it_quack(obj: Duck):
    obj.quack()

duck = Duck()
dog = Dog()

make_it_quack(duck)  # Quack!
make_it_quack(dog)   # Woof! But I can quack too.

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

Но если сделать так:

def make_it_quack(obj: Duck):
    print(type(obj))
    obj.quack()

То получим <class '__main__.Duck'> и <class '__main__.Dog'> в рантайме. Тип никуда не делся.

Если нам важно, то мы можем сделать так:

def make_it_quack(obj):
    if not isinstance(obj, Duck):
        raise TypeError("Object must be of type Duck")
    obj.quack()

И тогда на рантайме мы можем быть уверены что это Утка. В реальности так не делают потому что есть mypy(статический анализ).

Поэтому на питоне можно сделать что-то типо:

class Example:
    def example_method(self):
        pass

def test1(arg: Example):
    arg_type = type(arg)
    print(f"Type of argument: {arg_type.__name__}")
    
def test2(arg: int):
    print(f"argument: {arg}")

def run_def(test_method):
    annotations = test_method.__annotations__
    created_arg = annotations['arg']()
    test_method(created_arg)

run_def(test1)
run_def(test2)

Или что-то типо:

class ExampleClass:
    int_var: int
    str_var: str

annotations = ExampleClass.__annotations__
print(f"Type of int_var: {annotations['int_var'].__name__}")
print(f"Type of str_var: {annotations['str_var'].__name__}")

Покажите как сделать такое на rbs.

PS: я сознательно опускаю вопрос синтаксиса чтобы не спорить о вкусах, но я бы ставил синтаксис на первое место.

В Python соответствие типов тоже проверяется на этапе компиляции

Во что он компилируется?

А если так:

def greet(name: str) -> str:
    return name

print(type(greet(2))) # <class 'int'>

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

Покажите как сделать такое на rbs

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

Во что он компилируется?

байткод

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

Смысл то, не в проверке типов. С этим прекрасно справляется анализатор, а в доступности этих типов в рантайме.

А если так:

duck typing

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

DI, всевозможные ORM(см. SQLAlchemy), автоматическая валидация(см. pydantic), построение OpenAPI и т.д. и т.п.

Не стоит забывать что типы - это ещё и плюс к документации и читаемости.

При этом если не хочешь, то можно не использовать(но все используют потому что это удобно).

При этом RBS в Ruby появился в 3.0.0, а это 2020 год. Вовремя.

Смысл то, не в проверке типов. С этим прекрасно справляется анализатор, а в доступности этих типов в рантайме.

Смысл статической типизации именно в проверке типов в рантайме. А так получается лишь имитация статической типизации. А вручную сверять типы в рантайме и в руби можно с rbs, при желании.

DI, всевозможные ORM(см. SQLAlchemy), автоматическая валидация(см. pydantic), построение OpenAPI и т.д. и т.п.

Не стоит забывать что типы - это ещё и плюс к документации и читаемости.

Это всё и без типов реализуемо. Либо достаточно варианта как в rbs. А для читаемости IDE выводит типы в коде.

При этом RBS в Ruby появился в 3.0.0, а это 2020 год.

Ну за год до него появился sorbet. Кстати, в нём есть проверка типов в рантайме:

require 'sorbet-runtime'

class Example
  extend T::Sig

  sig {params(name: String).returns(String)}
  def self.greet(name)
    "Hi, #{name}"
  end
end

Example.greet(1)

#>> Parameter 'name': Expected type String, got type Integer with value 1 (TypeError)

Смысл статической типизации именно в проверке типов в рантайме.

Смысл статической типизации - там, где это именно типизация всего и вся, а не "тут прописали, а всё остальное как-нибудь само" - в том, чтобы проверка типов в рантайме оставалась только явной (условный object instanceof Something, прямо указанный в коде), а всё остальное оставалось только в компайлтайме.

чтобы проверка типов в рантайме оставалась только явной (условный object instanceof Something, прямо указанный в коде)

Так я вроде об этом и писал. Это когда проверка соответствия (строгого) типов выполняется при исполнении кода (как в моём примере на руби). В моём примере на питоне эта проверка не работает.

Смысл статической типизации именно в проверке типов в рантайме.

Так стоп. А в какой момент дискуссии типизация стала именно статической? Никто не требует от динамических языков такого. Хочется проверки на рантайме? Дарт такое умеет, но он менее гибкий чем питон.

А вручную сверять типы в рантайме и в руби можно с rbs, при желании.

Если нужна автоматическая проверка типов подключаешь pydantic и готово. Никакого дополнительного DSL или ручной проверки.

А вручную сверять типы в рантайме и в руби можно с rbs, при желании.

А можно пример?

Это всё и без типов реализуемо.

Добавте Swagger в Rails API :)

А в какой момент дискуссии типизация стала именно статической?

Когда я спросил:

Если он не поддерживает статическую типизацию, значит и у него польза от аннотации типов ровно такая же, разве нет?

И вы начали накидывать примеры с выводом аннотаций в рантайме, в которых не очевидна польза этого. То есть, если язык сохраняет динамическую типизацию, то типы имеют чисто информационную и утилитарную (линтинг и т.п.) функцию на этапе написания кода. Но при исполнении они ни на что не влияют.

А можно пример?

Как-то так, наверное:

# types.rbs
class Example
  def valid: -> String
  def invalid: -> String
end
    
# example.rb
require 'rbs'

class Example
  def valid = 'valid'
  def invalid = true
end

# Загружаем инфу из .rbs файлов
loader = RBS::EnvironmentLoader.new
loader.add(path: Pathname('.'))
def_builder = RBS::DefinitionBuilder.new(env: RBS::Environment.from_loader(loader).resolve_type_names)

# Берём от туда описания типов
example = Example.new
type_name = RBS::TypeName.new(name: example.class.name.to_sym, namespace: RBS::Namespace.root)
def_instance = def_builder.build_instance(type_name)
valid_expected_type = def_instance.methods[:valid].method_types.first.type.return_type
invalid_expected_type = def_instance.methods[:invalid].method_types.first.type.return_type

# и сверяем
puts example.valid.is_a?(Object.const_get(valid_expected_type.name.to_s))
#> true

# или так
puts TypeName(example.invalid.class.to_s).eql?(invalid_expected_type.name)
#> false

Добавте Swagger в Rails API :)

Не совсем понимаю что это значит. Что-то такое?

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

Как-то так, наверное:

жесть

Что-то такое?

да, но я скорее имел введу что-то типо Grape.

Вы хотите внутренние типы напрямую наружу выводить?

Да. Как делают это все современные фреймворки. Даже Grape это умеет, правда криво, но умеет.

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

Он поддерживает достаточно типов чтобы в 99% случаях об этом не беспокоится.

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

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

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

Да. Как делают это все современные фреймворки. Даже Grape это умеет, правда криво, но умеет.

Как он может это делать, если он никак не зависит от аннотации типов. Там параметры и представления прописываются отдельно независимо от внутренних типов.

Он поддерживает достаточно типов чтобы в 99% случаях об этом не беспокоится.

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

Ну вот в рельсах она не решается никак.

Так там такое и не нужно, это прежде всего фулстэковый фреймворк. Там в принципе необходимости в спеке нет. Остальной функционал можно гемами добавить.

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

Какие ещё ограничения? Ендпоинты, прежде всего, принимают котроллеры. А модель, как правило, соответсвует названию контроллера. Но зачем это знать внешним клиентам?

Как он может это делать, если он никак не зависит от аннотации типов. Там параметры и представления прописываются отдельно независимо от внутренних типов.

У него свой DSL на это. Но сложности начинаются когда объект усложняется. Тогда и появляется grape-swagger-entity, ну и это очередной костыль.

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

Это у вас примитивный бэк из 00ых, а все современные бэкэнды поддерживают OpenAPI Specification v3.1.0(ну или хотя бы v2).

Каких вам типов не хватает в json-е?

Так там такое и не нужно, это прежде всего фулстэковый фреймворк. Там в принципе необходимости в спеке нет. Остальной функционал можно гемами добавить.

Ага. Мобилки для рельсов не существует. И микросервисов тоже. И Vue, и React и Angular. Проще сказать для чего этот "лучший" в мире язык/фреймворк существует.

Вот у django с этим проблем нет, хотя это тоже фулстек фреймворк. flask тоже поддерживает swagger. Spring(java) не совсем из коробки, но поддерживает.

Какие ещё ограничения? Ендпоинты, прежде всего, принимают котроллеры. А модель, как правило, соответсвует названию контроллера. Но зачем это знать внешним клиентам?

Клиентам нужно знать спеку, как вы им её дадите им без разницы. Все современные фраймворки получают эндпоинты и модели из кода и на основе этого строят спеку. Автоматически строят. САМИ. Ничего делать не надо. Зависит от фраймворка, но скорее всего достаточно просто включить это в конфигах.

Это дефакто стандарт индустрии: WB, YandexCloud, VK и т.д.

Для любого Ruby фреймворка это недостижимая роскошь. Ближе всех к этому подошёл Grape, но даже там это боль.

Если нужна автоматическая проверка типов подключаешь pydantic и готово. Никакого дополнительного DSL или ручной проверки.

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

И описание типов в коде ничем не отличается от дополнительного DSL.

Так pydantic это по сути и есть ручная проверка, это ведь сторонняя библиотека, а не встроенный функционал. К тому же, как я понял, это всё же валидатор, а не тайпчекер.

pydantic совсем не ручной. Он из коробки знает какие типы у полей(потому что код содержит эту инфу) и валидирует. А для того чтобы завести валидацию в рельсы нужно написать validates на каждое поле. А если у тебя разные модели на разные слои, то будь добр писать валидации на каждую из этих моделей.

Входящие и исходящие данные не всегда соответствуют внутренним

Для этого и существует DTO, но для руби это недоступная магия.

И описание типов в коде ничем не отличается от дополнительного DSL.

Ага, только на сваггер такой DSL, на sorbet такой, а на валидацию такой. И все эти DSL появились из-за того что в рубях отсутствует аннотация типов. А так да, ничем не отличает.

pydantic совсем не ручной. Он из коробки знает какие типы у полей(потому что код содержит эту инфу) и валидирует.

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

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

Зачем на каждое? Вы что, позволяете клиенту в любое поле писать любые данные? Валидация и должна быть отдельно от типов.

Для этого и существует DTO, но для руби это недоступная магия.

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

Так pydantic это по сути и есть ручная проверка, это ведь сторонняя библиотека, а не встроенный функционал.

Вот только я в ручную типы не ввожу. Даже никакой метод вызывать не надо.

Встроенный куда? В FastAPI он из коробки.

К тому же, как я понял, это всё же валидатор, а не тайпчекер.

А в чём разница? Он проверяет типы.

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

Ага. Поэтому все модели выглядят как списки валидации полей. Особенно мне "нравилось" validates_associated.. А обработка ошибок валидации эта отдельная история. Зато валидация в рельсах с самого начала.

Входящие и исходящие данные не всегда соответствуют внутренним

Повторяю. DTO.

И описание типов в коде ничем не отличается от дополнительного DSL.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации