Здравствуйте, Shmj, Вы писали:
S> для облегчения написания/поддержки кода?
Ну когда на хаскеле пишешь, они повсеместно, просто потому что это основной способ написания кода, который меняет какое-то состояние, бросает ошибки и делает ввод-вывод. Это как прозой говорить.
А из более интересных применений, недавно на работе писал парсер с использованием библиотеки монадных парсер-комбинаторов megaparsec, там код выглядел так:
pLam = do params <- pParamDefs
kw "=>"
(ty, exp) <- try pTypedExpr <|> pUntyped pExpr
return $ Lam params ty exp
pTypedExpr = do
ty <- pType
kw ":"
exp <- pExpr
return (Just ty, exp)
pAssign = do
(ty,name) <- pPossiblyTypedName
kw "="
exp <- pExpr
return $ Assign ty name exp
pFunDef = do
name <- pName
lam <- pLam
return $ Assign Nothing name lam
pExpr = pArray <|> try pTable <|> pBlock <|> try pIf <|> try pLam <|> try pLet <|> makeExprParser pTerm operatorTable
pTable = between (symbol "{") (symbol "}") (Table <$> listOf pField)
pField = do nm <- pName
kw ":"
e <- pExpr
return (nm, e)
...
Тут код сразу описывает и грамматику, и построение типизированного AST из нее. Каждая строчка тут или успешно что-то парсит, продвигаясь вперед по тексту и порождая значения, или, если во входном тексте что-то другое, что эта строка не ожидает, прерывается и переходит к следующей альтернативе в выражениях с несколькими альтернативами (разделенными <|>), откатываясь назад в тексте, когда необходимо. Вся эта логика передачи управления туда-сюда и перемещения по входному тексту упрятана в монаду и do-нотацию, за счет чего код не перегружен механикой телодвижений, остается лишь существенная для задачи логика.
Здравствуйте, Shmj, Вы писали:
S>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
S>Бывает такое?
Бывает, но в haskel'е. Для того, чтоб монады были полезны на практике нужен high kinded polimorphism, которого в жаба-шарпах нету
S>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
S>Бывает такое?
S>>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
S>>Бывает такое?
J>Бывает, но в haskel'е. Для того, чтоб монады были полезны на практике нужен high kinded polimorphism, которого в жаба-шарпах нету
а можно как-то поподробнее и "на пальцах"?
допустим, в яве -шарпах, мне не нравится такой код
if (a != null)
{
if (a.Prop1 != null)
{
}
}
с помощью Maybe / Option / Either- монад его можно переписать во что-то более удобочитаемое,
а о чём именно говоришь ты?
Здравствуйте, takTak, Вы писали:
S>>>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
S>>>Бывает такое?
J>>Бывает, но в haskel'е. Для того, чтоб монады были полезны на практике нужен high kinded polimorphism, которого в жаба-шарпах нету
T>а можно как-то поподробнее и "на пальцах"?
T>допустим, в яве -шарпах, мне не нравится такой код
T>
T>if (a != null)
T> {
T> if (a.Prop1 != null)
T> {
T> }
T> }
T>
T>с помощью Maybe / Option / Either- монад его можно переписать во что-то более удобочитаемое, T>а о чём именно говоришь ты?
FileStream fileStream = new FileStream(...)
fileStream.WriteString("BlaBla")
как тут поможет обычный полиморфизм ?? да никак. Но он поможет если ты хочешь писать строки и в файл и в сеть и в память.
Так и с монадами. Ну ясно что у тя тут монада Maybe(nullable) — но для упрощения этого кода само понятие монад не нужно, нужно тупо захардкорить в язык операции над nullable (как это сделали в шарпе) и получить a?.Prop?.MyMethod();
А если ты хочешь написать код работающий с любыми монадами — вот тут становится интереснее
Вот тебе пример на псевдо шарпе, который умеет:
1) хай кайндед полимофизм.
2) умеет неявно lift'идь операторы для любого монадического типа, а не только для Nullable<> как сейчас. Тo есть M<int> a, b; a + b — преобразует в a.bind(x => b.bind(y => new M<int>(x + y)))
Теоретически я бы мог использовать query syntax, который суть — аналог do натации в haskel'е, но это сильно раздуло бы код, скрывая смысл.
M<int> Average<M<_>>(IEnumerable<M<int>> ints) {
var sum = new M(0);
var count = 0;
foreach(var v in ints) {
sum = sum + v;
count++;
}
return count > ? (sum / count) : new M<int>(0);
}
Думаю понятно что эта функция делает.
теперь попробует её использовать:
IEnumerable<int> items = ....;
// Identity - то монада Identity. Хотя опять же для удобства иcпользования можно было бы считать, что любой тип сам по себе является монадой Identity, чтоб не делать подобных приседаний
Console.WriteLine(Average(items.Select(x => new Identity(x))));
IEnumerable<Nullable<int>> items2 = File.ReadAllLines("1.txt").Select(TryParseInt); // TryParseInt имеет сигнатуру int? TryParseInt(string s);
Console.WriteLine(Average(items2)); // если какая то строка не является числом - получится null
Здравствуйте, Shmj, Вы писали:
S>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода? S>Бывает такое?
Бывает:
List, Parser, Future, Maybe, один раз получилась error-монада
Ну и если цацкель, то там IO-монада, без нее никуда.
J>А если ты хочешь написать код работающий с любыми монадами — вот тут становится интереснее
J>Вот тебе пример на псевдо шарпе, который умеет: J>1) хай кайндед полимофизм. J>2) умеет неявно lift'идь операторы для любого монадического типа, а не только для Nullable<> как сейчас. Тo есть M<int> a, b; a + b — преобразует в a.bind(x => b.bind(y => new M<int>(x + y))) J> Теоретически я бы мог использовать query syntax, который суть — аналог do натации в haskel'е, но это сильно раздуло бы код, скрывая смысл.
J>
J>M<int> Average<M<_>>(IEnumerable<M<int>> ints) {
J> var sum = new M(0);
J> var count = 0;
J> foreach(var v in ints) {
J> sum = sum + v;
J> count++;
J> }
J> return count > ? (sum / count) : new M<int>(0);
J>}
J>
J>Думаю понятно что эта функция делает.
J>теперь попробует её использовать: J>
J>IEnumerable<int> items = ....;
J>// Identity - то монада Identity. Хотя опять же для удобства иcпользования можно было бы считать, что любой тип сам по себе является монадой Identity, чтоб не делать подобных приседаний
J>Console.WriteLine(Average(items.Select(x => new Identity(x))));
J>
про монаду Identity я совсем не понял, зачем она вообще?
lift'идь — это перевести к монадическому виду?
т.е. польза от "хай кайндед полимофизм" в том, что нужно меньше проверки типов или что-то другое?
T>про монаду Identity я совсем не понял, зачем она вообще?
Ну монада — это что то типа интерфейса c методом bind, а Identity — реализация этого интерфейса. Я там выше написал во что транслируется код a + b. Если a имеет тип int, то такая трансформация невозможно, потому что у типа int нету метода bind. Значит нам нужна обертка с таким методом. Я написал её явно, но потенциально за меня её мог бы сгенерить компилятор.
T>lift'идь — это перевести к монадическому виду?
lift'инг операторов в c# — это возможность использовать операторы с типом Nullable<T>, если эти операторы определены для типа Т.
То есть так как для типа int определён оператор + , то я могу написать Nullable<int> x, y; x + y.
А у типа struct A {} оператора + нету, поэтому Nullable<A> x, y; x + y — не скомпилируется. Выше я пожелал чтоб операторы лифтились не только для NUllable, а для любого монадического типа
T>т.е. польза от "хай кайндед полимофизм" в том, что нужно меньше проверки типов или что-то другое?
Польза в том же, что и польза от обычных дженериков, но немного на другом уровне. Эта концепция вообще говоря к монадам отнашения не имеет. ТО есть может существовать и приносить пользу без них, а вот монадам без HKP(high kinded polimorphism) — туго.
Вернемся к примеру со средним. Немного изменю его
M<double> Average<M<_>>(IEnumerable<M<int>> ints) {
var sum = new M<double>(0.0);
var count = 0;
foreach(var v in ints) {
sum = sum + v;
count++;
}
return count > ? (sum / count) : new M<double>(0);
}
то есть если я передал список Nullable<int> то на выходе должен получить Nullable<double> . Если на входе список из Task<int> то на выходе Task<double> должен быть. Обычными дженериками такого не добьёшься. А вот шаблонами из С++ наcколько я понимаю можно.
Вот этот момент, то что я дженериком аргументом передаю не конкретный тип (ну например Nullable<int>), а open generic type — вот это и есть полимофизм высшего порядка
Здравствуйте, Shmj, Вы писали:
S>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
S>Бывает такое?
Я активно использую монады в Scala.
Это математическая абстракция и её сложно понять без программирования, примерно как сложно понять концепцию функций высшего порядка, пока не скажешь, что forEach() — это функция высшего порядка.
Монады — они везде. Например, джавовые Optional и CompletableFuture — монады. скаловские Option, Either, Future — монады. cats IO, monix Task, scalaz ZIO — тоже монады.
Здравствуйте, takTak, Вы писали:
T>т.е. польза от "хай кайндед полимофизм" в том, что нужно меньше проверки типов или что-то другое?
Польза в том, что можно писать обобщенный код, работающий с разными "контейнерами", т.е. абстрагироваться не только от типа элементов (как обычно в языках с генериками), но и от обобщенных типов контейнеров, получается дважды генерик — генерик, у которого параметр сам генерик. Или языком С++ шаблон, у которого есть параметр-шаблон.
Например, я пишу такую функцию:
> pair a b = return (a,b)
Ее тип автоматически выводится такой:
pair :: Monad m => a -> b -> m (a, b)
Она берет на вход два значения типов a и b, делает из них тупл (a, b) и заворачивает его в монаду ("контейнер") m.
А вот какую именно — тут я пока не указываю, я могу передать тип "контейнера" позже, при вызове. Так я могу получить список с туплом в виде единственного элемента
> pair 2 3 :: [(Int, Int)]
[(2,3)]
(после :: указывается тип, квадратные скобки означают список)
А могу опциональное значение Just с туплом внутри.
> pair 2 False :: Maybe (Int, Bool)
Just (2,False)
А могу и вовсе функцию из чего-то третьего в такой тупл:
Т.е. в зависимости от переданных типов аргументов и типа "контейнера", моя функция строит очень разные вещи, она опирается на ф-ю return из интерфейса переданной монады чтобы построить завернутое значение.
Теперь, что, если я не хочу никакой особый контейнер, а хочу вызвать мою ф-ю pair и получить тупл без заворачивания его в список, мэйби или еще что-нибудь хитрое? Мне надо передать какую-то монаду, но она не должна ничего особенного делать, просто содержать значение как есть. Это и есть монада Identity. Аналог struct Identity<T> { T value; }
T>>т.е. польза от "хай кайндед полимофизм" в том, что нужно меньше проверки типов или что-то другое?
DM>Польза в том, что можно писать обобщенный код, работающий с разными "контейнерами", т.е. абстрагироваться не только от типа элементов (как обычно в языках с генериками), но и от обобщенных типов контейнеров, получается дважды генерик — генерик, у которого параметр сам генерик. Или языком С++ шаблон, у которого есть параметр-шаблон.
ок, где-то на интуитивном уровне я это понимаю, но я до сих пор не могу понять другого: читается ли вообще написанный таким образом код? Ведь если я насoздaвал кучу типов для того, чтоб различать между ними, то это,наверное, из-за того, что я структурирую модель определённым образом, если же есть конструкт, который работает с любыми контейнерами одинаково (кстати, что там с проверкой типов при HKP), то как такой код вообще кто-то может позже понять ? приведённые примеры понятны, но это как бы на уровне "хеллоу вёд", как это всё выглядит в промышленном масштабе или даже если ты сам смотришь на что-то написанное тобой пару месяцев назад? есть такой опыт?
DM>Теперь, что, если я не хочу никакой особый контейнер, а хочу вызвать мою ф-ю pair и получить тупл без заворачивания его в список, мэйби или еще что-нибудь хитрое? Мне надо передать какую-то монаду, но она не должна ничего особенного делать, просто содержать значение как есть. Это и есть монада Identity. Аналог struct Identity<T> { T value; }
т.е. Identity просто заворачивает значение так, что с полученным конейнером можно работать привычными функциональными способами типа map, bind etc?
T>ок, где-то на интуитивном уровне я это понимаю, но я до сих пор не могу понять другого: читается ли вообще написанный таким образом код? Ведь если я насoздaвал кучу типов для того, чтоб различать между ними, то это,наверное, из-за того, что я структурирую модель определённым образом, если же есть конструкт, который работает с любыми контейнерами одинаково (кстати, что там с проверкой типов при HKP), то как такой код вообще кто-то может позже понять ? приведённые примеры понятны, но это как бы на уровне "хеллоу вёд", как это всё выглядит в промышленном масштабе или даже если ты сам смотришь на что-то написанное тобой пару месяцев назад? есть такой опыт?
Дело привычки. Поначалу может быть сложно, потом становится обычным делом. Чаще всего такой обобщенный код возникает там, где надо принимать функцию, и хочется разрешить не только чистые ф-ии, но и с эффектами. А какие там эффекты заранее не знаешь, но они как раз монадами кодируются.
Скажем, есть у нас функция выбора подпоследовательности из списка по заданному предикату:
filter :: (a -> Bool) -> [a] -> [a]
А мы хотим, чтобы наш предикат не просто возвращал Bool, а еще писал что-то в лог, или считал какую-то статистику. Такая функция-предикат для интов может иметь тип
Int -> IO Bool
или
Int -> SomeState Bool
или еще какой эффект иметь. Нам пофиг какой, если этот эффект вписывается в интерфейс монады. Тогда делаем обобщенную ф-ию
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
которая умеет применять предикат с эффектом, и сама тоже этим эффектом становится заражена.
Такие ф-ии обычно небольшие и несложные, вполне можно разобраться.
Другой частый вариант абстрагирования от монад — когда нужно объединить несколько эффектов. Для этого можно параметром монады с одним эффектом сделать другую монаду (заранее не знаем какую), которая будет описывать другие эффекты. Тогда их можно сложить в такой слоеный пирог. Это монадные трансформеры. К ним тоже быстро привыкаешь.
T>т.е. Identity просто заворачивает значение так, что с полученным конейнером можно работать привычными функциональными способами типа map, bind etc?
Да, просто содержит какое-то значение без изменений, при этом реализуя все необходимые интерфейсы "контейнера"/"эффекта".
DM>Такие ф-ии обычно небольшие и несложные, вполне можно разобраться.
DM>Другой частый вариант абстрагирования от монад — когда нужно объединить несколько эффектов. Для этого можно параметром монады с одним эффектом сделать другую монаду (заранее не знаем какую), которая будет описывать другие эффекты. Тогда их можно сложить в такой слоеный пирог. Это монадные трансформеры. К ним тоже быстро привыкаешь.
если несложные, то в чём профит? ведь можно было бы просто обернуть несколько вспомогательных функций вокруг основной, то и эффект был бы тот тоже, или нет?
а что за сфера-то, если не секрет, где ты хаскель в коммерческом плане используешь, если я , конечно, правильно понял ?
Здравствуйте, takTak, Вы писали:
T>если несложные, то в чём профит? ведь можно было бы просто обернуть несколько вспомогательных функций вокруг основной, то и эффект был бы тот тоже, или нет?
Если исходная ф-я (как filter выше) чистая, то передать в нее свои грязные функции просто не выйдет, как ни заворачивай. Потому и делают в библиотеках в дополнение к чистым комбинатором еще такие, что работают с произвольными монадами, чтобы люди могли свои ф-ии с эффектами тоже там использовать. Можно, конечно, каждый раз заново все реализовать для каждого типа, но это какой-то Go получится. В ФП такое не любят.
T>а что за сфера-то, если не секрет, где ты хаскель в коммерческом плане используешь, если я , конечно, правильно понял ?
Это у меня сейчас исследовательский проектик по выводу типов для языка
Здравствуйте, Shmj, Вы писали:
S>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
S>Бывает такое?
Сначала я думал, что монады — это ругательство.
Потом я научился Scala-е, а уже после узнал, что я использую монады и это не ругательство
Здравствуйте, Jack128, Вы писали:
J>Бывает, но в haskel'е. Для того, чтоб монады были полезны на практике нужен high kinded polimorphism, которого в жаба-шарпах нету
Кое-что есть в достаточной степени, чтобы кое-как кривенько поддерживать монады.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Shmj, Вы писали:
S>Могли бы вы привести пример из жизни, где вы использовали монады не ради академической науки а для облегчения написания/поддержки кода?
Без полноценной языковой поддержки монады бесполезны.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, Jack128, Вы писали:
J>>Бывает, но в haskel'е. Для того, чтоб монады были полезны на практике нужен high kinded polimorphism, которого в жаба-шарпах нету
IT>Кое-что есть в достаточной степени, чтобы кое-как кривенько поддерживать монады.
Ну до появления async/await я написал экстеншн методы для Task, чтоб эти таски можно было в query syntax использовать
Получилось весело:
var _1 =
from _2 in DoBlaBlaAsync()
from _3 in DoOtherBlaBlaAsync()
from x in GetBlaBlaAsync()
from _4 in DoWorkAsync(x)
select 1;
Здравствуйте, IT, Вы писали:
IT>Без полноценной языковой поддержки монады бесполезны.
Очень полезны. С языковой поддержкой их просто удобнее использовать.