Search code examples
functional-programmingiorakuside-effects

Does Raku has a data type for encoding side effects as pure values?


I am doing some exercise from the book Grokking Functional Programming, the origin code examples are written in Scala, and I want to rewrite it with Raku.

In Scala's cats effect library, there is a type call IO, which is a data type for encoding side effects as pure values, capable of expressing both synchronous and asynchronous computations.IO[A] is a value that represents a potentially side-effectful IO action (or another unsafe operation) that, if successful, produces a value of type A.

The following code print "hey!" twice:

import cats.effect.IO
import cats.effect.unsafe.implicits.global

object TestApp extends App {
  val program: IO[Unit] = for {
    _ <- IO.println("hey!")
    _ <- IO.println("hey!")
  } yield ()
  program.unsafeRunSync()
}

The code can be run in Scala playground.

The corresponding data type in Raku I can think of is Promise, or some other Lazy data type I dont't know yet.

So, does Raku has a data type for encoding side effects as pure values?


Solution

  • Using Raku's type-system it's fairly simple to implement monadic types like IO. Something like:

    role IO[$t] does Callable { 
        has $.type = $t;
        has &.cb;
    
        method map(&f) {
            return IO[$!type].new(cb => &f(&!cb));
        }
    
        method bind(&f) {
            return &f(&!cb);
        }
    
        submethod CALL-ME(*@t) {
            &!cb();
        }
    }
    
    sub sayIO($s --> IO[Nil]) {
        # Use Nil in place of unit.
        return IO[Nil].new(cb => sub { say $s });
    }
    
    sub execAllSync(@ios --> Any) {
        $_() for @ios;
    }
    
    sub execAllAsync(@ios --> Promise) {
        my Promise @promises;
        for @ios -> $a {
            push @promises, start { $a(); }
        }
        Promise.allof(|@promises);
    }
    
    execAllSync [sayIO("foo"), sayIO("bar"), sayIO("baz")];
    await execAllAsync([sayIO("foo"), sayIO("bar"), sayIO("baz")]);
    

    There may also be some type-fu you can do using given to create a more monadic interface, similar to what is in Monad-Result. But I'm not 100% sure that it is super useful. The side-effect is just the execution of the code, so all you really need is a wrapper around a function.