# Definition:
#   Haskell:   IO a
#   Perl 6:    { a }

# return :: (Monad m) => a -> m a
sub mreturn($a) { return { $a } }

# (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
sub mbind(
  Code $ma,        # m a
  Code $f,        # (a -> m b)
            # This should be "Code $f returns Code", but Pugs doesn't
            # grok that yet.
) {
  return {
    my $a  = $ma();    # Run m a, yielding a
    my $mb = $f($a);   # Give $f the a, yielding m b
    $mb();             # Return b
  };
}

# (>>) :: (Monad m) => m a -> m b -> m b
sub mbind_ignore_result(Code $ma, Code $mb) {
  return {
    $ma();     # Run m a, ignoring the result
    $mb();     # Run and return m b
  };
}

# fail :: (Monad m) => String -> m a
# (We can't/shouldn't use "fail" in Perl 6, too, as "fail" is a builtin
# in Perl 6.)
sub mfail(Str $a) {
  return {
    die $a;
  };
}

sub sequence_(Code *@actions) {
  return {
    $_() for @actions;
    undef;
  };
}

sub sequence(Code *@actions) {
  return {
    my @res = @actions.map(-> $action { $action() });
    @res;
  };
}

sub mapM(Code $f, *@input) {
  return {
    @input.map($f).map(-> $i { $i() });
  };
}

# getLine :: IO String
# getLine = ...implemented by the compiler...
sub getLine() {
  return {
    my $line = =$*IN;
    $line;
  };
}

# putStrLn :: String -> IO ()
# putStrLn = ...implemented by the compiler...
sub putStrLn(Str $x) { return { say $x; undef } }


# Now some example code:
{
  my $line_from_user = getLine();
  # Nothing is read yet.
  my $echo = mbind($line_from_user, -> $x { putStrLn($x) });
  # Nothing is read or printed yet.
  my @actions = (getLine(), $echo);
  # Again, nothing printed yet.
  my $both = sequence_(@actions);
  # Only now there're two lines read from the user, and the second one is
  # echoed back.
  $both();
}

{
  # let actions = [getLine, getLine, getLine]
  # actions :: [IO String]
  my @actions = (getLine(), getLine(), getLine());

  # let results = sequence actions
  # results :: IO [String]
  my $results = sequence(@actions);

  my $echo_prefixed = mbind($results, -> *@results {
    mapM(-> Str $x { putStrLn($x) }, @results);
  });
  $echo_prefixed();
}
