4 ms·
It's hard to identify a practical reason because presumably using your existing PHP stack is going to be easier for you than learning how to run a Haskell stack
by drostie 14y ago
It's hard to identify a practical reason because presumably using your existing PHP stack is going to be easier for you than learning how to run a Haskell stack. Every new language comes with such barriers. It's sometimes hard to see how severe those barriers are -- I remember trying to show a totally novice-to-programming kid how to set up EasyPHP so that they could start coding. Setting up EasyPHP, which is, well, Easy, requires explaining what HTTP is and what Apache is and all that... it's actually a huge barrier to entry for a novice programmer.
But if you're OK with the impractical, here goes:
Haskell trains you to think about programming in a couple of subtly different ways that change your entire perspective.
(1) Variables are replaced with definitions and values. You probably remember HTTP as stateless and cookies as a sort of informal "patch" for this; similarly it's hard to define state in Haskell. This means that `x = [1, 2, 3];` is allowed but `x[1] = 5;` is not, because the list cannot be directly modified. This does not actually weaken the language because you can always bind new names to new values, and idiomatically you'd often use recursion -- when you want to change a parameter you arrange the code so that you can recurse and call the function again with a new parameter. It's not 100% practical but let's take the Fibonacci numbers, which you might code in PHP like so:
function fibs($n) {
$curr = 0; $next = 1;
for ($i = 0; $i < $n; $i++) {
$tmp = $curr;
$curr = $next;
$next += $tmp;
}
return $curr;
}
In Haskell you could write this itero-recursively as:
let fibs n = f n 0 1
where f n curr next = if n < 1 then curr else f (n - 1) (next) (curr + next)
Notice that there's no $tmp and the named variables are now function arguments. You'll find yourself writing PHP more like:
function fibs($n, $curr = 0, $next = 1) {
return n < 1 ? $curr : fibs($n - 1, $next, $curr + $next);
}
(2) Haskell is lazy. The above is actually impure because it implements `fibs` as a function when really the Fibonacci numbers are a sequence and should be treated that way. Haskell actually has no problem with defining the infinite list of Fibonaccis as:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
That's a list just like any other in Haskell, it just happens to be infinite. Or, you can write the infinite list of primes:
primes = 2 : filter prime [3..]
where prime k = all (\n -> mod k n /= 0) (takeWhile (\n -> n^2 <= k) primes)
You will miss this a little in other languages; even Python, which can do this with custom generators, gets a little clumsy. Especially, I miss this in Node.js, where the whole language is built around an idea of "callbacks" which wouldn't need to exist if the JavaScript operators could work on deferred computations (sometimes called "promises").
(3) Haskell has a type system and explicit pattern-matching. This is a little harder to express: you're used to saying `x = my_object->x` in PHP to avoid saying "x" over and over. Let's say that the object should also have "y" and "z" fields; then you'll have to deal with cryptic errors if your function is ever called with a my_object which does not have a field named "x", but it will be okay if "y" or "z" is not allowed, even though that doesn't actually make sense really!
In Haskell you'll specify in the definition of the argument "Object x y z"; and then x, y, pattern-matching to those respective values. So your functions don't start off with a bunch of "data-extraction" commands; those commands are in the input pattern. You can also define the function when the patterns do not match, because you can define a function several times for different patterns.
The type system can also flag, for example, unsanitized text going into a SQL query and other such things you might have missed, so you can lean on it a surprising amount.
(4) The biggest thing that this page was talking about was monads, and the reason why it was talking about them is because Haskell offers a special notation for dealing with monads.
A monad is basically a context which you can't necessarily "break the data out of". For example, the "IO" monad is a context which says "this data depends on an unrepeatable IO action, like getting a random number or reading a file." You can't really erase that fact from the data, but that fact shouldn't stand in the way of handling the data. The IO monad allows you to very cleanly separate the stuff which might be different in two different runs of the program, from the stuff which is never so.
The author makes the claim that analogies are bad for understanding monads. Instead, you should just look at the definition. A monad is a typeclass which wraps another type, so the type is "M(T)" where T is any other type. A monad is defined by two functions. One is called "return" and has type T -> M(T), it puts plain data into the monad. The other function is called "bind" and accepts a function T -> M(T2), which intuitively "takes the data and feeds it to the function." The reason that you can't always "break out" of the monad is that the output of this function is M(T2), so the function output must also be in the monad. We know that "monad" is a scary-sounding word but really nobody has a better name for it.
Lists are a good monad; here `return x = [x]` and `bind xs f = concat (map f xs)`. Another simple monad is called Maybe, which can either be `Just x` or `Nothing`, it sort of represents a computation which might fail. Here `return x = Just x` and `bind Nothing fn = Nothing; bind (Just x) fn = fn x`. There's also a "State" monad, which gives you a "put" and "get" method and a scope block for typed modifiable variables, so you can have code that looks imperative -- but you've got to carefully mark it as such with the type system as a whole. The whole point being that once you allow that "put" statement you make it much harder to think about bugs because the value of the parameter will depend on where in the function that parameter appears -- so the type system acts as a warning flag.