Haskell has some expresiveness advantages over Python/Ruby, but it also has some expressiveness deficiencies compared with these languages. I can list some of these if you're interested.
But the end result is that in my experience, Haskell code is not much shorter than Python code (I don't have much Ruby experience, but I suspect it's probably on par with Python).
IMO, the main advantage of Haskell over Ruby/Python, is that you don't pay the enormous prices for the high expressiveness that you do for roughly the same expressiveness in Ruby/Python. I find it much easier to maintain Haskell code, it has virtually no runtime failures, it has good performance, and so forth.
I cannot possibly list all of the useful things that are nice about Haskell, the rabbit hole is very deep. But I'll enumerate just a few.
Type-classes:
As a simple illustrative example, let's look at the Show class:
class Show s where
show :: s -> String
This means: Any type "s" is an instance of Show, iff it is declared an instance, and has an implementation of the "show" function.
Example instance:
data Bool = False | True
instance Show Bool where
show True = "True"
show False = "False"
This definition also illustrates Pattern-Matching, which is a pretty useful benefit on its own.
Note, Haskell has a "deriving" mechanism that allows replacing the above boilerplate with:
data Bool = False | True
deriving (Show)
So far, we've used only type-classes that Python interfaces can encode. This is basically Python's __repr__ (except it's auto-generated to a sensible implementation that is usable as Haskell syntax).
Here's something Python/Ruby cannot directly do:
class Read r where
read :: String -> Maybe r
Note that "r" appears in the
result type of "read".
Now, you can compose "read" with other functions to build interesting values, that are still polymorphic on the result type. For example:
readLine :: Read r => IO (Maybe r)
This type means: Given that the "r" type is an instance of Read (its values can be parsed from String), readLn is an effectful procedure that when executed, yields a "Maybe r" value (which may be Nothing if input is not parseable).
With Python/Ruby you may be able to encode this via classmethods, but then all the relevant classes have to be modified to inherit from this. You can try returning factories, but then these must return any particular type they're requested. There's no easy/direct way to encode this as in Haskell.
This is basically a safe __unrepr__, which is not implementable in this way in Python not just because of the lack of return-type polymorphism, but also because of mutability/identity concerns. Immutability-by-default is another nice benefit, gets rid of a whole slew of object-identity/aliasing issues.
On top of the extremely versatile type-class mechanism, many useful abstractions are built, which do not exist in Python. Monads are just one of these useful abstractions.
For example, using lists and their instance of the Monad typeclass, I can write the "powerset" function as:
powerset xs = filterM (\x -> [True,False]) xs
filterM is much like filter, except it allows a monadic "effect" to be applied at the predicate function. In this case, the effect is non-determinism (or multiple results). We ignore the value of the element, and accept AND reject every element in the list -- thus we get the powerset.
Concurrency:
One of Haskell's models of concurrency is very similar to Erlang, and uses immutable-shared-state threads with explicit-mutable-state. This is great for both performance and simple semantics (the rarity of mutable state makes reasoning about data easy. The preemption makes reasoning about starvation easy). Haskell's threads are also user-level cheap threads, and you can literally create millions of them.
Say we want to do latency-hiding on some expensive procedure. I'll present the Haskell code to do so by creating a thread-pool that preemptively executes the given operation and passes the result to callers that need it:
preemptively :: Int -> IO a -> IO (IO a)
preemptively poolSize act = do
requestMVar <- newEmptyMVar
replicateM_ poolSize . forkIO . forever $ do
res <- try act
responseMVar <- takeMVar requestMVar
putMVar responseMVar res
return $ do
responseMVar <- newEmptyMVar
putMVar requestMVar responseMVar
either ioError return =<< takeMVar responseMVar
preemptively takes a number of threads to run, and an action to preemptively execute in all of those threads. It creates an empty MVar (basically an inter-thread channel with a 1-sized buffer) for placing requests.
Then it uses "replicateM_ poolSize . forkIO . forever $ do ...". This means the same as:
replicateM_ poolSize (forkIO (forever (do ...))). forever (do ...) executes the given do block in a loop, forever. forkIO executes the forever-loop in its own thread. replicateM_ poolSize executes the given forkIO action poolSize times. So this single line is enough to create our thread pool.
Then comes the code for the thread's single iteration. In each iteration, the thread executes "act" via "try". So any IO exceptions are caught and converted to a value, and placed in "res". Then the thread waits for someone to request a result by waiting on the mvar (takeMVar). Note the requests put on the requestMVar consist of a response MVar to reply to. The thread then places the result of the execution (which may be a value or an exception) into the response mvar.
Then we return an action that asks a thread in the pool for a result, and re-raises the IO exception if there was one, or just returns the threads' result.
Now, if you want to use preemptively, all you have to do is:
getConnection <- preepmtively 20 (sshConnect remoteHost)
And you have a thread pool of 20 threads that preemptively make an ssh connection to your remote host. getConnection will then be immediate if it happens after the threads are done.
This example is meant to illustrate that real-world, concurrent, effectful programming is nice in Haskell, and not just the theoretical pure stuff.