On 2013-12-04 01:53:39 +0000, Shammah Chancellor said:
On 2013-12-03 23:49:47 +0000, Max Klyga said:
On 2013-12-03 23:02:13 +0000, Shammah Chancellor said:
On 2013-12-03 21:51:20 +0000, Max Klyga said:
On 2013-12-03 02:45:44 +0000, Shammah Chancellor said:
I'm not particularly familiar with the syntax being used in the variet
of monad examples. I'm trying to figure out how this is different
from UFCS on InputRanges. It seems like std.algorithm implements
something which accomplished the same thing, but much easier to
understand?
Can somebody maybe do a compare and contrast for me?
-Shammah
Monads and input ranges are different things. I'll try to briefly
explain monads. Hope this will not worsen the situation by being too
confusing.
InputRanges provide a generic way for iterating over something.
UFCS can be used to create a range interface on things that do not provide it.
Monads are an abstraction for composing things within some context
(concatenating lists, composing operations on nullable values,
composing asynchronous operations). That sounds a bit too general and
vague, because it is. One can think about as a design pattern.
Monad has two operations:
- make a monad out of a value
- apply a function that takes a value and returns a new monad of the
same kind to value inside a monad
second operation has a different meaning for different monad kinds but
generally it means 'execute this code within current context'
for nullable values this means 'execute only if there exist a value'
for asynchronous operations this means 'execute this when the value is ready'
This operation is commonly named 'bind' or 'flatMap'
Some languages provide syntax sugar for monads (Scala's for, Haskell's do)
Monads are easier to understand once you've seen enough examples of
things that are monads.
Suppose you have a list of movies and want to produce a list of names
of all actors stating in those movies.
In scala you would typically write something like this:
for (movie <- movies; actor <- movie.actors) yield actor.name
Compiler rewrites that to
movies.flatMap(movie => movie.actors).map(actor => actor.name)
^
---------- this function takes a list
element and returns a new list, effectively creating a list of lists
and then flattening it by concatenating all the lists into one, hence
the name 'flatMap'. It transforms and then flattens.
Another popular example for Monads are optional values (similar to
nullables but forcing you to check for presence of value and explicitly
avoiding null dereferencing)
A common pattern for working with optional values is returning null
from your function if your input is null
So if say we are parsing JSON and we want to process only values that
contain certain field, that in turn contains another field. Example in
pseudo-scala:
for (value <- json.get("value"); // type of value is Option(JsonNode)
meaning that actual json node might be absent
anotherValue <- value.get("another")) // this is executed only
if value is present
doSomethingFancy(anotherValue) // ditto
and again, compiler will rewrite this into
json.get("value").flatMap(value =>
value.get("another")).foreach(anotherValue =>
doSomethingFancy(anotherValue))
Once again we see that flat map is used. The pattern is same - get the
value out of the box, transform it to another box of the same kind in
the context meaningful for this particular box kind
So the main benefit is being able to compose things in a consistent
way. Once you grasp the whole idea its fun finding out that some thing
you've been doing can be viewed as a monad. People created quite a lot
of different monads to this date.
I get the gist of that, but it seems like the range concept with UFCS
provides the same thing? E.G. range.map().flatten().map()?
Does it really not accomplish the same thing -- am I missing some key
point of monads?
You look only at the syntax side of the question.
range.map(...).flatten.map(...) might look similar and it could be
possible to squeeze monads to work with this api, but the thing is that
not every monad could provide a meaningful map function and as a whole
calling flatten after every map is a bit tiresome.
That may work for some monads, like List, because its effectively a range.
It will also work for Maybe monad. It could be viewed as a range of 0
or 1 elements.
But things get bad when we try to define other monads in terms of range
interface.
Current map implementation by design doesn't know anything about range
it processes. If we try to define Promise monad as a range it will
practically be useless unless we provide a custom map implementation
for promises, because std.algorithm.map will return a wrapper range
that will call popFront() that will block and wait for the value but
that defeats the purpose entirely as we wanted the result to be mapped
asynchronously when it will be available and not block.
What about other monads? Defining IO or State monads as a range would
be just impossible
So input range can be viewed as a monad but not the other way around.
Each monad needs its own unique flatMap implementation
But then, these other monads could be defined in D using UFCS? Or is D
syntax not generic enough to define monads?
Yes, they could be defined with or without UFCS, it may just be a
little inconvinient to use without syntax sugar. Monads could be
defined in any language that has first class functions