How to use IO cats API

Personally I find the IO cats API difficult to understand.
In fact i come no further then,
val run:IO[Unit] = IO.println(“Hello1, World1!”)
which eargerly evaluates and prints.

Let’s say i have a function :
def myfun():IO[String]=IO { “Hello World”}
How do i make myfun() print to "stdout "in a program ?

And a theoretical question what is the relation between map,flatmap & monads ?

1 Like

It should not, maybe you are seeing some REPL output or something.

val myFun: IO[String] = IO.pure("Hello, World")
val program: IO[Unit] = myFun.flatMap(IO.println)

Note that IO itself presents a new paradigm, thus is not rare that you find yourself stuck.
I would recommend checking the “Program as Values” series from Fabio Labella: archive and the talk “Intro to cats-effect” by Gavin: GitHub - Daenyth/intro-cats-effect: Slides and video recording for my "Intro to Cats-Effect" talk
I would also recommend joining the Typelevel discord server: Discord where it would be easier to answer questions.

4 Likes

Yeah, agreed – the pure-FP world is different, and you have to be prepared for changes to how your program works – this typically isn’t a topic you pick up fully in a day or two. I agree with the above recommendations, especially to get involved with the Typelevel Discord, which is a pretty friendly and helpful place.

But just to very briefly summarize a few things:

An IO is essentially a data structure (which contains a lot of code) that describes a program. In a pure-FP application, most of your code is just building that data structure, but not actually doing anything yet. Once you’ve built the entire structure (which is typically an IO containing more IOs containing yet more IOs, often many layers deep), you “run” it, and at that point everything happens.

(Yes, there are serious benefits from this approach, but it takes a bunch of getting used to.)

Very briefly:

map(f) is the central method of a Functor, and exists on structures that in some sense “contain” things – it means to run the function f on each value, resulting in a new Functor of the same sort. So myList.map(f) calls f on each value in myList, giving you a new List.

Now, imagine a function listFunc that itself returns a List[something]. If you run myList.map(listFunc), you’re going to wind up with a List[List[something]]. That’s typically not what you want – you just want a List[something] at the end. So List contains a method .flatten – if you have a List[List[something]], then calling .flatten on it will turn that into a plain List[something], by concatenating the internal Lists together.

myList.flatMap(f) basically does both of those operations – it runs f on each value in myList, each of which returns a List, and then flattens the result. Since you usually want to flatten after map, you wind up using flatMap a lot.

A Monad (very imprecisely speaking) is anything that is shaped like List – it’s a Functor with a .map() operation, and there is a reasonable definition of .flatten, such that calling Monad[Monad[something]].flatten makes sense. There’s more to it (especially if you want to be precise and correct about it), but in practice that’s usually what you care about in most Scala programming. Lots of Functors are Monads – List, Option, Future, IO, etc – but not all: it depends on whether there is a sensible meaning for .flatten.

(This is common enough that Scala supports it, in the form of the for comprehension. Scala’s for works on any data type that is Monad-shaped – basically, if it has map and flatMap, you can use it in for. for is just surface syntax: the compiler turns it into sequences of flatMap calls, with map for the last one.)

So if myIO (which is basically a program) is built from a sequence of IOs (each a sub-program) inside it, then myIO.flatten simply means that this program consists of all those sub-program steps, in order. And as a result, an IO-based application tends to be all about constructing these stacks of nested IOs, all getting flattened into a single IO at the top, which you then run.

Hopefully that helps at least a little. Like I said, this stuff takes some getting used to, but I found that, once I really understood it, it made my programming quite a lot more fun…

3 Likes

In addition to the great answers above, if you are still new to programming with monads and functors in general (not just the IO monad), I recommend the free book about the cats library, “Scala with Cats”.

3 Likes

Agreed – for the reading groups I was running at my last job, we used:

5 Likes

Hello @alain,

I think that before you try to understand the IO monad you want to learn about simpler Functors and Monads.

I don’t have anything remotely perfect to help you, but maybe some or all of the following can help a little bit somewhere on your upcoming journey:

If you find any of the above overwhelming then don’t be discouraged, there is lots to learn, and it is very interesting. Consider reading Functional Programming, Simplified (a Scala book) | alvinalexander.com.

Philip

1 Like

>And a theoretical question what is the relation between map,flatmap & monads ?

From Monad Fact #4

From Monad as functor with pair of natural transformations

join is another name for flatten.

flatmapping is first mapping and then flattening.

flattening is flatmapping identity.

From Symmetry in the interrelation of flatMap/foldMap/traverse and flatten…