LINUX.ORG.RU

Отступы или скобочки? - Да!

 , , , ,


2

2

Конвертер синтаксиса, который заканчивает главный холивар

«Может, в следующий раз вы посмотрите дважды - и не станете судить о книге по обложке» - Зекора

Спор «скобочки или отступы» старше, чем некоторые из его участников. У этого спора нет судьи и нет призового фонда, но есть ветераны и есть потерянные души, которые однажды написали end в Питоне и с тех пор не могут остановиться. Выиграть этот спор невозможно: стороны спорят о вкусах, а не о свойствах. Поэтому предлагаю - давайте закончим этот спор не победой, а разоружением.

Я написал маленькую утилиту, которая переводит исходный код Python между тремя синтаксисами: классические отступы, C-подобные фигурные скобки, S-выражения в духе Лиспа. Один и тот же quicksort, три вида:

def quicksort(items):
    if len(items) <= 1:
        return items
    pivot, *rest = items
    lo = [x for x in rest if x < pivot]
    hi = [x for x in rest if x >= pivot]
    return quicksort(lo) + [pivot] + quicksort(hi)
def quicksort(items) {
    if len(items) <= 1 {
        return items;
    }
    pivot, *rest = items;
    lo = [x for x in rest if x < pivot];
    hi = [x for x in rest if x >= pivot];
    return quicksort(lo) + [pivot] + quicksort(hi);
}
(defn quicksort (items)
  (if (<= (len items) 1) (return items))
  (set! '(pivot (* rest)) items)
  (set! lo (list-comp x (for [x in rest]) (when (< x pivot))))
  (set! hi (list-comp x (for [x in rest]) (when (>= x pivot))))
  (return (+ (+ (quicksort lo) [pivot]) (quicksort hi))))

Код тот же, семантика та же, а шкуры - три. И это не «примерно то же самое»: замкнутый цикл py -> шкура -> py даёт AST, структурно идентичный оригиналу - сравнение на уровне ast.dump. Как это проверяется на 38 модулях стандартной библиотеки и на самом конвертере, расскажу ниже.

Зачем это нужно (спойлер: не нужно, и в этом смысл)

«Вечный хаос - это же шоколадный дождь, ребят! Шо-ко-лад-ный дождь!» - Пинки Пай

На вопрос «зачем» отвечу сразу: чтобы перестать спорить. Сторонник скобочек может читать любой проект в своих скобочках, сторонник отступов - в своих отступах, а любители Лиспа наконец получат повод объяснить всем про S-выражения на материале, который все и так знают.

Есть и утилитарные применения, которые возможны скорее теоретически, чем практически, но всё же я их перечислю:

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

Как этим пользоваться

«Она была под буквой «Э»!» - Пинки Пай, нашла справочник

as lisp foo.py            # Python -> S-выражения
as brace foo.py -o foo.cpy
as py foo.lpy             # обратно
cat foo.py | as brace - --from py

Расширение само задаёт направление: .py, .cpy, .lpy; --from переопределяет. Ставится через pip install like-a.

Да, утилита называется as - по ключевому слову Python, которое всем известно из import json as j. Командная строка читается как английская фраза: as lisp - «как лисп». На PyPI имя as запрещено (ключевые слова Python регистрировать не разрешают), поэтому пакет живёт под именем like-a - like a boss lisp.

(Пользователям GNU binutils: я знаю про ассемблер. Двухбуквенное пространство имён было слишком лакомым. Если ~/.local/bin в вашем PATH стоит раньше /usr/bin - alias для сборки C. Или предложите ассемблеру переехать: трёхбуквенное сокращение ему подходит больше, к слову.)

Два слоя: где нужны токены, а где - дерево

«Голова у него - пони, а тело - из всякого прочего» - Чирили, про драконеквуса

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

Отступы <-> скобки - перекладка строк, а не структуры. Блочная граница уже известна лексеру: tokenize стандартной библиотеки выдаёт пары INDENT/DEDENT, остаётся дописать { на INDENT и } на DEDENT. Конвертация идёт на уровне токенов, без построения дерева. Поэтому в этой паре выживают комментарии, пустые строки и форматирование: мы переписываем границы блоков, не трогая остальной текст.

Отступы <-> S-выражения - перестройка дерева. a + b * c превращается в (+ a (* b c)), и чтобы это сделать, нужно знать приоритеты операций. Можно написать свой Pratt-парсер… а можно не писать: модуль ast уже давно распарсил всё правильно, пройдя через все граничные случаи, накопленные экосистемой Python за долгие годы. Конвертер просто обходит готовое дерево и печатает его в новой нотации. Обратный путь делает маленький ридер S-выражений, который строит AST напрямую.

Отсюда бесплатный вывод, который лично меня радует: S-выражения - это не «другой синтаксис», это то же дерево, но в текстовой форме. Что, как известно, никого не удивляет в Лиспе.

Боевые истории: что ломалось на stdlib

«Хаос - это чудесно, чудесно!» - Дискорд

Quicksort из вступления - мой код, написанный для иллюстрации (для красоты). С чужим кодом всё интереснее: его писали 35 лет, не оглядываясь на мой конвертер. Все примеры ниже - реальные баги, найденные, пока я добивался работоспособности замкнутого цикла на 38 модулях стандартной библиотеки. CPython - лучший фаззер синтаксиса. Комьюнити находило такие темные углы, куда нормальный человек не заглядывает.

Set-литерал в заголовке. В enum.py (строка 1057) встречается:

if target not in {
        None,
        None.__new__,
        object.__new__,
        Enum.__new__,
    }:
    __new__ = target

Многострочный set-литерал в {} коллидирует с блочными скобками - заголовок if при переводе в brace-кожу получает два конкурирующих {. Решение: заголовки, содержащие фигурные скобки, сворачиваются в одну строку.

Backslash-продолжения внутри скобок. inspect.py, строка 3248:

raise TypeError('missing a required argument: {arg!r}'. \
                format(arg=param_name)) from None

У строки-продолжения есть собственный вклад в баланс скобок (тут - открытая ( от TypeError(). Наивная буферизация продолжений теряет этот вклад, и баланс уезжает в минус на четыре сотни строк. Урок: у каждой строки есть delta, и она применяется всегда - даже когда строка уходит в буфер.

Пользовательская функция quote. В shlex.py (строка 323) живёт функция def quote(s). В lpy-шкуре кортежи печатаются как '(элементы...) - спец-форма quote. Вызов (quote s) оказался на глаз неотличим от кортежа из одного элемента… нет, отличим: настоящий кортеж-литерал всегда содержит список. Ридер теперь различает кортеж (quote (...)) и вызов пользовательской функции (quote x). Которая, между прочим, живёт себе в shlex и ни о чём таком даже не думает.

Строка "quote" как ключ словаря. В csv.py (строка 275) словарь {'delim': ..., 'quote': ...} при печати давал форму {("delim" x) ("quote" y)}, и ридер принимал строковый ключ "quote" за голову спец-формы. Лечение: проверять тип головы - спец-форма начинается с символа quote, а не со строки "quote". Именно для этого и существует подкласс str по имени Sym.

Замкнутый цикл как контракт

«Крест на сердце, взлететь готова - воткну кексик себе в глаз!» - Пинки Пай, клятва

«Без потерь» - слово, которое любят писать в README. Вот определение, которое проверяет машина:

ast.dump(ast.parse(src)) == ast.dump(ast.parse(
    convert(convert(src, 'py', skin), skin, 'py')))

для skin из {brace, lisp} и для всех файлов корпуса.

Метрики проекта без округления: 92 теста = 13 юнит-тестов + 76 корпусных прогонов (38 файлов × 2 направления) + 2 самоприменения + 1 тест, что сконвертированный конвертер исполняется.

И вишенка на торте - самоприменение. Каждый из семи модулей пакета aslang прогоняется через оба направления: результат обязан быть AST-эквивалентным и - что приятнее - исполняемым. Где-то в тестовом прогоне есть экземпляр ридера S-выражений, который сам был напечатан принтером из AST, распаршен обратно и выполняет работу. Утилита пережила собственную конвертацию. Это тот род самореференции, ради которого стоило писать инструмент.

Чем это отличается от того, что уже было сделано

«Надо, чтобы это было процентов на двадцать круче» - Рэйнбоу Дэш

bythonHyNimas
направление-> py-> py ASTсвой языклюбое -> любое
скобочная шкура+-++
S-выражения-+ (свои)-+
гарантия круга---+ (AST-equal)
семантика Python+почтинет+ точно

bython - препроцессор: пишет скобки, исполняет отступы, одно направление. Hy - настоящий Лисп со своей семантикой и макросами, замечательный и другой: он не переводит этот ваш Python, он заменяет его. Nim просто всё равно: это отдельный язык. as занимает оставшуюся нишу: чистый синтаксический слой над неизменной семантикой Python, во все стороны, с доказуемой обратимостью.

Ограничения

«Первое правило нашей игры: никаких крыльев и никакой магии» - Дискорд

  • Комментарии в lisp-шкуре - best-effort: ast их не хранит, конвертер возвращает по номерам строк. В brace-шкуре живут как родные.
  • typing.TypeAlias-стейтменты и экзотика последней версии - пока мимо lisp-ридера.
  • Threading-макросов ->/->> сознательно нет: они делают round-trip неоднозначным, а неоднозначность ломает главный тезис.

Если зайдёт

«Верну их вам в своё время» - Дискорд, про отобранные крылья и магию

Есть направление, которое я сознательно оставил за скобками: обратный ход - C без скобок, с отступами. Почему не сейчас: в Python скобки избыточны - лексер и так знает границы блоков, потому конвертация и живёт на уровне токенов. В C скобки несущие: enum {A, B}, инициализаторы массивов и структур, GNU-выражения ({ ... }) - чтобы убрать только блочные и не тронуть литеральные, нужен полноценный парсер (libclang или tree-sitter), а не перекладка токенов. И замкнутый цикл выйдет только AST-уровневый, не побайтовый.

Частичный ответ уже существует - Nim: пишешь с отступами, на выходе C. Но Nim - отдельный язык со своей стандартной библиотекой, на нём пишут с нуля. Шкура для C - про другое: читать существующий код в удобном виде, не переписывая. И спрос двусторонний: многим нравится всё в Nim, кроме питон-стиля - а скобочной формы у Nim нет, в его грамматике скобки заняты сетами и прагмами.

Nim-программистам привет: скобки под данные, а не под блоки - это красота дизайна, а не упущение. И шкура ничего бы у Nim не отбирала: канон остаётся за отступами, просто народ с C-привычками принёс бы код, который всегда можно было бы привести к каноническому виду. Так что работы в разы больше, но интерес к Nim доказывает, что это может быть востребовано - если зайдёт, займусь.

Попробуйте на чем-то своём

«Ура.» [тихо] - Флаттершай

as lisp ваш_файл.py

Прогоните свой самый нелюбимый модуль через обе шкуры. Самые красивые и самые страшные результаты несите в комментарии, лучшее буду размещать в README. А если после этого вам всё ещё хочется поспорить о скобочках и отступах - ну, вы поняли: спорить больше не о чем.


Ссылки: GitHub | GitFlic | PyPI | таблица аналогов и form reference - в README.



Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

вдвое больше работы делать

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

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

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

Если у тебя в несколько строк кода это не помещается, то как ты собираешься это человеческим языком в пару строк описывать?

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

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

Ах, если бы, ах, если бы — не жизнь была, а песня бы! %)

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

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

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

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

подпись к коммиту

То есть чтобы просто понять, что функция делает, нужно сначала или прочесать половину кодовой базы, или найти тот самый коммит, которым она была добавлена/исправлена? Отличный план, если я правильно понемаю (тм).

Может, лучше всё-таки черкнуть пару строк описания прямо над функцией, а? %)

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

О, кстати, надо Дипсику предложить «Напиши тетрис на брейнфаке».

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

Предпочитаю читать комментарии, а код напишет и ИИ, если к функции строго написать что она должна делать и как.

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

достаточно прочитать функцию, если непонятно что она делает

Главное, чтобы читалка не отвалилась каждую встречную функцию внимательно читать, от скобки до скобки.

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

Такой день сурка нам не нужен.

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

Лучше чем читать каждый встречный комент от буквы до буквы а потом ещё и криво написанную функцию чтобы понять не врет ли комент. Хмм, похоже комент можно и не читать.

ya-betmen ★★★★★
()

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

yvv1 ★★
()

Версия 0.1.1., добавлена rust шкура, для любителей

as rust quicksort.py

fn quicksort(items) { 

   if len(items) <= 1 { 
       return items; 
   } 
   let pivot, *rest = items; 
   let lo = [x for x in rest if x < pivot]; 
   let hi = [x for x in rest if x >= pivot]; 
   return quicksort(lo) + [pivot] + quicksort(hi); 
}
wirewalk
() автор топика
Последнее исправление: wirewalk (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

комент можно и не читать.

Если code first (code as spec) (плохая идея), то можно и не читать.
Если comment as spec , то это во-первых лучше, чем без спецификации вообще, а во-вторых, в каком бы виде ни была спецификация, читать ее, как минимум, полезно.

wirewalk
() автор топика
Ответ на: комментарий от ya-betmen

Разбор проблем по спеке это конечно круто.

По-настоящему круто это разбор проблем без спеки. Функция должна была разморозить холодильник, но разблокировала «испечь пирожок» в микроволновке? А может так и надо было? Может быть в этом был замысел? Или нет?

wirewalk
() автор топика

Чувак, купи лучше подписку. А то эту нейросетевую лапшу с первых букв видно.

BceM_IIpuBeT ★★☆☆☆
()
Последнее исправление: BceM_IIpuBeT (всего исправлений: 2)

Скобки... Отступы...

qsort [] = []
qsort (x:xs) = qsort (filter (<= x) xs) ++ [x] ++ qsort (filter (> x) xs)

А по теме — за упражнение зачОт!

gns ★★★★★
()
Последнее исправление: gns (всего исправлений: 1)

Оспаде, даже текст не смог сам написать. Зачем тут этот нейрослоп очередной?

no-such-file ★★★★★
()

Python

не нужен. А то развели тут нерелевантщину какую-то. Скобочки-х%@бочки.

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

Кстати, насчёт бреда инетересное наблюдение: зачастую все далеко ищущие изобретения казались современникам прошлых времён бредом :)). Летать? С ума сошёл! Видеть на расстоянии? Да не может быть! Что? Какой ещё «компьюктер»? Иди на счётах учись считать!

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

вместо прочтения пары строк описания, что функция делает

А имени функции и аргументов недостаточно?

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

Что код делает, можно понять по самому коду; а вот зачем он это делает — по одному коду обычно понять трудновато.

Так куску кода надо давать имя, зачем он это делает.

прочитать-имя-пользователя список =
  первый список

...


достаточны-ли-права? описание-пользователя право =
  имя = прочитать-имя-пользователя описание-пользователя
  пароль = прочитать-пароль-пользователя описание-пользователя
...
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от VIT

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

Учитывая, что еще недавно водители могли в обед после столовой кружечку выпить, да и в самолетах с поездами курить была норма, то вряд ли это миф. По человечески в общем было…

ЗЫ тут надо отделять алкогольное злоупотребление и банальную культуру пития, ибо ни одного пьяного тогда за рулем не было… Да и во многих развитых странах по сей день так…

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

ибо ни одного пьяного тогда за рулем не было

да ладно?

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

хотя кое-где это ещё надавно было - в ЮАРе нас выпивший местный возил в поисках ещё догнаться и корнишонов купить. очень обижался когда его «Стёпой» звали: ’I’m not stupid!"

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

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

А сегодня они видят берега..? Оо

Просто есть те кто в жару кружечку холодненького накинул и двинулся дальше, а есть те кто накидывается пока лезет (с этими естественно разговор должен быть отдельный)…

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

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

А имени функции и аргументов недостаточно?

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

Вот, к примеру, есть в стандартной библиотеке функция partition-by. У неё в документации написано

Applies f to each value in coll, splitting it each time f returns a
new value.  Returns a lazy seq of partitions.  Returns a stateful
transducer when no collection is provided.

И как нам всю эту информацию в название функции поместить?

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

прочитать-пароль-пользователя

Ну что эта функция читает откуда-то пароль пользователя, догадаться можно. Но вот откуда она его читает, что будет, если прочитать не удастся и всё такое прочее — из имени функции понять нельзя.

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

«Гарантия возврата» — то про кредит обычно…

Если для двустороннего преобразования, то «гарантия обратимости» или «обратимость».

Если же про гарантию преобразования по кругу (python -> скобки -> фигурные -> python), то наиболее логично «гарантия круга».

P.S. Англоязычные тексты для не-компьютерщика ещё больший сюрреализм: «скороход мыши указывает на икону, чтобы казнить монтажника».

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

И как нам всю эту информацию в название функции поместить?

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

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

Но вот откуда она его читает, что будет, если прочитать не удастся и всё такое прочее

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

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

Так это документация, а не комментарий

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

В принципе, в недоязычках всё равно код это не данные, так что иии так сойдёт %)

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

