Как называется мета-глагол (high-order function) "not-null"?

Dasar


type Option = Null | T;

Option Not-Null(Option v, F f) =>
v != Null ? f(v) : Null

Есть какое-нибудь устоявшееся название для операции, которая выше обозначена как Not-Null? (Для Null возвращает Null, а для не-Null применяет переданную функцию.)
Какое слово можно взять в качестве такого названия?
Хочется что-то короткое (3-4 буквы в одно английское слово.

yroslavasako

Хочется что-то короткое (3-4 буквы в одно английское слово.
map

Marinavo_0507

Maybe?

Dasar

map
Коротко, в одно слово, подходит по смыслу. К сожалению, map "занято" под более общую операцию: применение f к списку. http://en.wikipedia.org/wiki/Map_%28higher-order_function%2...

stm5872449

К сожалению, map "занято" под более общую операцию
 :grin:
 fmap aka functor map

yroslavasako

Коротко, в одно слово, подходит по смыслу. К сожалению, map "занято" под более общую операцию: применение f к списку
map - это операция, которая применяется к любому функтору. В общем виде. И нужно предоставить свидетельство, что Option является функтором.

Dasar

Maybe?
Хорошо подходит по смыслу, в похожем контексте используется в разнообразных проектах, короткое, в одно слово (очень близко к этому контрастное (отличается от других часто используемых глаголов).
Отторжение пока чувствую к нему. То ли потому что "придумано не нами (c)", то ли потому что это не совсем глагол. Не получается пока конкретно идентифицировать причины отторжения.
Сейчас буду использовать это слово. Открыт к другим предложениям. Какие еще варианты могут быть?

Dasar

map - это операция, которая применяется к любому функтору. В общем виде. И нужно предоставить свидетельство, что Option является функтором.
В чем суть предложение? Перегрузить поведение map для Option-а?
Возьмем задачу чуть шире. При обработке списков Option-ов возможны три поведения:

(null, 2, 3, null, 5, null, 7).F1(+1) ==> (null, 3, 4, null, 6, null, 8)
(null, 2, 3, null, 5, null, 7).F2(+1) ==> (1, 3, 4, 1, 6, 1, 8)
(null, 2, 3, null, 5, null, 7).F3(+1) ==> (3, 4, 6, 8)

При вводе нового глагола Maybe за F1 закрепляется название Map, а за F3 - Maybe. Вместо использования многосложных конструкций: F1 = Map, F3(f) = Filter(x => x != null).Map(f)

yroslavasako

map внутри map (для option внутри list) - это flatmap.

Dasar

map внутри map (для option внутри list) - это flatmap.
Корни решения растут из рассматривания Option-а, как списка с размером [0..1]?
Идею видел, но не размышлял вдумчиво о плюсах и минусах такого подхода. Математически идея красивая. Монады на ней же строятся.Со здравым смыслом у меня пока не бьется, но это может быть из-за новизны рассмотрения.

yroslavasako

Корни решения растут из рассматривания Option-а, как списка с размером [0..1]?
Корни решения растут из рассматривания Option-а как функтора.

6yrop

В C# этот вопрос уже решили за тебя, иначе ты не сможешь пользоваться monadic comprehension синтаксисом.

Dasar

Корни решения растут из рассматривания Option-а как функтора.
Из этого утверждения не следует, каким будет поведения flatmap-а для Option-а. Соответственно, оно не является ответом на мой вопрос.

Dasar

ты не сможешь пользоваться monadic comprehension синтаксисом.
не использую эту ересь! и другим не советую! )

yroslavasako

Из этого утверждения не следует, каким будет поведения flatmap-а для Option-а. Соответственно, оно не является ответом на мой вопрос.
Согласен. Необходимо дополнительное предположение о эквивалентности Option списку из не более чем одного элемента

danilov

ifSet

6yrop

не использую эту ересь! и другим не советую! )
ну и дурак :)

Dasar

Почему 'set'? Это или "установить значение", или "множество". Ни то, ни другое у меня не соединяется с задачей.

luna89

ну и дурак
В хаскелле вроде do-нотация тоже не тру считается

danilov

Это сокращение от ifValueIsSet

6yrop

В хаскелле вроде do-нотация тоже не тру считается
Где хаскель, а где мы.

