++for the link. I've seen similar descriptions of monads as wrappers elsewhere but the nice thing about that article is that it gives an example of a purpose for wanting to wrap things. And it gives a nice analogy with a familiar concept--tainting--that bring that purpose home in a nice familiar way.
In turn, that has crystallised, for me, various feelings and intuitions about the process that I couldn't quite get a handle on before. And that leads to another analogy that allows me to express those intuitions, and relate them to Perl in a nice way.
It all stems from one sentence in the linked article:
But we've already said that we don't want to allow people to unwrap these things.
Perl already has a taint-mode. When enabled, it will taint 'at risk data' and ensure that whilst you can pass it around, you cannot use it in any way that would constitute a risk. And if you copy that tainted data, or amalgamate it with some other non-tainted data, the results are also tainted to ensure you cannot use those for at risk operations. And it does all this without requiring monads. It could probably be successfully argued that implementing tainting for Perl would have been much easier had it's source code (C) had a monad-like concept. But that's history, and perl exists and has tainting.
But there is one (huge) difference between the way it is implemented in perl, and the way it could be implemented using monads. Perl allows you/me/the programmer, to unwrap tainted data. It doesn't impose the language designers will upon me that tainted data must always and forever remain tainted--which in Haskell's terms is anything and everything that isn't hard-coded in the source file. And that is huge in it's implications for the ease of programming every further manipulation the program will perform upon that data.
The analogy
In certain high security buildings--airports, courts, government buildings, even private firms--visitors, including potential employees, customers and the like, are 'tagged' (a visitor badge), and 'wrapped' (a security guard, secretary or other escort) when they come on site. And they (should) remain so tagged and wrapped throughout the duration of their visit.
But, when a visitor makes the transition to becoming an employee, they loose the tagging and wrapping. Okay, they may trade one tag for another, but they cease to be escorted. And the process that facilitates that unwrapping is a (more or less thorough depending) vetting procedure. It is simply impractical to have every employee be escorted (by whom?) throughout their working day. So after vetting--to what ever level is commensurate with their position--they are trusted.
And that's what Perl does also. It allows me--as the user of the language, the writer of the application and the best arbiter of what constitutes 'safe data' for my application and what risk I will accept; to 'vet' the data and declare it to no longer be tainted. From that point on, I can treat that data in exactly the same way as I do any values that I embedded in my application, using all of the standard language constructs the language provides.
I don't need to have a duplicate set of control structures--fmap as well as map, forM as well as for, and so on. And I certainly don't need to have to have a mechanism for re-using those extraordinary (and duplicated) control structure for every type of data.
In effect, Haskell's need for all external data to remain wrapped for the rest of its working existence, is like requiring all your employees to continue to be escorted throughout their working days.
Haskell's need for these extraordinary control structures arises solely from the language designers decision that Haskell users (programmers) cannot be trusted to decide for themselves, when their data is safe to be manipulated using the standard language facilities.
They could have taken a different route(*). Then could have taken the decision to provided primitives that would validate in-bound data (bytes--all external data coming into a program is at the base level bytes strings), to be of some intrinsic type, or some compound type(*). From that point on, that validated data would be manipulable using the standard language facilities. The manipulations may still have had to be performed within sections of the code marked as being effectful and/or sequenced (the IO monad), but the need for many of the other facilities 'provided' by other monads would disappear.
*This is exactly what several other functional languages (eg.ML) have done...but then the language is no longer 'pure'.
There is probably some deeply meaningful reason embedded within the Hindley-Miller type system why it is impossible to trust a programmer to decide when external data is safe(ly typed), but that only makes me question the need for HM typing--not the efficacy of trusting the programmer to make his own decisions regarding the (type) safety of his data.
-
-
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] |
I'm looking really hard for any kind of analogy or figure of speech here, but I don't see it. As is, I can't see much sense in this comment. In case it was unclear, Haskell doesn't have a taint mode feature. This blog entry was about someone building one. They could have provided a way to unwrap the data, but they chose not to. Most monads do provide an unwrap operation; it's often called something like "runBlah".
So you don't like this person's specific choice for your intended purposes, and that's fine. It's quite expected, in fact, since it was just a friggin' example! If I told you I don't like Perl because Larry imposes his will on me that I use regular expressions for parsing XML, you'd probably tell me I'm an idiot, and rightfully so. This is the same thing.
If you don't want this monad (which you probably don't) you could write your own. If you don't like the monad, you could write some other kind of type operator besides a monad. Indeed, using a monad for tainted form data in a CGI doesn't seem like such a great idea. How about:
newtype Taint a = Taint { untaint :: a }
instance Functor (Taint a) where ...
The point is that it's a free world, and the language provides you with the tools to build this stuff on your own. No one has to implement taint mode for you. | [reply] |
S'funny thing, but since writing that, I just came across the smart and very helpful tmoertel's Type-based solution to the strings problem. It's a very interesting piece and a powerful argument in favour of strong, static typing. Almost...
Right up until the last section where he says:
To be clear, the fundamental problem of having to manage different kinds of strings is still with us. As programmers, we still must understand the differences between URLs, XML, SQL, untrusted user input, and so on. But now, we don’t have to be perfect. As long as we can reliably slap the right type on a string when it first appears, we can let the computer worry about it from then on....
At which point I though: You know, it isn't hard construct a few types in Perl.
package SafeText;
sub new { return bless \$_[1], $_[0] }
...
package SafeURL;
use Base 'SafeText';
sub forTextPlain { ... }
sub forHTML { ... }
...
package SafeXML;
use Base 'SafeText';
sub forTextPlain { ... }
sub forHTML { ... }
Of course, with this implementation the programmer can easily bypass the wrapping and grab the contents directly. But, if he does, he's taking responsibility for his actions. Also, there are other forms of OO, like Object::InsideOut that can be used that will prevent casual breaking of encapsulation.
The real difference of course is that there is nothing to prevent the programmer from calling the wrong method and sticking the TextPlain representation of a url where the HTML representation should have been used. There is no compile-time checking of what methods are called on an object, or in what context those methods are called.
The runtime errors will catch attempts to call non-existent methods. But they won't catch the use of existing methods in the wrong context. Interpolating a URL as plainText into a string of HTML for example. However, Dominus' Interpolation could be used to go some way to achieving that. It still relies on runtime though.
The real trick with Haskell is the Monad. It won't let us take the special strings out of their wrappers and so mix them up with other 'normal' strings. Any (unwitting) attempt to do so is caught at compile time. It does of course allow us to liftM functions designed to operate on normal strings so that they can be used on the special strings, within their wrappers, without needing to take them out. Though we might need to use liftM2 or liftM3. (Is there a liftM4).
As these strings are [Char] (list of char), then we might want to use some of the many list manipulation functions upon these 'special' lists of char. So we need to use mapM rather than map, and concatM instead of concat. Or maybe we need one of fold1 or foldr, or preferably (in most situations it seems) foldl'. Hm. Seems there is only foldM which is really foldl under the covers, with all the unpleasant implications for stack usage that has. Gettin' messy in'it.
You're right. You can extract data from a monad. Here's a typical comment about doing so:
Strafunski defines an instance for IO as instance MonadRun (->) IO where run alg = alg . unsafePerformIO, but since users aren't generally supposed to call unsafePerformIO, I don't think this is a good idea.
So, you wrap stuff (like unsafe user input), in a monad and reap all the benefits of compile-time type safety, but with the caveat of all the awkwardness and special treatments (special functions) that entails.
Or, you unwrap them using a run"monad" function (Functor?), and forgo the benefits of having put them there in the first place?
I guess that you could write the runTaint er ... thing, so that it took a regex and validated the string against some criteria before passing you back an untainted [Char]. Sounds familiar. But...
With type signatures like ( RegexLike a b => RegexContext a b (b, b, b, [b]) ) and RegexLike a b => RegexContext a b [(MatchOffset, MatchLength)], lifting those into a taint monad is scary. And of course, we'll want to wrap those inside a Maybe monad so that we can deal with failures nicely. Even scarier.
But the real problem is that I don't see any substitution operator? Maybe I missed it. In Perl, we can untaint things by stripping away anything that doesn't match what we need and extract what's left. Sure we can pattern match against all the possibilities and manipulate the (wrapped) string to produce what we want, but that gets incredibly tedious and costly.
The point is that it's a free world, and the language provides you with the tools to build this stuff on your own. No one has to implement taint mode for you.
Another way of saying that is: You have to implement taint mode yourself!
Yes, I have no doubts that it is possible. I wouldn't want to be the one trying to do it, but there are some smart guys out there will do it for us eventually.
But that's what puts the Practical in Perl. It may not allow the programmer to easily implement all this stuff themselves, but it doesn't need to. Pretty much everything you need is right there out-of-the-box, and has been for years. Making the hard stuff easy--and giving it to us in nicely integrated, usable forms out of the box--is exactly the benefits you get from Perl.
Don't get me wrong. Haskell is amazing. The strong static typing and monads and its ability to add to the syntax are immensely powerful and allow the expert Haskell programmer to achieve pretty much anything. But it does require a huge learning curve, deep technical understanding, a huge compiler, complex libraries that are catching up with Perl in some areas (like regexes; having far exceeded it in others for a long time).
But Perl has been doing the business for thousands of people for years and years and without requiring them to have MSc's in Computer Science. Sure, it showing it's age and there are things on most Perl hackers wouldn't-it-be-nice-ifs lists, that it can't quite deliver through cpan or XS.
But then Perl 6 is 'just around the corner'. It's been a long straight road approaching that corner, and perspective is deceptive. The corner may still be further away than it appears, but for good reasons. And once again, that same pragmatic, Practical approach to language design will prevail.
The most useful, most used, most bang-for-buck features other modern languages have, will have been incorporated into the base syntax in a concise, compatible, usable way.
- More granular typing, without the bondage and discipline.
- More transparent OO, without forcing everything to be an object.
- More functional programming constructs, and the simplicity of applying them in more situations, without invoking the purity ethic.
- More readable regex (Rules), without giving up the power, flexibility and conciseness of what made all the other languages want to emulate PCRE in the first place.
- More introspection and reflectivity and meta-programming, without requiring every programmer to re-invent every basic language facility from scratch. (Usually in incompatible ways!)
- More context sensitivities, and more contexts, and multi-methods so that the programmer doesn't have to invent myriad variations of names for every minor variation of resultant from a function or method; and for every different arity of function or method; and for every different variation of representation of each type.
Apply as much of the 'withouts'--B&D, objects, FP, purity, laziness, readability, reflectivity, et al-- as you need, without having to apply it to everything you do, or give up all the others, to achieve it.
My personal beef with Haskell is not what it can do, it's what it makes me give up in order to be able to do it.
It's all about trade offs, and Mr.Wall has, for me, an uncanny knack of whittling away the powerful arguments, hype and doctrine underlying the zealous fervour of each of these schools of thought and extracting the bits that the majority can benefit from, without their needing to convert on-masse to use them.
I'm not sure if you are the same anonymonk as wrote Re^10: Is it worth using Monads in Perl ? and what the Monads are ?--from the tone, probably not--but the Q language referenced in that post shows that Functional Programming doesn't have to be 'pure'. Indeed, it can be very 'practical', and can even gain power from being so. (Runtime symbolic expression manipulation with having to add another level of parser.)
My point is, every language makes fundamental choices about it's design. In the context of Haskell's strong, static type system, monads are a powerful, useful feature that allow (some would say, 'are required' for) it to do some very clever things.
In the context of Perl, monads would be an obscure, manual, tedious drain upon resources that dynamic (semi-compiled) languages can ill-afford, and that would produce few benefits for considerable costs.
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |