Типизация функций - зло или добро?
Запись от CoderHuligan размещена 17.02.2023 в 13:47
Показов 9927
Комментарии 97
|
Это продолжение размышлений из позапрошлого поста данного блога. На этот раз разговор пойдет о функциях. Обычные языки программирования (ЯП) имеют не только типизированные наборы данных - структурный тип, но и как ни странно, это распространяется и на функции (процедуры). То есть: каждая отдельная функция представляет собой совершенно отдельный тип. Происходит это от того, что сигнатуры функций могут различаться, как по количеству формальных аргументов, так и по их типу, и по их порядку размещения. А так как это все не поддается никакой систематизации с точки зрения языка, и отдано на откуп программистам, то и получается, что сигнатура функции представляет собой отдельный "функциональный" тип. Хорошо это или плохо я не знаю, вот и пытаюсь выяснить.. Плохо то, что порядок размещения, тип и количество аргументов накладывает дополнительную нагрузку на мозг программиста, так как все это надо помнить, а если и не помнить, то обращаться к докам, что замедляет процесс кодирования. К тому же, вызывающая функция обязана знать эту сигнатуру, иначе вызов вызываемой функции становится невозможен. А это означает одно: большую связность кода, что приводит к проблемам переносимости, расширения, изменения и т.д. Раньше, когда памяти было немного, такой подход был оправдан. Однако сейчас памяти у мас завались: её некуда девать, а производительность от этого не увеличилась, а продолжает падать.. Далее. По соглашениям функция (к примеру в языке Си или С++) оставляет возвращаемое значение в регистре EAX. Так уж повелось и закрепилось. Однако для передачи аргументов используется стек в памяти. Иначе невозможно обеспечить рекурсивность, когда функция, к примеру, вызывает сама себя. Однако в обычных приложениях рекурсивные алгоритмы используются крайне редко, поэтому парадигма, которая заточена только на то, что кто-то когда-то может использовать рекурсию, становится достаточно накладной. Допустим у функции есть два или три аргумента. Её вызывает другая функция. Эта другая знает, что вызываемая имеет три аргумента определенных типов. И вот, нам нужно изменить сигнатуру вызываемой: добавить четвертый аргумент. Добавили. При этом нам требуется изменить и код тех функций, которые вызывают данную, так как её сигнатура изменилась.. Это не просто плохо, а очень плохо, и считается дурным тоном. Но от этого никуда не деться. ООП прошу не предлагать. То есть изменился тип функции, теперь он стал другим, и по цепочке это повлияло на все вызывающие функции. Это плохо потому, что каждый программный компонент становится зависим от других, а ведь все мечтают о независимости модулей.. Оопщики это понимают, но нашли выход в другой, еще более развесистой лапше. Как можно было бы выйти из данной ситуации? Вот два примера:
Вот пример другого подхода:
Что мы видим в последнем примере? Ну, то, что теперь мы имеем функцию с одной точкой входа в неё. Именно с одной! То есть функция возвращает одно значение и принимает одно значение, хотя фактически работает с двумя! Через единственный указатель.. Появляется сразу вопрос: а что мы можем с этого получить? новую парадигму? Может быть, а может быть и нет. Но, то, что это приносит свои плюшки - очевидно. Если с одним структурным типом работает множество функций, то изменение этого типа, не приносит изменений этих других функций просто потому, что налицо наличие единственного типа сигнатуры функции, за исключением типа единственного аргумента. Но и это обстоятельство можно было бы преодолеть.. А как это уже другой вопрос.. | ||||||||||
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 97
Комментарии
-
Опять же.... если используете чужую либу - вы понимаете зачем ее используете. Ну и контролируйте ченжлог.... если там вышли не адекватные изменения - значит оставляете в зависимостях старую версию.Запись от voral размещена 20.02.2023 в 12:32
-
Иначе можно совсем до маразма найти. И завтра давайте подумаем: а если завтра Вася Пупкин в коде на C++ захочет писать русскими словами команды..... Будете правки в компилятор C++ вносить или же попросту пошлете его с его хотелками?Запись от voral размещена 20.02.2023 в 12:34
-
Запись от XLAT размещена 20.02.2023 в 13:18
-
Да причем здесь пространство имен?? Речь не об этом. Вот два кода:
1)
иCode 1 2 3 4
func1(){ func2(a, b, c) func3(c, b, a, g) }
2)
Какой код чище? А где аргументы? В последнем случае важно только имя функции. Сигнатура каждой функции скрыта от внешнего мира (сокрытие данных). В самом коде, во время вызова, это не прописывается. Это прописано только в определении функции. Или в обьявлении. Налицо ограничение. Если вместо трех параметров функции надо передать всего одно, то статические языки этого не позволят. Динамические позволят, да и то не все.Code 1 2 3 4
func1{ func2// a b c лежат на стеке func3// тоже }
Современные языки мало чего позволяют. К примеру нельзя прыгнуть прямо внутрь выражения. Это невозможно. Но если это реализовать, то появилось бы больше возможностей для самовыражения. Так и по другим пунктам.Запись от CoderHuligan размещена 20.02.2023 в 15:17
-
1 первый чище. т.к. гарантированно нет сайдэффектов, и гарантировано есть параметры необходимые для выполнения функции
Сообщение от CoderHuligan
2 судя из приведенного примера вам просто надо все переменные хранить в глобальной области видимости.... Надо ли это кому то еще кроме вас
?
3 читая 1 код я точно знаю что этой функции нужны конкретные переменные, во втором случае я спокойно выкину переменную, которая, как мне вдруг покажется, не нужна.
Таким образом тут дело не только в чистоте (но я по прежнему считаю первый более чистым), но и в меньше шансов получить баги сложно выявляемые.
И чем это хорошо? т.е. публичная функция параметры которой являются секретом - ни каких несостыковок не замечаете?
Сообщение от CoderHuligan
Более того, передачу по значению и по параметру -придумали не просто так. При этом если чайник создаст функцию по тому принципу как сейчас есть (в случае простых типов параметров), она не будет иметь сайдэффекта (по крайней мере в отношении этих параметров), а вот в вашем случае будет если он не предпримет некоторые действия. Если же вы имеете ввиду стек вызываемой функции - тогда опять же вы ни чего нового не предлагаете. т.к. во втором примере вы не показали как вы эти переменные в стек положили (хотя, судя по комментарию речь все же о глобальных переменных).. При этом глбальные переменные на столько глобальные, что они даже между модулями глобальные.... А это вообще трындец проекту. тут уже рассуждать о "сайдэффектах" нет смысла - код будет жить своей жизнью.
И вот вам еще и 4 пункт... в вашей второй версии, мы, используя эту функцию, и не имея представления что там внутри, должны будем на всякий случай, сами делать копию перменной. (возьмите ваши примеры, и подумайте что будет если вам важно, чтоб переменные в области видимости func1 не меняли значения. те.. передавались именно по значению. Вы уверены что внутри func2 нет a= a +100500 ?
И что за функция где это нужно? И чем отсутствие одного из параметра, отличается от его необязательности и наличием возможности задать значение по умолчанию? Странная ситуация, вы не приводите конкретный пример. Вы сразу говорите "это важно и надо"... зачем? почему? кому?
Сообщение от CoderHuligan
Нет. Программирование это не рисование где провел линию по другому и можно сказать "я художник я так вижу"... Ваша хотелка, если ее реализовать, приведет к сложно тестируемому коду, который будет сложно читать. А уж баги в нем искать...... Тут не самовыражаться надо, а максимально четко излагать свои мысли.
Сообщение от CoderHuligan
А все крутится вокруг чисто академического интереса, а не реальной ситуации с реальным проектом. Как и с обсуждением goto. К слову, я пожалуй впервые за 23 года (раньше не помню, возможно даже впервые за 33-36 лет применил goto. Да и то потому что между меткой и goto гарантировано не будет расти код, и я единственный разработчик.Запись от voral размещена 20.02.2023 в 16:03
-
Проиллюстрирую ваш код для случая, что нам важно чтобы функция не меняла входные параметры
А "классический" будет гораздо чище. даже если пред положить, что функция работает "по ссылке", тогда мы "сохраним" только те переменные, которые передавали, а не все....Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
func1{ tmpA = a tmpB = b tmpC = c tmpG = g // и так все переменные в нашей программе, // т.к. мы же не знаем что функция захочет использовать func2// a b c лежат на стеке a = tmpA b = tmpB c = tmpC g = tmpG func3// тоже a = tmpA b = tmpB c = tmpC g = tmpG }
Запись от voral размещена 20.02.2023 в 16:13
-
Ну и плюсом... Что значит "лежат на стеке"? В стеке их функции разбирают "по порядку" или "по имени"? Как вообще вы видите помещение этих переменных в этот стек?Запись от voral размещена 20.02.2023 в 16:15
-
Поймите же наконец одну вещь: нельзя всех построить в одну шеренгу и начать отдавать команды типа упал/отжался.. Ну не все хотят строем идти, многие ностальгируют по turbo basic, причем до сих пор. По простым осям, языкам. По языкам, в которых - да, можно отстрелить себе ногу, но это будет ваша нога, а опыт приобретается в боях, а не в комфортной среде языков, где вам этого сделать не позволят. Современные языки, заточенные на продакшен зачастую не есть то, что нужно любителям. Мне интересно искать баги в собственном коде и разбираться в низкоуровневых конструкциях. Интересно использовать простые решения, простые IDE, языки. То есть те инструменты которые я могу понять. Если я не могу понять инструмент, то он не мой. Как школьнику объяснить каким образом инфиксное выражение превращается в целевой код? Для этого школьнику надо сперва прочесть книгу дракона (Ахо, Ульман, Компиляторы и их реализация) книжку на 1000 стр. Затем какой-нибудь ассемблер и еще кучу разной литературы. Между тем постфиксное выражение можно объяснить на пальцах одной руки, так как оно не требует преобразования и не требует знания работы YACC/LEX. То есть: меняем всего один параметр, и сразу отпадает куча слоев, и все становится проще и яснее. В инфиксное выражение прыгнуть естественно нельзя, а постфиксное - можно. Профи фыркнут как обычно, а ценители заценят.
Разработчику это надо. Ему важно иметь возможность работать только с одной сущностью. Допустим одна функция производит деление двух аргументов и на выходе возвращает два значения а) частное б) остаток.
Другая функция берет два аргумента и делает с ними что-то еще. Допустим в процессе нам потребовалось, что вторая функция еще принимала бы третий аргумент - погрешность. В этом случае придется изменить код всех функций в которых прописан вызов данной. Это не есть хорошо. Между тем, если бы была скрыта явная передача фактических параметров, то изменение кода произошло бы только в данной функции.
Почему аргументы мы передаем через стек, а возвращаемое значение оставляем в низкоуровневом регистре EAX? Это сразу привязывает нас к определенной жестко заданной системе. Если же возвращаемые значения также оставлять в стеке, то можно возвращать любое их количество.
Можно возразить: тогда трудно было бы контролировать стек. Но это можно автоматизировать.Запись от CoderHuligan размещена 20.02.2023 в 16:39
-
Не обязательно. Есть же _fastcall вариант. Но и ещё один момент, который ниже.
А можно передавать один аргумент, структурой\классом.
То мы получаем раздутую и избыточную структуру, а так же полную неопределённость, ибо мы уже не знаем какие из её полей нужны данной функции, а какие нет. Получаем лапшу, кашу которую вы "ООПэшникам" приписываете.
К каким докам? Можно просто на сигнатуру функции посмотреть прямо в IDE...Запись от Usaga размещена 20.02.2023 в 16:43
-
Запись от CoderHuligan размещена 20.02.2023 в 16:54
-
Запись от Usaga размещена 20.02.2023 в 17:40
-
Вы некорректно проводите параллели. Если ребенок хочет прыгнуть с крыши чтоб полетать. А родитель говорит что так нельзя. Это тоже считаете что ребенка "в одну шеренгу и начать отдавать команды типа упал/отжался.."?
Сообщение от CoderHuligan
А почему вы приводите отстраненный пример? вы уж берите и сравниваейте, что легче объяснить:
Сообщение от CoderHuligan
1. вот функция у нее есть аргументы они такие
2 вот функция ты там ее вызови, но сначала глянь в доке чего она хочет и загони куда то вот туда эти переменные.....
Вот в вашем варианте так и получится в итоге.
Сообщение от CoderHuligan
Да ситуация частая - поэтому в проекте создаются обертки, которые коррелерируют с предметной областью и гоняйте именно стркутуру/объект.
Сообщение от CoderHuligan
Это не так. Пути как я описал выше два:
Сообщение от CoderHuligan
1. Значение по умолчанию
2. Другая похожая на первую функция, но с погрешностью
Если для проекта важно: все равно надо будет править места вызова. И в случае вашего стека тоже.
Т.е. вся система разом становится не стабильной.
Сообщение от CoderHuligan
Но вы так и не пояснили: а как будет конкретная функция доставать из этого стека то, что ей нужно. "Стек" в классическом понимании тут явно не подойдет. По именам тоже не проктит (и придется с чужими либами как то сопрягаться, и вообще с переменными не разбериха будет Например у меня есть в стеке два числа a и b . я пишу код на вашем языке
a= 2 b =4
Что в результате выполнения будет?Code 1 2 3 4 5
// тут некая реализация помещения в стек sqr sqr sum print
Запись от voral размещена 20.02.2023 в 19:01
-
Запись от Usaga размещена 21.02.2023 в 00:55
-
Да, вы правы, стек тут не прокатит.. Зато очередь может. Допустим код заталкивает в очередь a= 2 b =4. Очередь выглядит так:
Сообщение от voral
а в
А sqrt извлекает корень из 4 и толкает результат в очередь, которая теперь выглядит так:
рез 2
Следующий sqrt извлекает корень из 2 и толкает результат в очередь:
рез рез
Получившийся код:
а в sqrt sqrt
С другой стороны, если бы у нас был стек, то код был бы таким:
а в sqrt SWAP sqrt
swap берет два верхних значения и меняет их местами. Налицо полная зависимость от стека. С очередью все намного проще и естественней выходит. Причем очередь позволяет делать задержку выполнения, что не позволяет стек. Это более высокоуровневая структура данных чем стек, так как имеет вместо одного входа, один вход и один выход (почти элемент И/НЕ).
То есть видим: многое зависит от реализации низкоуровневых механизмов, которые отражаются на верхних уровнях.Запись от CoderHuligan размещена 21.02.2023 в 12:06
-
Запись от CoderHuligan размещена 21.02.2023 в 16:11
-
таким образом все становится очень зависимым от очередности и от делает ли функция что то или нет. И сразу мой код на вашем языке становится абсолютно не читаемым. Стоит между двумя sqr добавить еще функцию и все разваливается. Не говоря о том, что вы не угадали, то что я хотел сделать
Сообщение от CoderHuligan

А так же и код ваш где то в начале обсуждения становится нерабочим. Точнее это лотеря - может повезет а может нет. Если переменные в очреди останутся в нужном порядке - повезло. Если учесть что вы ратуете еще и за "прыжки" по коду, то код становится абсолютно не отлаживаемым.
Нет. Тут мы видим, что тут сложность вырастает многократно. Мало того что мы будем обязаны знать как реализована каждая функция внутри, но и перед вызовом каждой обеспечивать правильно состояние очереди.
Сообщение от CoderHuligan
Запись от voral размещена 21.02.2023 в 16:28
-
Что является самым хреновым вариантом сайдэффекта.
Сообщение от CoderHuligan
Запись от voral размещена 21.02.2023 в 16:29
-
Запись от CoderHuligan размещена 22.02.2023 в 11:18
-
Запись от XLAT размещена 22.02.2023 в 11:21
-
Я удалил, потому что это действительно ерунда полная.
В качестве лабораторной работы для собственного опыта - можно было бы запилить. Тем более что заготовка есть, правда на си и только под одного игрока. С сетью я не работал, хотя есть примеры кода как это делается.
Если и буду делать, то только на Tcl Tk, тем более там очень легко работать с сокетами и делать полноценное гуи. Коммерция меня не интересует.Запись от CoderHuligan размещена 22.02.2023 в 11:48

?