danilov

В java есть ifPresent. Ну, и собственно, map...

Dasar

Это сокращение от ifValueIsSet
кучеряво ) Мозг сломаешь, пока уложишь сокращение с полным названием и смыслом )

valodyr

В Rust это называется and_then (есть и парная к ней or_else).

apl13

кучеряво ) Мозг сломаешь, пока уложишь сокращение с полным названием и смыслом )
Upgrade your brain.
Чем тебе вполне монадный (f)map не нравится?
Да,
flatMap :: (a -> Option b) -> Option a -> Option b
flatMap _ Null = Null
flatMap f x = f x

А чтоб не совпадало с определением fmapа, надо строго боксить. В таком виде в твоем Option смешаны в кучу Null и _|_.

Dasar

Чем тебе вполне монадный (f)map не нравится?
Название функции не совпадает с той целью, которая ставится при вызове этой функции.
Flat-овость - это особенность реализации Option-а, а не смысл операции.

apl13

Название функции не совпадает с той целью, которая ставится при вызове этой функции.
?

Dasar

Вернемся к этому вопросу.
Как называется парная операция к fmap? Которая выполняет операцию на Null, но не выполняет над не-Null.

apl13

Ы. :ooo:
Ей нужно даже какое-то специальное название?
ifNull :: b -> Maybe a -> Maybe b
ifNull b Nothing = Just b
ifNull _ _ = Nothing

marat7256

Которая выполняет операцию на Null, но не выполняет над не-Null.

random

Dasar

Не учитывает случай детерминированных операций над Null-ом.

Dasar

Почему проводятся параллели с flatmap? что общего?
У flatmap частичная сигнатура (рассматривая кол-ва): many<many<item>> -> many<item>, у обсуждаемой операции: item{0..1} -> item{0..1}

marat7256

Константы можно выкинуть не ограничивая общности.

Dasar

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

marat7256

Приведи пример.

Dasar

Значение для Null берется из контекста. Соответственно, операция детерминированная - для одного и того же контекста выдаёт одно и тоже, но не является константной - при разных контекстах выдает разные результаты.

string display(item, точность, settings) =>
item.weight.to-string(точность ? settings.точность)

? - синоним операции, которая выполняет операцию над Null, но не выполняет над не-Null

marat7256

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

Dasar

Я так понимаю, что конеткст несложными преобразованиями можно перевети в параметры функции и тогда это уже не будет операция над нул.
Да, замыкание можно развернуть. Но зачем? Как это упростит код?

Dasar

