I decided recently, while starting a new project, that it would be interesting to try and take some of my new found paradigms from Haskell back into the relative slums of PHP. As much as I like the fact that PHP just works, the more I get into Haskell the more I resent having to type reams and reams of code in PHP just to get things done.
That's just me and I won't change!
One of the things that I am really starting to miss now that I understand Monads (at least enough to write them and use them, my own and the libraries) is the ability to be able to have "Maybe". It is such a lovely way of handling data when there really isn't anything and it sure as hell kicks ass over using "NULL" as a return type.
PHP is riddled with functions that return FALSE to mean they didn't actually work or some other value if they did which makes a whole mockery of its so called "types". Case in point, when using PDO for example (I could be wrong but I don't think so) you MUST cast numeric fields using (int) or it ends up going out as a string. And when you have an internally imposed coding convention that further insists you prefix integers with `$int` you really begin to appreciate Haskell-s type system and hard-as-nails compiler!
Just Maybe....
So I thought just maybe(!) I could create a very very simple wrapper to at least encapsulate the sentiment of not using NULL to mean "I borked your API call" or something.After a while I came up with this first cut, which is pretty much what I am using day-to-day now, if I like it enough after using it on the project, and when it is "stable" I will stick it on GitHub or something like all the other hip cats are doing these days.
Explaining It...
Let's start with the abstract base class, this is quite complex:
abstract class Maybe {}
Yes, sarcasm, one of the most underestimated tools of any developers personal survival kit. And like me, it is abstract and doesn't seem to do much. Oh how we laughed etc.
Let's move on to the implementation of Nothing.... huh?
class Nothing extends Maybe { function __invoke() { throw new Exception("Nothing to see here"); } function isNothing() { return true; } }
Firstly we can see that it extends Maybe, which is a requirement if we are to be able to type a function parameter as a Maybe and be able to pass in Nothing or Just instances. There is also a simple helper function here called isNothing() which returns true. This is so that when an instance of Nothing is returned from a function, you can see what came back:
$objResponse = functionThatReturnsJustStrings(); if ($objReponse->isNothing()) { // do something with nothing! } else { $strActualData = $objResponse(); } function functionThatReturnsJustStrings() { return rand(1,10) > 5 ? nothing() : just("Time to go for a coffee"); }
Extracting the data is done by invoking the returned instance as a function. Why? Because that's the shortest syntactical form I could think of using without having to create a "getter" method. The Nothing class will throw an exception because you don't want to be doing that on Nothing after all.
I did try to make it cleverer at this point but the simple fact is that even though PHP now allows anonymous functions and closure capture ("use") it is ugly as sin and trying to emulate Haskell just leads to pretty unreadable code so I abandoned that and just stuck with the core goal of trying to have a Maybe/Just/Nothing triumvirate going on. That's why the __invoke() function hands back the actual wrapped data value.
With the above code snippet in mind, the implementation of Just isn't too hard to grasp:
class Just extends Maybe { protected $theThing; function __construct($aThing) { $this->theThing = $aThing; } function __invoke() { return $this->theThing; } function isNothing() { return false; } }
This time it says that it is "not" Nothing and the __invoke() function returns the wrapped data value. Forget any notions of "bind" (>>=) I just didn't want to work that hard for this! The only other point is that the constructor just saves whatever value you passed in, even NULL so you can see that handing back NULL won't be a problem any more and can be interpreted "correctly", whatever that means within the context of your application.
Some helpers for polish...
Just to make life a little easier I wrote these functions too which just make it easy to use the above classes:
function nothing() { return new Nothing(); } function just($mxdVal) { return new Just($mxdVal); } function fromJust($objMaybe) { return $objMaybe(); }
So far this little crew have taken me a long way to feeling happier about PHP, writing clearer code (dare I say even self-documenting at times?!) and to boot, the following idiom has arisen:
$result = callAFunction(); if ($result->isNothing()) { // deal with it! } else { $data = $result(); //... process $data }Seems to work well and makes for readable code too which was the whole point in the first place. Like I said before I had tried to make this Haskell-shaped too by actually passing two functions into the "()" call, the first being executed when Nothing was present and the second when Just something was present but it was uglier than I am so I dropped it, if you don't believe me here's a made up example to show the ugliness in action:
function monadUseCase($text, $sku) { $result = callAFunction(); $result(function() use ($text) { ... }, function($data) use ($text, $sku){ ... }); }Maybe I will go back and have another go some day, in the meantime, "Enjoy and good luck in the editor!"
PS: You MUST (as in RFC-2119) check out "Cooking With Dog", it got me cooking again!
I appreciate the sentiment, but this post has nothing to do with Monads ;)
ReplyDeleteWhat you've done is to separate out a distinguished 'failure' value (any instance of Nothing) from all other possible values (wrapped in a Just). This is useful, mainly because it lets us distinguish *different kinds of failure*. For example, if we look up a Person based on their name then get their partner, we will either get a 'Just (Just Person)' on success, or a 'Just Nothing' if they have no partner, or a 'Nothing' if there is no Person with that name.
Your "isNothing" and "__invoke" methods basically allow you to pattern-match on Maybes.
That's all well and good, but everything is still being done manually. Maybe is a Functor, which lets us apply regular functions to Maybe values and receive back Maybe values, which means we don't have to write special version of everything just to handle Maybe. Maybe is also a Monad, which allows us to collapse ("join") nested Maybes into one. This is very useful when we have a sequence of steps to perform, any of which might fail, but our error handler doesn't care which one happened to fail.
You have inspired me though, I've got an idea for what might be a semi-nice interface for Monads, Functors, etc. in an OO setting.
Warbo,
ReplyDeleteExactly correct in your analysis! I guess I was more after "Maybe" pattern rather than actual monads. I just don't think PHP would allow for a nice looking DSL/class based approach as it's syntax is pretty ugly and not that elegent. Using anoymous functions is ugly.
I am happy to have inspired somebody though. Can't say that happens too often any more! LMAO
All the best with your interface idea though. For me this is what "real" software engineering is all about...using your head and rolling your own to gain insights.