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

3

5

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?

Sherard answered 4/2 at 7:57 Comment(4)
Raku isn't an exclusively functional language, so it doesn't have a need to work around that to get actual work done. There may be something functionally similar, but it is unlikely to be as used as much as it is in a purely functional language.Tetanize
I think you are right.Sherard
Thanks for the playground link. FWIW my cargo cult attempt to force the example to show signs of asynchrony failed. Specifically, I changed the code to have 9 consecutive lines from _ <- IO.println("hey1!") to _ <- IO.println("hey9!"), then ran it many times. Each time the lines were executed in order. I wanted to sprinkle sleep like delays between some lines to see what that did, but after about 10 minutes of failing to figure out how to do that (eg this didn't help) I fell asleep.Tem
May be you can use _ <- IO. sleep(5.seconds) to sleep some seconds, when you import scala.concurrent.duration._Sherard
L
3

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.

Litigant answered 6/2 at 16:0 Comment(5)
Thank you for your detailed answer. This is exactly what I want, can you explain the code a little bit for others who have similar questions.Sherard
Basically we're creating a data type of "IO" with some generic type (you'd probably want that to be the type of the return value of the operation you're planning on wrapping). This effectively makes the operation "lazy", but also makes it a "Monad", we can use the properties of monads like bind, map, and return (didn't implement this one), to compose side-effects as data. In this case, I took a shortcut and just made the callback executable by calling the data as a function.Litigant
If you wanted to be 100% pure and such, you'd need to restrict the access to the callback (aka the side-effect) via bind and map.Litigant
very informative and imo a good example of how natural it is to do even "advanced" functional coding in rakuBewail
@RawleyFowler seems like adding a call to now would give a (useful) side-effect?Greenstone
T
4

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

What do you mean?

Raku's Block data type can encode side effects as pure values:

my &pure-value = { say 'something'; 42 }

capable of expressing both synchronous and asynchronous computations.

Sure:

my &synchronous-computation  = {       say 42 }
my &asynchronous-computation = { start say 42 }

asynchronous-computation; # displays `42` asynchronously
synchronous-computation;  # displays `42` synchronously
...

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

I don't know Scala, but I don't understand why you're suggesting the following Scala code is asynchronous. It, and other similar examples, look synchronous to me:

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()
}

I tried to explore your question more fully but some things got in the way:

  • The book you linked can't be read for free in a reasonable way.

  • I infer from your question, and the verbiage in the cats-effects doc, that vanilla (impure) Scala doesn't have a data type for encoding side effects as pure values. Raku makes it as easy as {...}. So there's little motivation to dig in to understand all that machinery.

  • cats and cats-effects are libraries, not part of Scala. Also, the cats links you've provided are to its 2.x version, which I think tracks its Scala 2.x library. I tried but gave up finding 3.x equivalents of the 2.x doc you linked. I'm loathe to invest further effort into the older library (2.x, whose last major updates were several years ago I think?) and older Scala (2.x, even more out-of-date with respect to Scala 3.x I think?).

  • I'm unsure what you're trying to achieve. Given that the Scala code is old-trending-obsolete, it seems unlikely that gaining more detailed insight into Raku analogs to that Scala code will get you or anyone else very far. What are you trying to achieve overall? Where do you hope to be in your Scala vs Raku explorations related to this a year from now?

Tem answered 5/2 at 2:0 Comment(1)
Thank you for your detailed explanation. I update my question, and provide a playground link. I'm just wondering if Raku is also capable of providing such an IO library, just to explore the possibilities of Raku in functional programming.Sherard
P
3

Not sure what exactly you need, but probably you could use Supply.

So FizzBuzz example in Raku could look like:

my Supply $s .=interval: 1;

$s              .tap( -> $x { say $x     });
$s.grep( * %% 3).tap( -> $  { say 'fizz' });
$s.grep( * %% 5).tap( -> $  { say 'buzz' });

sleep 16;

or with react-whenever block

my Supply $s .=interval: 1;

react {
    whenever $s               { say $_ }
    whenever $s.grep( * %% 3) { say 'fizz' }
    whenever $s.grep( * %% 5) { say 'buzz' }
}
Piscatelli answered 4/2 at 11:43 Comment(1)
Supply is a kind of synchronous data stream with multiple subscribers, and IO can be used as function parameters and return value, which describes some side effects, I think Raku doesn't provide such data type by now, as @Brad Gilbert said, Raku isn't an exclusively functional language.Sherard
L
3

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.

Litigant answered 6/2 at 16:0 Comment(5)
Thank you for your detailed answer. This is exactly what I want, can you explain the code a little bit for others who have similar questions.Sherard
Basically we're creating a data type of "IO" with some generic type (you'd probably want that to be the type of the return value of the operation you're planning on wrapping). This effectively makes the operation "lazy", but also makes it a "Monad", we can use the properties of monads like bind, map, and return (didn't implement this one), to compose side-effects as data. In this case, I took a shortcut and just made the callback executable by calling the data as a function.Litigant
If you wanted to be 100% pure and such, you'd need to restrict the access to the callback (aka the side-effect) via bind and map.Litigant
very informative and imo a good example of how natural it is to do even "advanced" functional coding in rakuBewail
@RawleyFowler seems like adding a call to now would give a (useful) side-effect?Greenstone

© 2022 - 2024 — McMap. All rights reserved.