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?
the point is that for things like Option, the implementation is easy enough that operational thinking can be helpful. But this doesn't scale to more complicated abstractions like the real cats.effect.IO or fs2.Stream
so algebraic thinking is where we think of things like
IO as programs and how we can compose them I guess? so in that sense algebraic thinking for
IOis pretty much the same as algebraic thinking for