Idand in code using
IO, and see how they differ (because the latter takes great care to satisfy various laws under a broad range of operational conditions, and the former doesn't).
Here there are a few implementations of
IO, the first one of which is essentially with
() => A. But I do think that looking at the implementation is not the right way to understand
IO at first
IO[A]is a program.
Option[A]is a program. These programs can be composed using certain operations. You build your code by building mini-programs, assembling bigger programs out of small ones, and translating programs from one "language" to another (eg.
forprioritises brevity over clarity, and when trying to form a clear semantic mental model it's unhelpful
Option[A] is a program. There are two ways of constructing primitive programs in the "
Optional language". One is with
pure[A](a: A): Option[A], the other is with
empty[A]: Option[A]. Then you have ways of composing these Optional programs.
map transforms the result of an
Option[A] into an
Option[B], assuming you can encode this transformation as
A => B. But there is an interaction with
empty.map(f) = empty. This gives us semantics: optional programs can express short-circuiting. You can have more powerful ways of composing optional programs, for example by building programs that depend on the result of the previous program:
Option[A] => (A => Option[B]) => Option[B] , which is flatMap. Flatmap also shorcircuits on empty programs. Another way you can compose optional programs is by choice, by taking a different path when you find an
empty program, this is
orElse. Finally, when you are done composing, you need to run this program, which is
A => Option[A] => A, i.e.
This exemplifies completely algebraic thinking: I could implement optional programs with the ADT you know as
Option[A], or with
Either[Unit, A], or with a function (Option.fold), but the way you write and reason about these, in the mindset I just laid out, is unchanged.
or with a function (Option.fold)
what do you mean by this?