Название функции не совпадает с той целью, которая ставится при вызове этой функции.
Задача 1: получить все сообщения из тредов на текущей странице
решение в лоб: current-page.threads.map(.messages Результат: списки списков вместо списка. Разница между получаемым и ожидаемым в уровнях списков. Закономерное решение использовать flatmap.
Задача 2. скачать аватарку по текущему сообщению.
решение в лоб: current-message.avatar.download. Результат: выдает ошибку рантайма или компиляции: аргумент ожидается, а его - нет. Нет несовпадения уровней, flatmap выпрыгивает как рояль из кустов. Сформулированная семантика уточненного решения не даёт отсылку к преобразованию уровней: обрабатывать ситуации с отсутствующим входным параметром особым образом.

rosali

В хаскеле принято так

Maybe(Nothing, Just
maybe,

Either(Left, Right
either,

то есть можно назвать твою функцию тупо option.

Dasar

either что делает?

apl13

http://www.haskell.org/onlinereport/haskell2010/haskellch9....
either               :: (a -> c) -> (b -> c) -> Either a b -> c
either f g (Left x) = f x
either f g (Right y) = g y

marat7256

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

6yrop

решение в лоб: current-message.avatar.download. Результат: выдает ошибку рантайма или компиляции
Твой лоб выдает ошибки. Ты решил поделиться этим с форумом?

Dasar

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

Dasar

either :: (a -> c) -> (b -> c) -> Either a b -> c
Either - это маркер, помогающий преобразовать целое, описывая операции лишь над частью.
Так?

marat7256

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

Dasar

По-моему, это очевидно без примера.
Неочевидно "Однако, это позволяет понять, что ты как-то особенно трактуешь операцию над нул."

marat7256

Хорошо. Пусть это неочевидно.
Вот с этим ты согласен?
операция над нул, если ты еще включаешь в рассмотрение некоторый конеткст, становится операцией над этим конекстом

Dasar

да

marat7256

Тогда мне кажется очевидным, что "чистая" операция над нул может быть либо константа либо random. С чего я и начал.

Dasar

Тогда мне кажется очевидным, что "чистая" операция над нул может быть либо константа либо random. С чего я и начал.
Очевидно: только Null никогда не бывает.
Всегда есть контекст: Null + f

Dasar

Генеренная в runtime функция без побочных эффектов - является ли чистой операцией?

marat7256

Напомню на минуту с чего все началось:
Которая выполняет операцию на Null, но не выполняет над не-Null.

Dasar

Ок. Исходное утверждение не полное в точной формальной логике.
Корректное утверждение в этой логике:
Которая выполняет операцию на Null внутри контекста, но не выполняет над не-Null внутри контекста.

apl13

:hair:

Dasar

Расшифруй, пожалуйста, словами.
ps
hints:
В чём отличие Either-типа от произвольного tagged union-типа?
В каких задачах стоит использовать Either?

rosali

> В чём отличие Either-типа от произвольного tagged union-типа?
> В каких задачах стоит использовать Either?
В задачах, в которых лень заводить отдельный tagged union-тип? :) В Left можно например положить успешный результат функции, а в Right исключение. Ну это так, пальцем в небо, я не программирую на хаскеле промышленно. Но вообще твой вопрос из разряда зачем нужен std::pair когда можно всегда написать свой собственный struct.
Ну кстати да, погрепал стандартную библиотеку, так его там и используют
try :: IO a -> IO (Either Exception a)
tryJust :: (Exception -> Maybe b) -> IO a -> IO (Either b a)
и т.д.

Dasar

За основу тезиса "Either - маркер, помогающий преобразовать целое, описывая операции лишь над частью" взята статья: http://danielwestheide.com/blog/2013/01/02/the-neophytes-gui...
В ней утверждается, что map и flatmap для Either перегружены особым способом, в отличии от tagged union.
Соответственно, тезис "Either используется в задачах, в которых лень заводить отдельный tagged union-тип" - слабый, из-за отсутствия эквивалентности между Either и tagged union.
std::pair и struct имеют эквивалентные операции.

rosali

> В ней утверждается, что map и flatmap для Either перегружены особым способом, в отличии от tagged union.
Столько cs философии на пустом месте, у меня аж голова кружится. Короче, в хаскельном Either нет этих "проекций", это самый обычный tagged union. either это его fold. fmap не определен.
Я тут правда понял, что хаскельное maybe это тоже fold, а Not-Null с которого всё началось, это таки map. Так что так его и надо называть.

Dasar

Короче, в хаскельном Either нет этих "проекций", это самый обычный tagged union. either это его fold.
Фу! Как скучно! )
Согласен, в haskell - это просто tagged union для ленивых.
ps
Either в scala-е интереснее, помогает делать сложные преобразования простым образом.

yroslavasako

Фу! Как скучно! )
Для оживления дискуссии предлагаю обсудить disjoint union. Tagged union, он же обыкновенная сумма типов, нуден тем, что его каждый раз надо конструировать и раскрывать обратно. А disjoint union - это возможность просто объявить супертип, который просто представляется либо одним, либо другим.
В скале есть костыль для этого
Но он не слишком удобный. Лучше бы это была фишка языка. Вот есть, например, мультинаследование, можно объявить тип наследником нескольких других. type child extends c1 & c2 & c3 . В любой точке, где ожидается тип с1, можно использовать child взамен его. А также взамен c2 и c3.
 А иногда очень не хватает обратной конструкции type base supersede c1 | c2 | c3. И тогда в любом месте кода, где ожидается base, можно было бы использовать на выбор c1, c2, c3. Вот, например, построена у тебя система на обработке событий. ClassA умеет делать process(event : EventA), ClassB умеет делать process(event : EventB). А тебе хочется занаследоваться от обоих, тогда тебе нужно уметь делать process(event : EventA | EventB). А я не знаю язык, в котором такая конструкция типов поддерживалась бы. Программисты скалы страдают и использует вместо строгой типизации (Event1, Event2, Event1 | Event2) просто Object. Программисты джавы не страдают, они с самого рождения только Objectами и пользуются, про типобезопасность не слышали.
