3 ms·
The function is not the focus. The article presents three abstractions that allow you to apply regular functions on things to which they are not directly applic
by chousuke 13y ago
The function is not the focus. The article presents three abstractions that allow you to apply regular functions on things to which they are not directly applicable.
Maybe is usually used as an example since it's the simplest non-trivial monad (and therefore also an applicative functor).
The point of eg. fmap etc. is that you can have regular Int -> Bool functions, and apply them to data with more structure than simple values. a Maybe String is not a String, but you can still transform one to uppercase by doing fmap (map toUpper) mstring. The fun part is that that exact same thing works on a list of strings, an IO String (fmap (map toUpper) getLine), a Tree of Strings, etc... It's fully generic over anything that can implement Functor; the exact behaviour is defined by the particular instance.
Applicative is just an extension to the idea, where the function applied itself can have this extra structure, and when combined with an infix fmap you get to do
(+) <$> Just 2 <*> Nothing <*> Just 5 -- Nothing
(++) <$> getLine <*> getLine -- IO action that reads two lines and concats them
To see why this works has to do with currying. Just stare at the types of fmap and <* > long enough and you should see it eventually.
a Monad is yet more powerful because it allows a previous computation to affect what follows in an arbitrary manner (Applicative doesn't), allowing things like state, asynchronous execution, continuations, and countless other things. It's such an useful abstraction that Haskell provides the do syntax for using it.
Edit: In languages like Java where all types are nullable, if you uppercase a String, but that String is null, your program breaks, even though the type system claims that you have a String when in reality all types are Maybes, and you are forced to always check for nulls manually or just rely on users to not give you nulls, even though it would be trivial for the compiler to check something like that statically if the language were expressive enough to allow it.
- ams6110 13y agoSo if I understand correctly, the Maybe monad is analogous to SQL's handling of NULL. In SQL, I can invoke the UPPER function on a string, and if that string is a string I get the uppercase version, if it's a NULL I get a NULL in return. I get that, but every time I read about Monads (to the GP's point) it just seems like a very formal academic theory and I keep thinking "so what?" I have not done much with Haskell, maybe it's one of those concepts that seems more natural in practice than it does to try to read about it?
- steveklabnik 13y agoThe big difference is that it's statically computed. Rust also has a Maybe type as well. You use it like this: use core::io::{Reader,ReaderUtil}; use core::io::println; fn main() { let in = io::stdin().read_line(); println("INPUT:"); println(int::str(int::from_str(in))); } This code will produce an error, because `int::from_str` can fail. Imagine trying to convert a number from the string "foobar". $ make rustc option.rs option.rs.rs:125:23: 125:41 error: mismatched types: expected `int` but found `core::option::Option<int>` (expected int but found enum core::option::Option) option.rs:125 io::println(int::str(int::from_str(in))); ^~~~~~~~~~~~~~~~~~ error: aborting due to previous error make: *** [build] Error 101 What Rust is saying here is that we don't have the right types: `int::str` takes an int, but `int::from_str` returns an `Option<int>`. We can make the compiler satisfied by pattern matching over the return value: use core::io::{Reader,ReaderUtil}; use core::io::println; fn main() { let in = io::stdin().read_line(); match int::from_str(in) { Some(number_string) => println(int::to_str(number_string)), None => println("Hey, put in a number.") } } Now, we explicitly handle both cases (Some(x) being a success, and None being the failure), and the compiler is happy. This is where the advantages of Maybe come into play. In dynamic languages, you don't get the advantage of always making sure you handle the degenerate case. Now, what the Maybe _monad_ does is allow you to chain multiple things that work with Maybe together. I don't have a Rust example of this handy. Like "Do this, do that, do the other thing, and then let's see if that failed anywhere along the way."
- chousuke 13y agoThe reason why people talk so much about monads is that they are ubiquitous, and Haskell in particular can make good use of them because if type classes and other high level abstraction facilities, plus do notation. Asynchronous execution is one particular case that I like as an example: You can implement promises (which represent async computations) in Haskell and such a Promise type will also be able to implement the monad type class. Because they are monads, you can use do notation: -- Let's assume we want to fetch two large datasets asynchronously over the network and filter them asyncSanitize :: Promise [String] -> Promise [String] -> Promise [String] asyncSanitize p1 p2 = do x <- p1 y <- p2 return (sanitize (x ++ y)) -- no callbacks! which looks like the same code you would use for filtering two Maybe values using do notation. In fact, it is the same code, and if you leave out the type signature, then "asyncSanitize" will work for any monadic value: you could easily test your function using eg. Identity [String] (pure values), IO String (synchronous fetching), or decide to switch to some Database monad instead that fetches the values on demand from the Database. The code will not need to change at all.