@@danchatka8613 @Conando well... you have to be careful. If you chose the wrong tensor for your monoid in the category of endofunctors you end up with just an applicative and not a monad .. ;) I also had another good one .. but i can't get right anymore .. something like "Lenses are just profunctors over the Co-State (aka Store) CoMonad" or something..
Aside from all the kind words from others, which I support, in particular I want to compliment you on the sound quality. I wish everyone had their audio quality this good. I have a hearing problem so usually I have to rely somewhat on subtitles. Not here, so I'm very happy. Much thanks, and also for the high quality presentation.
On the beginning of the second part: in Rust there are libs for that problem such as 'anyhow' and others. I started using 'error-stack' recently, works pretty nice 😊
Surprised Lisp didn't come up. The central problem is that we really want a degree of control over how our code is compiled, so we sort of need a handle into the compiler. A monad is effectively a lightweight user defined language inside a prescribed language. While making these is pretty simple, making tooling that is able to combine them and manage them without any dedicated support from the underlying language will always be hell. Lisp is basically "write your own compiler", so will always be interesting for this sort of thing. As a side note; A list parser that takes a function as it's last element, and regards a semicolon as a non-consuming closing bracket is already quite interesting. Basically when you meet a semicolon you treat it as a closing bracket, but also backtrack and add another opening bracket when you hit what would be its partner. ( a; (b) ; c) would be equivalent to (((a ) (b)) c), as both semicolons track back to the bracket before the 'a'. The idea is that your program starts with an opening description of state, and each line describes a function on that state. The semicolon is partially being used to make it so the programmer doesn't have to keep track of how deep it goes, but it also lets the parser treat it as imperative code despite being pure functional. I have to say I fundamentally disagree with this on whether there should be a difference between 'map' and 'then'. When writing initial code I do not want to have to be thinking about corner cases, so I really don't want to be forced to think about whether a particular function can throw or not. Your solution later is better. I want to be able to query the functions and ask specific questions such as "do you throw", "do you allocate", "are you thread safe", "are you safe for user inputs". When I am looking over my code for thread safety, I don't want multiple keywords that only differ in terms of some other specific questions, as it is just noise. I want my language to support my IDE in telling me the details of what I have written, not test my memory of random details.
leave it to the guy who ~dislikes monads to give you the best description of what they are :D - Everyone else explaining them just seem excited they understood it. You don't truly understand something, until you see it's flaws and better alternatives, and are willing to take the full context in.
Such a great and interesting talk. when talking about algebraic effects I remembered the Flix programming language, which does have some of that, but only limited to pure unpure, at least the last time I looked. But the idea Is really cool
Great talk, I really enjoyed it! I have one comment - There is a caveat regarding the idea of "overloading semi-colons". In a strongly typed language, the "." operator on a type (or member functions, class functions... whatever really) requires a non-void type. To chain a statement, you're calling another "." operator on the returned type. The semi-colon delimiter does not necessarily have this constraint... for instance, you can follow up any semi-colon with a "goto" or "jump" statement in something like assembly or permitting languages. This is not a method called on a returned type. I'd say that the sentiment is half-right. It does overload the semi-colon operator given that the next statement is operating on the returned type from the previous statement. Anyhow, I'm nitpicking - this was a really good talk.
A mini comment about the audio. I feel the audio levels are not very consistent and go from quite soft to high to soft again. I'm not an audio expert, but I wonder if putting a compressor would help keeping a more balanced level.
Nice. Love the balance between what is and what could be. I'm not a fan of exceptions, so in those cases where I'd want to return a value anyway, just return a different value? Or run a different function? Not sure about this use-case.
In more practical way to explain monad, for a structure to satisfy monadic interface: - Value that's protected in some kind of wrapper. usually supported by function that convert value into wrapped context (lifting). - function that is able to operate on that value, typically called map (here called .then) - we need to have function to get that value from the wrapper, unwrap it. Typically called .fold, here call map. - and finally we need to be able to flatten nested monads, when map returns another monad, typically called flatMap. Typically you wan't this behaviour controlled, so you don't auto merging like .then do. What I particularly don't like in this talk is that speaker used somewhat unconventional naming, adopted my JS to explain monads. Particularly example of Promise, which is basically eager Task monad, with inverted executor arguments. You can definitely explain this structure without ever called it monad, then you just call it thing with A, B, C thingy. I think "functional bros" basically made everyone hate us, because of strange words that most people don't know what they mean, and we sound like an ashole for using them, infront of people who never heard about it. So I invite you that, maybe just use "functional patterns" instead, then explain in humane way what it is.
Very interesting talk, I'm glad I got to discover a few things with it! What's this language you mentioned at the very end? It sounded like "qbml" or "gbml", but neither me nor the youtube captions could quite catch that
26:36 Implicit generic sum types would probably help here. Example: fn domainFromEmailAddress(A) Result[B, DomainFromEmailAddressError] fn tldFromDomain(B) Result[C, TldFromDomainError] fn Result.then[Ok, Err](fn (Result.Ok) Result[Ok, Err] ) Result[Ok, Err | Result.Err] So when calling "Result.Ok(A).then(domainFromEmailAddress).then(tldFromDomain)" the return type would be "Result[C, DomainFromEmailAddressError | TldFromDomainError]" I wonder, if there already is a language, which allows that. Rust doesn't. Probably Haskell or OCaml.
A believe you could actually do something like that in TypeScript. TypeScript has some pretty impressive and flexible union and intersection type features - because it needed them to statically describe the sorts of things people would do implicitly in untyped JavaScript.
Really not a fan of the idea of "try->throw->catch-> try again inside the function that threw" idea near the end, that sounds like itd be extremely hard to reason through. Maybe resumes that affect the control flow of the thrower should explicitly be passed in as an argument and if they dont apply they go back up as normal and if they do they try the passed in thing
hmm... algebraic effects. I've been designing a language as a hobby for years (unlikely I will ever make it) and this is pretty similar to ideas I had ... (among many other on my wish list)
If people at least would TRY to explain what a Monad actually is. Surprisingly this short description here is more detailed than many multi-paragraph long dense descriptions. When you look up Monads on say Wikipedia you will be left wondering why anybody would ever voluntarily torture them self like that.
Well presented, well structured. Allow me one nit on your characterization of (modern) C++ in 7.4: "C++ avoids monads as much as possible" Nothing could be further from the truth. std::expected corresponds to Rust's (and your) Result type. Clearly you are not familiar with Eric Niebler's std::ranges library, std::future, Bartosz Milewski's work or Hana Dusikova's Graph Based Update System.
Please name algebraic effect libraries you know of, especially for main stream languages (ranked in Tiobe's top 30). Did you try any of them? How well did they work?
@@danchatka8613 I'm currently using Polysemy in production code, and Haskell is 39 on the Tiobe index. Personally, I only have experience using algebraic effects in Haskell. Tiobe is a weird metric for this because Scratch is there before Kotlin, Scala, Dart and Haskell… Still, within Tiobe's top 30, Scala (29) has effekt, Rust (20) has effing-mad, Ruby (16) has dry-effect and affect, Swift (15) has BowEffects, Javascript (7) has Effects.js and algebraic-effects, C# (5) has Eff, C(2) has libhandler and Python (1) has Eff.
@@PierreThierryKPH thank you for providing these starting points. tiobe has its fatal flaws. most languages i'd use are below 30 on the list. i'll look into Scala's effekt and Rust's effing-mad
@@danchatka8613 If you like algebraic effects, you may want to take a look at them in Haskell, because there, purity means the compiler can guarantee you that your code with effect X has no other effects. I'm pretty sure most other languages won't make that guarantee.
It's also super annoying that you have to write all the try catch and through stuff when you write some quick and dirty experiment / tool for yourself. Then it's also about using the tool/language construct only in the right cases. You should return error codes (something monadic) for common errors you handle directly. Exceptions should more be used for exceptional stuff you through up several layers and give some error message.
So a better definition of a Monad is: _"A wrapper that allows you to use a chain of functions (any of which could fail) as if there was no wrapper. If a failure happens, the remaining functions aren't executed and the failure can be resolved after the chain."_ *EDIT As others have pointed out below, this doesn't apply to all monads.* I like this definition because it tells you _why_ we use Monads, not just what their abstract definition is. All the properties of Monads are clear once you understand the purpose. It makes me realise that Message-Passing languages don't have this problem, because you can modify the receiving object to respond to failures. You don't have to change every calling function into a Try-Catch or an And-Then, or change the functions' type annotations. The object _is_ the Monad. I will say however - it's useful to know how a function could fail from looking at its type signature.
No, this definition is not correct. That's only for very specific data types like Optional or Result. Other monads do not have a concept of "failure" and have other purposes. For example, Reader which is used for some kind of dependency injection behaviour. Or something like "IO" which performs a side-effect like getting the current date.
Just to add to what others have replied: this video makes it sound like monads must include an "early return" functionality, but that's not a requirement. E.g., lists/arrays as monads via map and flatMap have no failure or early return functionality. It's all about chaining and state.
My mistake. You're right that the motivations for Reader and IO are very different to the 'safe chaining' in Option/Result To be honest, it's very unclear if there's any shared purpose between Reader/IO monads and Option/Result, only shared implementation. No one online can explain why they're grouped under the same concept. It's as if we met someone who has never seen a car, and explained that cars have 5 wheels.. but never that 4 wheels are for rolling and 1 wheel is for steering. It's not important that the wheels have the same shape and structure and name, their _purpose_ is more crucial. If Option/IO/Reader were invented today for the first time, would we group them under the same umbrella?
@@LowestofheDead Yes, they serve very different purposes. And that is precisely why the term Monad isn't used that much outside of the pure functional world - every use case of Monads gets its own name. Also, languages like Haskell like to name things based on their structure rather than their purpose. There is an advantage to calling Monads by their name though, you can write code that operates on all Monads. To give a comparison, in C you can't implement a generic list. So every time you need to use a list of something, it get it's own special implementation. That makes it usually very explicit what the list is used for and how. It works just fine. But most people who have programmed in a language with generics are really annoyed when they have to go back to C and just want to create a list of integers, or whatever. It's the same operations every time, so who wants to implement them again. Also, in C, whenever you a new see list, you need to "relearn" it's interface. If you get an Vec in Rust, you immediately know what to with it. Similarly, some one can write things like monad transformers or special operators that work on all monads, and once you learned those you can use them everywhere. Now, I don't know whether languages "should" group all Monads together. Maybe it makes sense for some, but not for others. But hopefully this gives you a better idea why some people like to talk about Monads. :)
are promises monads? let's check; a monad needs: to be a functor: a function m of form `Type -> Type` Int is a type, Promise Int is a type, it actually works for any input type ---> ok a function of form `(a -> b) -> m a -> m b` (often called 'map') do_when_information_becomes_available is an example of such function ---> ok to be a monad, it further needs: a function of type `a -> m a` (often called 'unit' or 'return') that preserves the wrapped value async, thread, go,... depending on the language may be examples of such function ---> ok a function of type `m a -> (a -> m b) -> m b ` (often called '>>=' or 'bind') that sequences calculations compose_async_when_information_becomes_available is an example of such function ---> ok so why would a promise not be a monad?
@@yjlom In JavaScript, the type of [ [ [ 42 ] ] ] is Array, not Array. Meanwhile, the type of Promise.resolve( Promise.resolve( Promise.resolve( 42 ) ) ) is not Promise, but Promise.
@@yjlom In JS 'then' is overloaded, in that it behaves like both map and flatMap. I think they are trying to separate them though, but it's hard because so much code already exploits this overloading.
This is the most understandable explanation of Monads I ever heard.
Well then you must not have heard that monads are monoids in the category of endofunctors
@@conando025 Keep your demonic incantations far from me! ;-)
@@danchatka8613 @Conando well... you have to be careful. If you chose the wrong tensor for your monoid in the category of endofunctors you end up with just an applicative and not a monad .. ;)
I also had another good one .. but i can't get right anymore .. something like "Lenses are just profunctors over the Co-State (aka Store) CoMonad" or something..
Aside from all the kind words from others, which I support, in particular I want to compliment you on the sound quality. I wish everyone had their audio quality this good. I have a hearing problem so usually I have to rely somewhat on subtitles. Not here, so I'm very happy. Much thanks, and also for the high quality presentation.
On the beginning of the second part: in Rust there are libs for that problem such as 'anyhow' and others.
I started using 'error-stack' recently, works pretty nice 😊
You did a great job motivating the idea behind algebraic effects. Thanks!
Good talk on explaining the fundamentals of monads. You're a good presenter!
Surprised Lisp didn't come up. The central problem is that we really want a degree of control over how our code is compiled, so we sort of need a handle into the compiler. A monad is effectively a lightweight user defined language inside a prescribed language. While making these is pretty simple, making tooling that is able to combine them and manage them without any dedicated support from the underlying language will always be hell. Lisp is basically "write your own compiler", so will always be interesting for this sort of thing.
As a side note; A list parser that takes a function as it's last element, and regards a semicolon as a non-consuming closing bracket is already quite interesting. Basically when you meet a semicolon you treat it as a closing bracket, but also backtrack and add another opening bracket when you hit what would be its partner. ( a; (b) ; c) would be equivalent to (((a ) (b)) c), as both semicolons track back to the bracket before the 'a'. The idea is that your program starts with an opening description of state, and each line describes a function on that state. The semicolon is partially being used to make it so the programmer doesn't have to keep track of how deep it goes, but it also lets the parser treat it as imperative code despite being pure functional.
I have to say I fundamentally disagree with this on whether there should be a difference between 'map' and 'then'. When writing initial code I do not want to have to be thinking about corner cases, so I really don't want to be forced to think about whether a particular function can throw or not. Your solution later is better. I want to be able to query the functions and ask specific questions such as "do you throw", "do you allocate", "are you thread safe", "are you safe for user inputs". When I am looking over my code for thread safety, I don't want multiple keywords that only differ in terms of some other specific questions, as it is just noise. I want my language to support my IDE in telling me the details of what I have written, not test my memory of random details.
Moreover, surprised that CommonLisp's Condition system wasn't mentioned when he talked about continuations (around 44 min mark)
What is the presentation software being used here? It's pretty nice and so easy to accompany
Awesome presentation! There's so much to learn in technology, I love it.
leave it to the guy who ~dislikes monads to give you the best description of what they are :D
- Everyone else explaining them just seem excited they understood it. You don't truly understand something, until you see it's flaws and better alternatives, and are willing to take the full context in.
Very underrated comment. You cannot understand something until you see its flaws. Brilliant.
BROTHER, YOU ARE THE BEST!!! You oooh really helped me!! THANK YOU VERY MUCH!
best content around this topic! well exposed and very interesting, thank you!
Such a great and interesting talk. when talking about algebraic effects I remembered the Flix programming language, which does have some of that, but only limited to pure unpure, at least the last time I looked. But the idea Is really cool
Great talk, I really enjoyed it! I have one comment -
There is a caveat regarding the idea of "overloading semi-colons". In a strongly typed language, the "." operator on a type (or member functions, class functions... whatever really) requires a non-void type. To chain a statement, you're calling another "." operator on the returned type. The semi-colon delimiter does not necessarily have this constraint... for instance, you can follow up any semi-colon with a "goto" or "jump" statement in something like assembly or permitting languages. This is not a method called on a returned type.
I'd say that the sentiment is half-right. It does overload the semi-colon operator given that the next statement is operating on the returned type from the previous statement.
Anyhow, I'm nitpicking - this was a really good talk.
A mini comment about the audio. I feel the audio levels are not very consistent and go from quite soft to high to soft again. I'm not an audio expert, but I wonder if putting a compressor would help keeping a more balanced level.
Nice. Love the balance between what is and what could be.
I'm not a fan of exceptions, so in those cases where I'd want to return a value anyway, just return a different value? Or run a different function? Not sure about this use-case.
In more practical way to explain monad, for a structure to satisfy monadic interface:
- Value that's protected in some kind of wrapper. usually supported by function that convert value into wrapped context (lifting).
- function that is able to operate on that value, typically called map (here called .then)
- we need to have function to get that value from the wrapper, unwrap it. Typically called .fold, here call map.
- and finally we need to be able to flatten nested monads, when map returns another monad, typically called flatMap. Typically you wan't this behaviour controlled, so you don't auto merging like .then do.
What I particularly don't like in this talk is that speaker used somewhat unconventional naming, adopted my JS to explain monads. Particularly example of Promise, which is basically eager Task monad, with inverted executor arguments.
You can definitely explain this structure without ever called it monad, then you just call it thing with A, B, C thingy. I think "functional bros" basically made everyone hate us, because of strange words that most people don't know what they mean, and we sound like an ashole for using them, infront of people who never heard about it.
So I invite you that, maybe just use "functional patterns" instead, then explain in humane way what it is.
Very interesting talk, I'm glad I got to discover a few things with it!
What's this language you mentioned at the very end? It sounded like "qbml" or "gbml", but neither me nor the youtube captions could quite catch that
Cubiml
26:36 Implicit generic sum types would probably help here.
Example:
fn domainFromEmailAddress(A) Result[B, DomainFromEmailAddressError]
fn tldFromDomain(B) Result[C, TldFromDomainError]
fn Result.then[Ok, Err](fn (Result.Ok) Result[Ok, Err] ) Result[Ok, Err | Result.Err]
So when calling "Result.Ok(A).then(domainFromEmailAddress).then(tldFromDomain)" the return type would be "Result[C, DomainFromEmailAddressError | TldFromDomainError]"
I wonder, if there already is a language, which allows that. Rust doesn't. Probably Haskell or OCaml.
A believe you could actually do something like that in TypeScript. TypeScript has some pretty impressive and flexible union and intersection type features - because it needed them to statically describe the sorts of things people would do implicitly in untyped JavaScript.
Presentation starts at 2:15
Really not a fan of the idea of "try->throw->catch-> try again inside the function that threw" idea near the end, that sounds like itd be extremely hard to reason through. Maybe resumes that affect the control flow of the thrower should explicitly be passed in as an argument and if they dont apply they go back up as normal and if they do they try the passed in thing
hmm... algebraic effects. I've been designing a language as a hobby for years (unlikely I will ever make it) and this is pretty similar to ideas I had ... (among many other on my wish list)
Great presentation! ❤
35:00 effect wish list sounds a bit like what zig does with the explicit allocator passing, and try keyword.
If people at least would TRY to explain what a Monad actually is. Surprisingly this short description here is more detailed than many multi-paragraph long dense descriptions. When you look up Monads on say Wikipedia you will be left wondering why anybody would ever voluntarily torture them self like that.
"13.5. Allow continuing" - this is precisely what Lisp does with its "conditions".
lol. I'm about to be that guy.... 6:43 there is a syntax error... my eyes started bleeding immediately lol.
Well presented, well structured. Allow me one nit on your characterization of (modern) C++ in 7.4: "C++ avoids monads as much as possible" Nothing could be further from the truth. std::expected corresponds to Rust's (and your) Result type. Clearly you are not familiar with Eric Niebler's std::ranges library, std::future, Bartosz Milewski's work or Hana Dusikova's Graph Based Update System.
Maybe "good" and "bad" are concepts that apply to humans but not to machines?
I wonder why he didn't mention algebraic effect libraries, there are already several of them just in Haskell.
Please name algebraic effect libraries you know of, especially for main stream languages (ranked in Tiobe's top 30).
Did you try any of them? How well did they work?
@@danchatka8613 I'm currently using Polysemy in production code, and Haskell is 39 on the Tiobe index. Personally, I only have experience using algebraic effects in Haskell. Tiobe is a weird metric for this because Scratch is there before Kotlin, Scala, Dart and Haskell… Still, within Tiobe's top 30, Scala (29) has effekt, Rust (20) has effing-mad, Ruby (16) has dry-effect and affect, Swift (15) has BowEffects, Javascript (7) has Effects.js and algebraic-effects, C# (5) has Eff, C(2) has libhandler and Python (1) has Eff.
@@PierreThierryKPH thank you for providing these starting points.
tiobe has its fatal flaws. most languages i'd use are below 30 on the list.
i'll look into Scala's effekt and Rust's effing-mad
@@PierreThierryKPH Thanks a lot for sharing this!
@@danchatka8613 If you like algebraic effects, you may want to take a look at them in Haskell, because there, purity means the compiler can guarantee you that your code with effect X has no other effects. I'm pretty sure most other languages won't make that guarantee.
Does Olle ever attend this?
It's funny that by far the closest system we have to what he desires is java, the only downside being 'its annoying to write down all the exceptions".
It's also super annoying that you have to write all the try catch and through stuff when you write some quick and dirty experiment / tool for yourself. Then it's also about using the tool/language construct only in the right cases. You should return error codes (something monadic) for common errors you handle directly. Exceptions should more be used for exceptional stuff you through up several layers and give some error message.
I think roclang does support 13.4 in the wishlist
I thought this was going to be about the Monadology by Leibniz, is this something to do with code it seems, hmmm
Functional programming is to normal programming what Jazz is to Rock. Change my mind.
making delimited continuations more mainstream
You almost lost me at "Java Exceptions are bad" - but I'm glad I sticked with you. Great talk, did learn a lot of new things!
He must see Roc lang
So a better definition of a Monad is: _"A wrapper that allows you to use a chain of functions (any of which could fail) as if there was no wrapper. If a failure happens, the remaining functions aren't executed and the failure can be resolved after the chain."_
*EDIT As others have pointed out below, this doesn't apply to all monads.*
I like this definition because it tells you _why_ we use Monads, not just what their abstract definition is. All the properties of Monads are clear once you understand the purpose.
It makes me realise that Message-Passing languages don't have this problem, because you can modify the receiving object to respond to failures.
You don't have to change every calling function into a Try-Catch or an And-Then, or change the functions' type annotations. The object _is_ the Monad.
I will say however - it's useful to know how a function could fail from looking at its type signature.
No, this definition is not correct. That's only for very specific data types like Optional or Result. Other monads do not have a concept of "failure" and have other purposes. For example, Reader which is used for some kind of dependency injection behaviour. Or something like "IO" which performs a side-effect like getting the current date.
Nice try though 🙂👍
Just to add to what others have replied: this video makes it sound like monads must include an "early return" functionality, but that's not a requirement. E.g., lists/arrays as monads via map and flatMap have no failure or early return functionality. It's all about chaining and state.
My mistake. You're right that the motivations for Reader and IO are very different to the 'safe chaining' in Option/Result
To be honest, it's very unclear if there's any shared purpose between Reader/IO monads and Option/Result, only shared implementation. No one online can explain why they're grouped under the same concept.
It's as if we met someone who has never seen a car, and explained that cars have 5 wheels.. but never that 4 wheels are for rolling and 1 wheel is for steering.
It's not important that the wheels have the same shape and structure and name, their _purpose_ is more crucial.
If Option/IO/Reader were invented today for the first time, would we group them under the same umbrella?
@@LowestofheDead Yes, they serve very different purposes. And that is precisely why the term Monad isn't used that much outside of the pure functional world - every use case of Monads gets its own name. Also, languages like Haskell like to name things based on their structure rather than their purpose.
There is an advantage to calling Monads by their name though, you can write code that operates on all Monads. To give a comparison, in C you can't implement a generic list. So every time you need to use a list of something, it get it's own special implementation. That makes it usually very explicit what the list is used for and how. It works just fine. But most people who have programmed in a language with generics are really annoyed when they have to go back to C and just want to create a list of integers, or whatever. It's the same operations every time, so who wants to implement them again. Also, in C, whenever you a new see list, you need to "relearn" it's interface. If you get an Vec in Rust, you immediately know what to with it.
Similarly, some one can write things like monad transformers or special operators that work on all monads, and once you learned those you can use them everywhere.
Now, I don't know whether languages "should" group all Monads together. Maybe it makes sense for some, but not for others. But hopefully this gives you a better idea why some people like to talk about Monads. :)
promises aren't monads
are promises monads? let's check; a monad needs:
to be a functor:
a function m of form `Type -> Type`
Int is a type, Promise Int is a type, it actually works for any input type ---> ok
a function of form `(a -> b) -> m a -> m b` (often called 'map')
do_when_information_becomes_available is an example of such function ---> ok
to be a monad, it further needs:
a function of type `a -> m a` (often called 'unit' or 'return') that preserves the wrapped value
async, thread, go,... depending on the language may be examples of such function ---> ok
a function of type `m a -> (a -> m b) -> m b ` (often called '>>=' or 'bind') that sequences calculations
compose_async_when_information_becomes_available is an example of such function ---> ok
so why would a promise not be a monad?
@@yjlom
In JavaScript, the type of [ [ [ 42 ] ] ] is Array, not Array.
Meanwhile, the type of Promise.resolve( Promise.resolve( Promise.resolve( 42 ) ) ) is not Promise, but Promise.
@@yjlom In JS 'then' is overloaded, in that it behaves like both map and flatMap. I think they are trying to separate them though, but it's hard because so much code already exploits this overloading.