Comments 55
Добро пожаловать в функциональное программирование.
Многие вещи становятся проще, если предполагать, что у всех функций всегда один аргумент.
Математик и логик Хаскелл Карри ещё в первой половине 20 века ввёл концепцию "каррирования" — превращения функций многих переменных в функцию одной переменной. Эта техника широко используется в языках, поддерживающих функциональную парадигму, например, Haskell и OCaml.
В Standard ML пошли немного другим путём: функции с "несколькими аргументами" на самом деле всегда принимают на вход один аргумент — кортеж.
Ваша проблема на самом деле связана с типами. Меняя порядок следования аргументов в динамическом языке вы неявно меняете тип функции. В хорошем статическом языке такая ситуация невозможна. Предвосхищая замечания, для типов, одинаковых на машинном уровне, но разных семантически, существует newtype
.
Передача вместо множества аргументов одного большого объекта, конечно, решает проблему с порядком аргументов, но рождает проблемы ещё больше и серьёзнее.
newtype Title = Title String
newtype Width = Width Integer
newtype Height = Height Integer
showWarning :: Width -> Height -> Title -> String -> a
showWarning (Width width) (Height height) (Title title) contents = undefined
main = showWarning 1 2 (Title "Title") "Content"
-- No instance for (Num Width) arising from the literal ‘1’
main = showWarning (Height 1) (Width 2) (Title "Title") "Content"
-- Couldn't match expected type ‘Width’ with actual type ‘Height’
main = showWarning (Width 1) (Height 2) (Title "Title") "Content"
-- Ok
Все, все, ухожу. Представляю, какая космическая скорость разработки у вас, если на каждый чих нужно пару типов объявить.
Мне — не нужно. У автора поста возникла проблема. Я утверждаю, что фундаментально это проблема не синтаксиса, а типизации и показываю как бы автор мог её решить на статически типизированном языке.
И в базе так же лежат, дайте угадаю, сериализованные вместе с типами, да?
Ну можно и с типами, если очень хочется. Правда не совсем понимаю, какое это отношение имеет к обсуждаемой теме.
… а теперь напишите функцию, которая будет поворачивать изображение, пользуясь "типами" высоты и ширины...
data Image = Image Width Height [[Int]]
rotate :: Image -> Image
rotate (Image (Width w) (Height h) xs) = Image (Width h) (Height w) $ transpose xs
Весьма категоричное заявление, коллега. Причем в нем не охвачены средние программисты, к коим я и себя отношу и коих по моему мнению — большинство. Мне, например, типизация (если она есть) весьма помогает ориентироваться не только в чужом коде, но и в собственном тоже (разумеется, при поддержке IDE). Не спасает, но и не мешает.
Чтобы оно, это ваше IDE, разобралось в типах, — языку типизация вообще не нужна
Объяснитесь, пожалуйста. Если досуг, разумеется.
Зато без "жесткой типизации" изменилось стороннее API, данными которого мы пользуемся — и вместо падения сразу вся база оказывается забита строками "undefined" на месте обязательных колонок :)
Вот-вот. Вам нужны аннотации, тесты, CI, валидация… А мне достаточно прописать пару типов!
При этом мой неакадемичный подход заодно и строчку "undefined" вместо "http://example.com" развернет, потому что я сигнатуру функции буду матчить по префиксу, а прекрасные типы схавают и не поперхнутся.
Существует такой тип как System.Uri
.
аннотация типов
Получается, что самому языку типизация не нужна, но чтобы IDE могло в типах разбираться все-таки в каком-то виде (аннотации) эти типы указывать нужно.
А я вообще весьма категоричный хрен.
Вы полностью подверждаете свою собственную характеристику. Причем во всех частях. Похвальная самокритичность.
языку типизация вообще не нужна
— Эрланг это язык со строгой динамической типизацией.
Именно она помогает паттерн-матчингу и предохраняет от большинства ошибок.
— Указание типа это один из вариантов паттерн-матчинга
В Эрланге например можно написать так:
f(X) when is_integer(X) -> ...
В других языках указывается тип аргумента:
f(int x) { ... }
хендлер не заматчится и процесс рухнет
— В Эрланге есть процессы, которые встроены на уровень языка.
В отличие от многих других языков, где потоки создаются средствами ОС.
Вместо пяти экранов защитного кодаНадо написать пять экранов паттернов.
я просто напишу хендлеры на все понятные случаи входных данных и всеЭто в любом языке так — написали хендлеры и все. Другое дело, что в Эрланге проверки можно описывать декларативно, и среда сама выкинет исключение если что. И да, если указано, что функция принимает на вход int, а в рантайме туда передается object, то и в других динамических языках выбрасывается исключение, и процесс так же падает. Если конечно нет специального обработчика, который его поймает.
обсуждать нужды средних программистов мне тоже как бы недосуг: это как обсуждать стихи средних поэтов, интересно только автору.
Безотносительного всего остального, но эта аналогия совсем не к месту. Стихи — творчество и тут естественно выбирать лучшее, а вот в программировании работы хватает, так что задействованы не только "средние", а и "слабые" программисты. И инструменты позволяющие чего-то добиться имея слабую/среднюю команду однозначно интересны. Опять же, безотносительно типизации.
Полностью согласен, программирование — это craft, а не art. Воспроизводимость, дополняемость, инженерность, если хотите, здесь важнее уникальности или самобытности. Понимание этого приходит со временем и не ко всем.
Слабой команде можно платить меньше. Если язык позволяет с такой командой добиваться результата, то это вполне возможная стратегия.
имеет смысл эту команду обучать, а не топор точить
И. Не "а", а "и". Имеет смысл и инструментарий содержать в порядке, и навыки его использования нарабатывать. Вы, дружище, бросаетесь в крайности, пролетая мимо золотой середины.
function showWarning({
width = 200,
height = 100,
title = "Предупреждение",
contents = "Ничего серьезного."
}) {
//...
}
Судя по примеру на JS — вас не волнует, что там реально предается в opts, тогда вместо ваших «отдельных классов» просто используйте в PHP для тех же целей stdClass что-то типа такого:function showWarning(opts) { var opts = opts || {}; // если отсутствуют входные параметры var width = opts.width || 200; // если не указана width, то width = 200 var height = opts.height || 100; // если нет height, то height = 100 var title = opts.title || "Предупреждение"; var contents = opts.contents|| "Ничего серьезного."; //... }
В PHP приходится платить разработкой структуры входных/выходных аргументов в отдельных классах. В основном получается муторно, но иногда — весело.
$params = new \stdClass;
$params->width = $x;
$params->height = $y;
function showWarning($params = null) {
$params = $params ?? new \stdClass; // если отсутствуют входные параметры
$width = $params->width ?? 200; // если не указана width, то width = 200
$height = $params->height ?? 100; // если нет height, то height = 100
$title = $params->title ?? "Предупреждение";
$contents = $params->contents ?? "Ничего серьезного.";
//...
}
function showWarning($params = null) {
$params = (object) $params; // если отсутствуют входные параметры
$width = $params->width ?? 200; // если не указана width, то width = 200
$height = $params->height ?? 100; // если нет height, то height = 100
$title = $params->title ?? "Предупреждение";
$contents = $params->contents ?? "Ничего серьезного.";
//...
}
Судя по примеру на JS — вас не волнует, что там реально предается в opts
Не совсем так. Я бы с радостью задал ожидаемую структуру входного объекта и для JS, вот только пока не знаю, как это сделать. Лучшее, что я пока нашел:
/**
* ..
*
* @param {Object} opts
* @param {string} opts.currency
* @param {Object} opts.screen
* @param {boolean} opts.screen.save
* @param {string} opts.screen.prefix
* @param {Object} opts.suite
* @param {string} opts.suite.pack
* @param {string} opts.suite.scenario
*/
function doGood(opts) {
var opts = opts || {}
var suite = opts.suite || {pack: "undef", scenario: "undef"}
var optsScreen = opts.screen || {} // screenshots related opts
var saveScreens = optsScreen.save || false // don't save screenshots by
var savePrefix = optsScreen.prefix || "default" // default prefix for screenshots
var currency = opts.currency || "EUR"
]1
Получается "масляное масло" — задавать структуру входного аргумента в док-блоке и парсить его же в теле функции в первых строках. Структура входных аргументов на PHP удобна всплывающими подсказками и autocomplet'ом (поддержка IDE).
Если бы можно было ставить ссылку на структуру в док-блоке, хватило бы примерно такого:
/**
* ..
*
* @param {Object} opts
* @param {string} opts.currency
* @param {Object} opts.screen - see @Screen.obj
* @param {Object} opts.suite - see @Suite.obj
*/
function doGood(opts) {}
А чем вариант @param {ScreenOptions} opts.screen
плох?
У меня очень положительные впечатления от использования java packages & PHP namespaces, а вот в JS я подобного механизма не вижу. Когда PHPStorm пытается предложить мне автодополнение к началу какой-нибудь JS-функции, то он сперва долго "шуршит мозгами", а потом выдает сразу список всего, что нашел, причем одноименных функций из разных источников там столько, что автодополнением для JS'а я, например, пользоваться не могу. Возможно дело в том, что я работаю с Magento, а там куча не только своего кода, но и и кода из сторонних модулей, со своими JS-зависимостями. Иногда приходится из 5 JQuery-библиотек, которые тянутся модулями, оставлять одну посвежее, а остальные глушить (правда это для М1 было, с двойкой пока такого опыта не было).
Надо будет попробовать ваш вариант на небольшом, отдельном проекте и понаблюдать за. Боюсь, правда, разбор всего содержимого каталога vendor вызовет примерно такой же эффект — куча одноименных функций без возможности идентификации источника кроме как по имени файла (даже без пути). Но если давать "кучерявые" имена для своих структур (VendorProjectModuleClass — чтобы у IDE даже сомнений не было, что я имею в виду), то такой вариант, я думаю, вполне даже хорош.
Отвлеченно о входных/выходных аргументах