8

I have been getting my head around monads in functional programming and seem to see some commonality between Perl's default variable $_ and FP monads.

Is this true? Are there similarities, if not why not - perhaps this'll allow me to grok monads once and for all by understanding why the use of $_ is not monad like!

My (at this stage vague) understanding of a monad is that one aspect is that it can be used to chain commands together so that the output of one is magically the input of the next.

So for example this perl:

while (<STDIN>) {
   chomp;
   s/Hello/Goodbye/;
   print;
}

Takes from the stdin, removes white space (chomp) replaces "Hello" with "Goodbye" and prints out the result. Each of those commands uses the value in the implied variable $_ and outputs the result into it.

The same code but showing the implied variable use:

while ($_ = <STDIN>){
    chomp $_;
    $_ =~ s/Hello/Goodbye/;
    print $_;
}

So correct me if I'm wrong but that feels very much like one aspect of monads.

6
  • 1
    what makes you think it's monadic? I have some familiarity with monads but none with Perl's default variable. Perl's junctions do I believe give a fair similarity to the list monad (graph monad?) but can't speak to the default variable. Explain your thoughts and reasoning on this if you want an answer that makes sense. Oct 27, 2015 at 14:21
  • No problem, will edit my question to flesh out my thoughts. Oct 27, 2015 at 14:23
  • @JimmyHoffa Made the edits, hope I haven't scared you with the perl! Oct 27, 2015 at 14:38
  • 1
    Part of the reason monads are so frequently seen as confusing is because they're often explained in FP terms, and FP languages are confusing enough as it is. Have a look at Eric Lippert's series explaining monads in much more approachable OO terms. He actually manages to make the concept make sense. Oct 27, 2015 at 14:58
  • 4
    I just want to say, thank you for editing your question, it's a welcome relief for someone new to the site to do that. Thanks!
    – enderland
    Oct 27, 2015 at 15:41

2 Answers 2

6

I understand why you see a similarity between monads and the $_ variable:

  • a monad encapsulates a value in some context, and allows operations to be performed on that value.
  • the $_ variable refers to the current context. Operations can use this context.

So the common part is that there is some value, some context, and some operations. Unfortunately, this could describe all programming. The $_ variable is not monad like because it does not actually have any properties of a monad. Most importantly, using the $_ variable does not compose.

One aspect of monads is that they can be defined by two operations: one to wrap a value in a context (sometimes called a constructor, or a return operation), and one bind operation to apply an action to a value. The action given to bind takes an unwrapped value, and returns a monad of the expected kind again. It is really important that bind does not return a naked value, but a monad. The monad is responsible for deciding whether, how often, and in which order the bind action is applied to its wrapped value, if any. This flexibility allows monads to model collections with any number of elements, and also control flow constructs such as conditionals.

In Perl, lists have mostly monad-like behaviour. Their construction is implicitly tracked through list context, and their bind operation is the map builtin. The special context of a list is that all values in the list are ordered. Examples:

# the empty list
() #=> ()
# a list of one element
(1) #=> (1)
# a longer list
(1, 2, 3, 4) #=> (1, 2, 3, 4)
# identity
map { ($_) } (1, 2, 3) #=> (1, 2, 3)
# double each item
map { ($_, $_) } (1, 2, 3) #=> (1, 1, 2, 2, 3, 3)

We can show that this satisfies the monad laws:

  • the constructor is the neutral operation for bind: Given a list @list, map { ($_) } @list is always the same as @list, and wrapping a value $x in a list ($x) and then mapping a function f over it map { f($_) } ($x) has the same effect as f($x).

  • binding actions satisfies a specific equivalence. For a given @list and all (pure) functions f and g, these two expressions must be equivalent:

    map { g($_) } map { f($_) } @list
    map { map { g($_) } f($_) } @list
    

Now let's contrast this with operations such as s/foo/bar/ that operate on $_ by default. We clearly have a kind of constructor for $_, e.g. for loops place the value in $_ by default. And we have a kind of binding mechanism that allows $_ to be used by some operation. These operations take the value from $_ and usually modify it in-place. However, in-place modifications are fundamentally at odds with monadic behaviour – the bind operation expects an action that takes an unwrapped value and returns a monad. With in-place modification, we must return exactly one value. There's no wrapping/unwrapping taking place either explicitly or implicitly.

The point that the result of each computation is used by the next step may look monadic, but it's just imperative programming. Being a stateful variable is all that $_ is. Of course, you can model imperative stateful computations with a state monad, but that is more about the ; operator than the $_ variable.

By the way, the only special aspect of $_ is that the *_ variables are super-global and always resolve to the main package. The general syntax of your example code could be mirrored with any other implicit variable, e.g. $::florp:

sub florpyreadwhile {
  my ($fh, $body) = @_;
  while ($::florp = <$fh>) {
    $body->();
  }
}

sub florpychomp {
  return chomp $_[0] if @_;
  return chomp $::florp;
}

sub florpysubstitute {
   my ($re, $sub) = @_;
   my $str_ref = (@_ >= 3) ? \$_[2] : \$::florp;
   return $$str_ref =~ s/$re/$sub/;
}

sub florpyprint {
  return print @_ if @_;
  return print $::florp;
}

# using florpy sugar

florpyreadwhile \*STDIN => sub {
  florpychomp;
  florpysubstitute qr/Hello/ => 'Goodbye';
  florpyprint;
};

# without florpy sugar

florpyreadwhile \*STDIN => sub {
  florpychomp $::florp;
  florpysubstitute qr/Hello/ => 'Goodbye', $::florp;
  florpyprint $::florp;
};
1
  • This is wonderful, thank you amon. This combined with reading Eric Lippert's series from @mason-wheeler's comment is really starting to make this gel. More digesting to do... Oct 27, 2015 at 23:25
2

Well, I don't know Perl, but that code reminds me of the State monad. So here's my attempt at mimicking it in Haskell:

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.State
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as TextIO

main :: IO ()
main = flip evalStateT "" $ do
  stdin <- lift TextIO.getContents
  forLinesM_ stdin $ do
    modify T.strip                        -- chomp
    modify (T.replace "Hello" "Goodbye")  -- s/Hello/Goodbye/
    get >>= (lift . TextIO.putStrLn)      -- print 

-- | Auxiliary function to loop over the lines of a 'Text', putting
-- each line into the implicit state of the 'StateT' monad transformer.
forLinesM_ :: Monad m => Text -> StateT Text m b -> StateT Text m ()
forLinesM_ text body =
    forM_ (T.lines text) $ \line -> do
      put line
      body

The State monad does require that the type of the state be uniform throughout the computation, so I'm guessing it's rather more restrictive than the Perl implicit variable.

I find the above code cringeworthy. What Perl does with implicit variables, Haskell does with function or monadic composition. So a better Haskell program for this would be:

{-# LANGUAGE OverloadedStrings #-}

import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as TextIO

main :: IO ()
main = TextIO.interact go
  -- The '.' operator is right-to-left function composition, so 
  -- this reads from bottom to top:
  where go = 
       -- Join the lines back together
         T.unlines

       -- Replace strings in each line  
       . map (T.replace "Hello" "Goodbye")

       -- trim whitespace from each line
       . map T.strip                    

       -- Split input into lines
       . T.lines  

Function composition resembles Unix pipes more than Perl implicit variables.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.