Pull to refresh

Comments 55

Добро пожаловать в функциональное программирование.

Поражаюсь вашей скорости чтения, коллега.

У меня тоже заняло секунд 20. Наверное потому, что я давно и много занимаюсь и программированием, и чтением блогов. В чем проблема?

У вас тоже? Вы с коллегой lair'ом там что, на скорость чтения соревнуетесь? В таком случае примите от меня респекты и в свой адрес, коллега.

Переходите на Python… Там можно «из коробки» вернуть из функции несколько значений и, или присвоить их одной tuple, или сразу «рассортировать» по отдельным переменным.

Или на последние версии javascript (ECMA 2015)

В GO тоже можно несколько значений вернуть, если не путаю.
Ну, да: hour, min, sec := time.Now().Clock()
UFO just landed and posted this here

Многие вещи становятся проще, если предполагать, что у всех функций всегда один аргумент.


Математик и логик Хаскелл Карри ещё в первой половине 20 века ввёл концепцию "каррирования" — превращения функций многих переменных в функцию одной переменной. Эта техника широко используется в языках, поддерживающих функциональную парадигму, например, Haskell и OCaml.


В Standard ML пошли немного другим путём: функции с "несколькими аргументами" на самом деле всегда принимают на вход один аргумент — кортеж.

Каррирование к тому, что обсуждается в этом посте, не имеет никакого отношения. Оно никак не упростит то же "программирование процессов мышкой".

Если у функции больше двух аргументов, стоит задуматься о передаче хэша/словаря/таблицы.
Из крайности в крайность: четыре внятных аргумента вида width height и так далее, которые влазят в экран и имеют говорящие имена, лучше, чем ватное opts, на мой взгляд; с другой стороны — десять аргументов — это уже нездорОво.

Виной всему — моя лень. Мне было лениво расписывать "нездоровый" вариант на десять аргументов, и я остановился на более коротком, надеясь, что идея будет понятной. В результате запорол пример. Ну что ж, буду работать над собой.

Ваша проблема на самом деле связана с типами. Меняя порядок следования аргументов в динамическом языке вы неявно меняете тип функции. В хорошем статическом языке такая ситуация невозможна. Предвосхищая замечания, для типов, одинаковых на машинном уровне, но разных семантически, существует newtype.


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

UFO just landed and posted this here
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
UFO just landed and posted this here
Все, все, ухожу. Представляю, какая космическая скорость разработки у вас, если на каждый чих нужно пару типов объявить.

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


И в базе так же лежат, дайте угадаю, сериализованные вместе с типами, да?

Ну можно и с типами, если очень хочется. Правда не совсем понимаю, какое это отношение имеет к обсуждаемой теме.

UFO just landed and posted this here
По теме «космической скорости»: использовали бы статическую типизацию для дюймов и метров, глядишь, Mars Climate Orbiter по-прежнему бороздил бы просторы Большого театра.

Не помогло бы. По радиосигналу информация о типах, увы, не передается...

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

data Image = Image Width Height [[Int]]

rotate :: Image -> Image
rotate (Image (Width w) (Height h) xs) = Image (Width h) (Height w) $ transpose xs
UFO just landed and posted this here
UFO just landed and posted this here

Весьма категоричное заявление, коллега. Причем в нем не охвачены средние программисты, к коим я и себя отношу и коих по моему мнению — большинство. Мне, например, типизация (если она есть) весьма помогает ориентироваться не только в чужом коде, но и в собственном тоже (разумеется, при поддержке IDE). Не спасает, но и не мешает.

UFO just landed and posted this here
Чтобы оно, это ваше IDE, разобралось в типах, — языку типизация вообще не нужна

Объяснитесь, пожалуйста. Если досуг, разумеется.

UFO just landed and posted this here

Зато без "жесткой типизации" изменилось стороннее API, данными которого мы пользуемся — и вместо падения сразу вся база оказывается забита строками "undefined" на месте обязательных колонок :)

UFO just landed and posted this here

Вот-вот. Вам нужны аннотации, тесты, CI, валидация… А мне достаточно прописать пару типов!


При этом мой неакадемичный подход заодно и строчку "undefined" вместо "http://example.com" развернет, потому что я сигнатуру функции буду матчить по префиксу, а прекрасные типы схавают и не поперхнутся.

Существует такой тип как System.Uri.

UFO just landed and posted this here
аннотация типов

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


А я вообще весьма категоричный хрен.

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

UFO just landed and posted this here
Вы правда не понимаете или прикидываетесь?

языку типизация вообще не нужна

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

— Указание типа это один из вариантов паттерн-матчинга
В Эрланге например можно написать так:
f(X) when is_integer(X) -> ...
В других языках указывается тип аргумента:
f(int x) { ... }

хендлер не заматчится и процесс рухнет

— В Эрланге есть процессы, которые встроены на уровень языка.
В отличие от многих других языков, где потоки создаются средствами ОС.

Вместо пяти экранов защитного кода
Надо написать пять экранов паттернов.

я просто напишу хендлеры на все понятные случаи входных данных и все
Это в любом языке так — написали хендлеры и все. Другое дело, что в Эрланге проверки можно описывать декларативно, и среда сама выкинет исключение если что. И да, если указано, что функция принимает на вход int, а в рантайме туда передается object, то и в других динамических языках выбрасывается исключение, и процесс так же падает. Если конечно нет специального обработчика, который его поймает.
обсуждать нужды средних программистов мне тоже как бы недосуг: это как обсуждать стихи средних поэтов, интересно только автору.

Безотносительного всего остального, но эта аналогия совсем не к месту. Стихи — творчество и тут естественно выбирать лучшее, а вот в программировании работы хватает, так что задействованы не только "средние", а и "слабые" программисты. И инструменты позволяющие чего-то добиться имея слабую/среднюю команду однозначно интересны. Опять же, безотносительно типизации.

Полностью согласен, программирование — это craft, а не art. Воспроизводимость, дополняемость, инженерность, если хотите, здесь важнее уникальности или самобытности. Понимание этого приходит со временем и не ко всем.

UFO just landed and posted this here

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

имеет смысл эту команду обучать, а не топор точить

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
в последних стандартах js последний пример еще красивее пишется
function showWarning({
    width = 200,
    height = 100,
    title = "Предупреждение",
    contents = "Ничего серьезного."
}) {
    //...
}
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 приходится платить разработкой структуры входных/выходных аргументов в отдельных классах. В основном получается муторно, но иногда — весело.
Судя по примеру на JS — вас не волнует, что там реально предается в opts, тогда вместо ваших «отдельных классов» просто используйте в PHP для тех же целей stdClass что-то типа такого:

$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 ?? "Ничего серьезного.";
    //...
}


в первой строке функции, чтобы уж совсем красиво было, вместо Null-коалесцентного оператора лучше сделать приведение типа входного $params к объекту, а то вдруг там массив или еще что вместо stdClass:

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 ?? "Ничего серьезного.";
    //...
}
Но вообще, такой подход к вводу параметров/выводу результатов — это даже не BDSM, это гарантированный отстрел всех ног да и рук тоже :)
Судя по примеру на 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 даже сомнений не было, что я имею в виду), то такой вариант, я думаю, вполне даже хорош.

Sign up to leave a comment.

Articles