А есть языки, где это реализовано из коробки?

Dasar

Еще хочется, чтобы поддерживалась эквивалентность:
 supersede c1 | c2 | c2 <=> supersede (supersede c1 | c2) | c3 <=> supersede c1 | (supersede c2 | c3) <=> supersede c2 | (supersede c3 | c1)
Делается через нормализацию supersede-типа.
Экспериментировал с этой концепцией в своем язычке. До релиза не довёл.

yroslavasako

Еще хочется, чтобы поддерживалась эквивалентность:
supersede c1 | c2 | c2 <=> supersede (supersede c1 | c2) | c3 <=> supersede c1 | (supersede c2 | c3) <=> supersede c2 | (supersede c3 | c1)
именно. Поэтому мне костыль для скалы и не понравился, он ненормализованный

Dasar

Для нормализации требуется полноценный язык с операциями декомпозиции, сортировки и т.д. Такой язык - или не строится поверх используемой системы типов, или строится с огромным оверхедом (по объему кода и производительности компиляции).

yroslavasako

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

Dasar

Ну я понимаю, что внутри скалы я это не сделаю.
Практически или теоретически?
Scala-у плохо чувствую, но предполагаю, что теоретически сделать можно. Точно можно сделать на template-ах C++, но это будет здоровый монстр.

yroslavasako

Ну если доходить до темплейтов и макросов, то проще уже сразу писать плагин для компилера. Тогда может и можно будет что-то сделать.
А так implicit нельзя запускать по цепочке. А на них строилось то решение, которое ненормализованное. Для нормализации потребуется рекурсивное implicitное преобразование, а такого нельзя.

Dasar

А так implicit нельзя запускать по цепочке.
да, это часто мешает.
Для нормализации потребуется рекурсивное implicitное преобразование
Как вариант, сделать через явный вызов преобразования. Для этого требуется функция, выполняемая на уровне системы типов, с сигнатурой: flat_supersede<T1,..TN> Normalize(recursive_supersede<..>)
На template-ах C++ вид вызова: supersede<bla-bla>::normalized.

karkar

 
Tagged union, он же обыкновенная сумма типов, нуден тем, что его каждый раз надо конструировать и раскрывать обратно. А disjoint union - это возможность просто объявить супертип, который просто представляется либо одним, либо другим.
...
А есть языки, где это реализовано из коробки?

См. union types в Ceylon:
http://ceylon-lang.org/
А другая твоя хотелка - это intersection types из того же Ceylon'a. Уже все сделали.

karkar

Фу! Как скучно! )
Согласен, в haskell - это просто tagged union для ленивых.
ps
Either в scala-е интереснее, помогает делать сложные преобразования простым образом.
Тебя обманывают, а ты не верь.
В хаскеле "Either e" это монада (и функтор, само собой поэтому имеет и fmap и flatMap (в хаскельном его написании) и поддержку do-нотации (которая вместо скального for'a). Работает как RightProjection в скале, т.е. маппит значения Right.

rosali

> Тебя обманывают, а ты не верь.
"unbiased" как раз означает, что "главной" проекции не зафиксировано. А так получается, что Either в хаскеле работает как Try в скале. И используется так же, исключения пробрасываются через Left, а успехи обрабатываются с помощью fmap над Right.
Ну ок, это не то же самое, что "Either это обычный tagged union", согласен :)

Dasar

Работает как RightProjection в скале, т.е. маппит значения Right.
Итоговая коллекция будет содержать как модифицированные Right, так и исходные Left?

karkar

Да.
data  Either a b  =  Left a | Right b
deriving (Eq, Ord, Read, Show, Typeable)

instance Functor (Either a) where
fmap _ (Left x) = Left x
fmap f (Right y) = Right (f y)

instance Monad (Either e) where
return = Right
Left l >>= _ = Left l
Right r >>= k = k r

yroslavasako

Осталось дождаться, когда там паттерн матчинг изобретут. Потому что делать юнион тип и проверять все варианты через if - это извращение.
Оставить комментарий
Имя или ник:
Комментарий: