![](/i/fill.gif) |
![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Am 20.04.2012 15:41, schrieb Invisible:
> One Haskell VFAQ is "What is a monad?"
> ...
Somehow I get the impression that people who have never come across the
term "monad" would be much better suited to explain the conceptual
idea... All I understand is bollocks, but something tells me the concept
is probably so trivial that a handful of well-chosen words could explain
it all, if they just avoided the technical blurb and "Haskellisms".
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
On 21/04/2012 03:18 PM, clipka wrote:
> All I understand is bollocks, but something tells me the concept
> is probably so trivial that a handful of well-chosen words could explain
> it all
When I write Haskell code "in a monad", the compiler is automatically
inserting a bunch of invisible function calls to the "bind function" of
the monad in question.
The bind function gets given two arguments: The result from the previous
statement, and a function representing /all/ of the following
statements. So it gets to decide if and how the rest of the code block
executes.
In short, between every pair of statements, a bunch of user-defined code
is invisibly being executed, allowing it to do totally hoopy things to
how your code runs.
And that's basically /it/.
(Now wasn't that such an anti-climax? Everybody makes such a Big Deal
out of monads, that sometimes when a person finally understands what
they are, it doesn't seem all that impressive, so they go away thinking
they missed something...)
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
clipka <ano### [at] anonymous org> wrote:
> All I understand is bollocks, but something tells me the concept
> is probably so trivial that a handful of well-chosen words could explain
> it all, if they just avoided the technical blurb and "Haskellisms".
Most of the mathematical background in Haskell and purely functional
languages reminds me of the theory of electronics: The people in the
know seem to consider many of the topics trivial and self-evident, but
for some reason are usually unable to explain them in an understandable
manner.
The difference is that in eletronics the concepts feel very complicated
when they try to explain them, while in Haskell they feel pretty trivial,
yet the real understanding of it always escapes the layman. For instance,
I still don't understand what currying *really* is about, and every time
I try to suggest "is like this?" the answer is "not really". It's like
some trivial-sounding but still ethereal concept that's impossible for
a mere mortal to grasp.
And then they wonder why functional programming isn't mainstream.
--
- Warp
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
On 21/04/2012 9:01 PM, Orchid Win7 v1 wrote:
> And that's basically /it/.
Well it tells me nothing.
--
Regards
Stephen
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
On 21/04/2012 09:56 PM, Stephen wrote:
> On 21/04/2012 9:01 PM, Orchid Win7 v1 wrote:
>> And that's basically /it/.
>
> Well it tells me nothing.
Heh, man... I thought "it inserts user-defined code between each pair of
statements" would a pretty simple idea. I guess not... ;-)
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
On 21/04/2012 09:24 PM, Warp wrote:
> The difference is that in eletronics the concepts feel very complicated
> when they try to explain them, while in Haskell they feel pretty trivial,
> yet the real understanding of it always escapes the layman.
Haskell is like Go: The rules are trivial. The game IS NOT.
As far as I know, the only way to really comprehend how to play Go is to
actually... you know... play Go. Only then do you start to understand
what the strategy and tactics are all about.
Haskell is similar. On the surface it looks pretty easy. The entire
language consists of a handful of simple yet very general concepts. The
way you APPLY those concepts to solve a problem? Well, that's an art. An
art you only really learn by doing. (ESPECIALLY since half this stuff
isn't written down anywhere, unfortunately.)
> For instance,
> I still don't understand what currying *really* is about, and every time
> I try to suggest "is like this?" the answer is "not really".
1. You can take a 5-argument function, pass it 2 argument, and get a
3-argument function as the result.
Examples:
- (+1) takes a number and adds one to it.
- (5 ==) takes a number and returns True if it's 5.
- "map" takes a function and a list. "map abs" takes a list of numbers
and returns a list of positive numbers.
2. Suppose foo is a 1-argument function. If you do "foo x y", then x is
the argument to foo itself, and y is the argument to whatever foo
returns (which must be a function).
Example: "head" takes a list and returns the first element. If you give
it a list OF FUNCTIONS, then obviously the result of calling "head" will
itself be another function. So it is legal to write
head function_list 7
It looks like we're calling "head" with 2 arguments, yet "head" is a
1-argument function. But what is /really/ happening is that
function_list is the argument to "head", and the 7 is then passed as an
argument to whatever "head" RETURNS.
(Notice how the result of calling "head" would not normally be a
function. There are lots of other examples of where a result wouldn't
usually be a function, but under sufficiently unusual circumstances it is.)
3. If you write a class which works one way for functions, and another
way for non-functions, then you can polymorphically handle functions
with ANY NUMBER OF ARGUMENTS. If it weren't for currying, you'd have to
write a separate version of the class for every size of function. And
that would violate DRY.
For example, QuickCheck allows you to turn any function into a "test",
provided that
- The function returns true/false values.
- All of the function's arguments support automated test data generation.
In particular, it works for 0-argument functions (i.e., constants),
unary functions, binary functions, ternary functions, etc. Basically
there's a class instance that says "this is how you test a Bool", and
another that says "this is how you test a function that takes one
argument and returns something testable". A Bool is testable, but so is
a 1-argument function. And, by induction, an N-argument function.
> And then they wonder why functional programming isn't mainstream.
Oh, I think that's due to a little bit more than just the ethereal
nature of advanced concepts. It's probably more "man, this stuff is too
different to what I'm used to; I can't be bothered with it". That and
(most especially in the case of Haskell) a supreme lack of quality
documentation.
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Just for giggles, what would monads look like in C++?
Well, let's pretend for a minute that C++ has a Function<X, Y> type,
which represents a function that takes an X as input, and produces a Y
as output. (Yes, I realise that isn't the real syntax C++ uses. Just
pretend, for a minute.)
Further, let's pretend that you can create an anonymous function just by
writing
int function(int x) {return 2*x;}
Again, this obviously doesn't actually work in C++, but it makes the
example code much easier to write.
(Actually, C++ is one of only a few languages where you probably /can/
implement monads "for real". But this made-up syntax makes it easier to
quickly explain what's happening.)
Listing #1:
Type1 x = foo();
Type2 y = bar(x);
Type3 z = baz(y);
return z;
This code needs little explanation. You can write the same thing in
Haskell as
Listing #2:
let
x = foo
y = bar x
z = baz y
in z
Now consider this seemingly identical code:
Listing #3:
do
x <- foo
y <- bar x
z <- baz y
return z
It seems like listing #2 and listing #3 should both be identical, right?
Well, no. If I translate listing #3 into C++, you'll see it's
drastically different:
Listing #4:
Box<Type1> box_x = foo();
return Bind(box_x,
Box<Type3> function(Type1 x)
{
Box<Type2> box_y = bar(x);
return Bind(box_y,
Box<Type3> function(Type2 y)
{
Box<Type3> box_z = baz(y);
return Bind(box_z,
Box<Type3> function(Type3 z) {return Return(z);}
);
}
);
}
);
Clearly, this /looks/ totally different. If you can actually /follow/ it
all, you'll see that every statement is now followed by a call to this
Bind() function, taking the rest of the code block as an argument.
Important things to notice:
* The return type of foo() has changed from Type1 to Box<Type1>. This
means that the implementation of foo() has to be changed.
* The first argument to Bind() is a Box<Type1>, but the input to its
second argument is a Type1.
The Box<> class "forms a monad" if and only if it provides the following
functions:
template <typename X>
Box<X> Return(X);
template <typename X, typename Y>
Box<Y> Bind(Box<X>, function<X, Box<Y>>);
(In the interests of good OO design, these should probably be /class
methods/ rather than stand-alone functions. After all, their
implementation is completely different for each class that behaves as a
monad. But I will keep them as normal functions for now...)
Notice how what the Return() function does is NOTHING LIKE what a return
statement usually means. (!) Arguably this is a rather bad naming
choice. [You'll also notice how, in this example, Return() is completely
redundant. Taking it out wouldn't change a thing. I only put it there
for completeness.]
It's not hard to figure out how to implement this:
template <typename X>
class Box
{
private:
X Datum;
public:
Box(X in) {Datum = in;}
X GetDatum() {return Datum;}
};
template <typename X>
Box<X> Return(X in) {return Box<X>(in);}
template <typename X, typename Y>
Box<Y> Bind(Box<X> in, Function<X, Box<Y>> next)
{return next(in.GetDatum());}
(I'm guessing my syntax is wonky - even admitting that we're using
pretend language extensions. It probably also contains at least a dozen
incorrect or at least dubious characteristics. But the gist of it should
be clear.)
This is a monad, but it doesn't "do" anything particularly special. With
Box implemented this way, Listing #4 now does exactly the same thing as
Listing #1, but 8x more complicated. Why bother?
This implementation makes Box the "identity monad". Like the identity
function, the identity monad does... nothing. (And, like the identity
function, this sounds like a very useless thing, but actually isn't.)
So let's try another implementation:
template <typename X>
class Box
{
private:
std::vector<X> Data;
public:
Box(std::vector<X> in) {Data = in;}
std::vector<X> GetData() {return Data;}
};
template <typename X>
Box<X> Return(X in)
{
std::vector<X> out;
out.push_back(in);
return out;
}
template <typename X, typename Y>
Box<Y> Bind(Box<X> box_in, Function<X, Box<Y>> next)
{
std::vector<X> in = box_in.GetData();
std::vector<Y> out, temp;
std::vector<X>::iterator iti;
std::vector<Y>::iterator ito;
for (iti = in.begin(); iti < in.end(); iti++)
{
Box<Y> box_out = next(*it);
temp = box_out.GetData();
for (ito = temp.begin(); ito < temp.end(); ito++)
out.push_back(ito*);
}
return Box<Y>(out);
}
Now Box<X> contains a std::vector<X> instead of just one X value. The
Return() function now generates a 1-element vector. And the Bind()
function now executes next() MULTIPLE TIMES, once for each element in
the input. It then collects all the outputs from each call, and gathers
them into a single giant vector.
(Incidentally, I couldn't figure out a direct way to just append the
entire contents of one vector to another. Obviously there /is/ a way,
and I'm just not looking hard enough...)
Notice how the code for foo(), bar() and baz() HAS NOT CHANGED, and the
code that calls them HAS NOT CHANGED. And yet, now each function can
potentially generate multiple results!
(Of course, for foo() to produce more than one output given a single
input, then either foo() itself or some function it calls would have to
be modified to actually make USE of this feature. But, for example, some
function that foo() calls might change, while foo() itself remains
unaffected, and foo() could still use this new feature of having
multiple results.)
This is what Haskellers call the "list monad", basically. (With the
added twist that Haskell lists are lazy, so it's possible for functions
to produce "infinity results".)
It shouldn't be too hard to see that you could alter [the first] Box
implementation such that it contains a success marker, and that Bind()
only runs the next step in the chain if the previous one succeeded. You
could also modify it to contain an error description in the case of
failure. Then, if bar() decides to return an error-in-a-Box rather than
a value-in-a-Box, baz() gets completely skipped. But baz() ITSELF
doesn't need to do any explicit error checking. Bind() does that for you.
In this way, baz() returning an error-in-a-Box aborts the entire Box
computation. A bit like... THROWING AN EXCEPTION. Except it's a library,
not a language feature, so you can change the design of it at will.
(E.g., make it so you have to throw three exceptions before the code
aborts.) This is what Haskellers call the "failure monad", "error monad"
or "exception monad".
You can also design Box to store various other auxiliary information as
it goes along. You could have a table of data, for example, and provide
commands to replace that table with a new one. This is how Haskell
simulates destructive updates without ACTUALLY doing destructive updates.
(This is the difference between the State monad and the ST monad. The
former allows you to simulate destructive updates, in the way described.
The latter ACTUALLY DOES destructive updates, in the manner of C++ or
Java or whatever.)
It's not hard to see how a Box could contain a string buffer and a
character index, and you could write functions that take a Box and
return a new Box after reading data from the string buffer. This is how
you write parser monads.
I could continue at length, but by now you probably get the picture.
User-defined code invisible inserted between the visible statements you
actually wrote leads to all sorts of interesting possibilities.
Incidentally, since C++ REALLY DOES have a function type (or at least,
function POINTER type) which can be passed as an argument, it's one of
the few languages where it ought to be possible to implement a monad
"for real". I might try it if I get the time.
The problem, of course, is that it's going to be so hellishly verbose
that no sane person would ever, EVER write their C++ code like this...
Saying that, I hear C++ lets you override operator(). So perhaps the
simplest way to describe a monad is as "override operator;". ;-)
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
Le 21/04/2012 23:37, Orchid Win7 v1 nous fit lire :
> "override operator;".
are you sure you do not want "operator," instead ?
For once, I'm glad C++ does not allow such!
The overloading (?) of << and >> is already enough trauma when dealing
with streams... or not.
c = ceo << eke ;
d = kaz << ekk ;
one is a shift operation, the other is write in a stream... can you say
which one ? (and either c is an integer or a stream...)
in fact, monade seems to be the << with polymorphic type added.
(for streams, << returns a reference to updated stream (sort of), so
cout << "hello " << "world" << endl;
is in fact ((cout << "hello") << "world ) << endl;
Now, if << is specified to return not the same reference type as the one
on the left, it can become "interesting".
But of course, then, we need a template to declare the << for the part
on right... or a lot of overloading.
I wonder if there is a deep difference between haskell & forth... or a
bit of lisp. Just that haskell dropped all ()...
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
On 21/04/2012 10:17 PM, Orchid Win7 v1 wrote:
> On 21/04/2012 09:56 PM, Stephen wrote:
>> On 21/04/2012 9:01 PM, Orchid Win7 v1 wrote:
>>> And that's basically /it/.
>>
>> Well it tells me nothing.
>
> Heh, man... I thought "it inserts user-defined code between each pair of
> statements" would a pretty simple idea. I guess not... ;-)
I should have said that I am none the wiser. I still do not know what a
monad is.
--
Regards
Stephen
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
On Sat, 21 Apr 2012 16:18:46 -0500, Orchid Win7 v1 <voi### [at] dev null> wrote
:
>
> 1. You can take a 5-argument function, pass it 2 argument, and get a
> 3-argument function as the result.
>
> Examples:
>
> - (+1) takes a number and adds one to it.
>
> - (5 ==) takes a number and returns True if it's 5.
>
> - "map" takes a function and a list. "map abs" takes a list of numbers
> and returns a list of positive numbers.
I think I understand monads—I'll likely never try Haskell to fin
d out for
sure.
I'm pretty sure I understand currying—at least as far as it goes
in Python.
But I don't understand how any of these are taking a 5 argument function
,
passing it 2 arguments, and getting a 3 argument function as a result.
-Shay
Post a reply to this message
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |
|
![](/i/fill.gif) |
| ![](/i/fill.gif) |
|
![](/i/fill.gif) |