Если есть что-то неочевидное, что должно быть понятно в момент использования, то будет

Сиди потом гадай — если в имени функции не упоминаются исключения, это значит, что функция не бросает исключений или что разработчик просто поленился лишние буковки писать? Да идите вы к козе в трещину со своими ребусами %)

Просто. Документируйте. Свои. Функции.

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

подпись к коммиту

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

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

Я помню как разбирал фиксы одного товарища, там было комент с номером бага, иф с поверкой условий, и собственно апдейт данных. Таких фиксов по коду было штук дохрена. Эти коменты были вообще бесполезны т.к фиксить проблемы надо было вообще в других местах.

ya-betmen ★★★★★
()
Ответ на: комментарий от Sm0ke85

В Германии всё ещё 0,5 пива — допустимо за рулем

BruteForce ★★★★
()
Ответ на: комментарий от ya-betmen

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

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

Учитывая, что еще недавно водители могли в обед после столовой кружечку выпить, да и в самолетах с поездами курить была норма, то вряд ли это миф. По человечески в общем было…

Это понятно, и я с этим моментом не спорю. Отношение к алкоголю в 1972 году сильно отличалось от такого в 1985. Однако если вы помните фильм, который построен на пафосном тезисе «нужно ходить в школу», то по-идее туда не только после кружки нельзя заявиться, туда нужно ходить исключительно в парадной форме, с чистыми ботинками, причесанный, напомаженный, и в костюме жениха. Какое пиво? Это как в церковь в шортах на босу ногу.

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

тут надо отделять алкогольное злоупотребление и банальную культуру пития, ибо ни одного пьяного тогда за рулем не было… Да и во многих развитых странах по сей день так…

Ни одного пьяного за рулём … во многих развитых странах по сей день… Хмм. Алкоголь - главный убийца в США на дороге. До 40% всех дорожных происшествий в США включают алкоголь. Да в США сильное алкогольное лобби, слишком выгодный бизнес, поэтому 0.08 промилле разрешен, но здесь очень много «но». Начиная с того, что полиция может остановить водителя при любом подозрении, а суд может назначить DUI при любой степени, даже разрешённой. В правилах это явно прописано.

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

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

А так о механических полётах ещё древние мечтали, миф про Дедала с Икаром, крылья Дедал сам сконструировал. Полёты на Луну Сирано де Бержерак описывал, без технических подробностей, с подробностями Жюль Верн попытался, вышло не очень, но потом другие подтянулись.

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

От человека зависит, короче.

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

Нет, я правильно написал. При 0.8 промилле человек скорее мёртв чем жив. Предельные дозы 0.2.

Я не знаю, где «у вас». США вообще одно из немногих государств, где разрешен алкоголь за рулём. В большинстве стран что я знаю алкоголь запрещен.

VIT ★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.