<blockquote>
<p>Why do we need monadic types?</p>
</blockquote>
<p>Since it was the quandary of I/O and its observable effects in nonstrict languages like Haskell that brought the monadic interface to such prominence:</p>
<ul>
<li>
<blockquote>
<p>[...] monads are used to address the more general problem of computations (involving state, input/output, backtracking, ...) returning values: they do not solve any input/output-problems directly but rather provide an elegant and flexible abstraction of many solutions to related problems. [...] For instance, no less than three different input/output-schemes are used to solve these basic problems in <a href="https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.86.9725&amp;rep=rep1&amp;type=pdf" rel="nofollow noreferrer">Imperative functional programming</a>, the paper which originally proposed <em>`a new model, based on monads, for performing input/output in a non-strict, <a href="http://conal.net/blog/posts/the-c-language-is-purely-functional" rel="nofollow noreferrer">purely functional</a> language'.</em> [...]</p>
<p>[Such] input/output-schemes merely provide frameworks in which side-effecting operations can safely be used with a guaranteed order of execution and without affecting the properties of the purely functional parts of the language.</p>
<p><a href="https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.95.9846&amp;rep=rep1&amp;type=pdf" rel="nofollow noreferrer">Claus Reinke</a> (pages 96-97 of 210).</p>
</blockquote>
<p><sup>(emphasis by me.)</sup></p>
</li>
<li>
<blockquote>
<p>[...] When we write effectful code – monads or no monads – we have to constantly keep in mind the context of expressions we pass around.</p>
<p>The fact that monadic code ‘desugars’ (is implementable in terms of) side-effect-free code is irrelevant. When we use monadic notation, we program within that notation – without considering what this notation desugars into. Thinking of the desugared code breaks the monadic abstraction. A side-effect-free, applicative code is normally compiled to (that is, desugars into) C or machine code. If the desugaring argument has any force, it may be applied just as well to the applicative code, leading to the conclusion that it all boils down to the machine code and hence all programming is imperative.</p>
<p>[...] From the personal experience, I have noticed that the mistakes I make when writing monadic code are exactly the mistakes I made when programming in C. Actually, monadic mistakes tend to be worse, because monadic notation (compared to that of a typical imperative language) is ungainly and obscuring.</p>
<p><a href="https://arxiv.org/pdf/1905.06544" rel="nofollow noreferrer">Oleg Kiselyov</a> (page 21 of 26).</p>
</blockquote>
</li>
<li>
<blockquote>
<p>The most difficult construct for students to understand is the monad. I introduce <code>IO</code> without mentioning monads.</p>
<p><a href="https://wiki.haskell.org/index.php?title=Haskell_in_education&amp;oldid=63321" rel="nofollow noreferrer">Olaf Chitil.</a></p>
</blockquote>
</li>
</ul>
<p>More generally:</p>
<ul>
<li>
<blockquote>
<p>Still, today, over 25 years after the introduction of the concept of monads to the world of functional programming, beginning functional programmers struggle to grasp the concept of monads. This struggle is exemplified by the numerous blog posts about the effort of trying to learn about monads. From our own experience we notice that even at university level, bachelor level students often struggle to comprehend monads and consistently score poorly on monad-related exam questions.</p>
<p>Considering that the concept of monads is not likely to disappear from the functional programming landscape any time soon, it is vital that we, as the functional programming community, somehow overcome the problems novices encounter when first studying monads.</p>
<p><a href="https://pms.cs.ru.nl/iris-diglib/src/getContent.php?id=2017-Steenvoorden-SupportLearning" rel="nofollow noreferrer">Tim Steenvoorden, Jurriën Stutterheim, Erik Barendsen and Rinus Plasmeijer.</a></p>
</blockquote>
</li>
</ul>
<p>If only there was another way to specify &quot;<em>a guaranteed order of execution</em>&quot; in Haskell, while keeping the ability to separate regular Haskell definitions from those involved in I/O (and its observable effects) - translating this variation of <a href="https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&amp;rep=rep1&amp;type=pdf" rel="nofollow noreferrer">Philip Wadler</a>'s <code>echo</code>:</p>
<pre class="lang-ml prettyprint-override"><code>val echoML    : unit -&gt; unit
fun echoML () = let val c = getcML () in
                if c = #&quot;\n&quot; then
                  ()
                else
                  let val _ = putcML c in
                  echoML ()
                end

fun putcML c  = TextIO.output1(TextIO.stdOut,c);
fun getcML () = valOf(TextIO.input1(TextIO.stdIn));
</code></pre>
<p>...could then be as simple as:</p>
<pre class="lang-hs prettyprint-override"><code>echo :: OI -&gt; ()                         
echo u = let !(u1:u2:u3:_) = partsOI u in
         let !c = getChar u1 in          
         if c == '\n' then               
           ()                            
         else                            
           let !_ = putChar c u2 in      
           echo u3                       
</code></pre>
<p>where:</p>
<pre class="lang-hs prettyprint-override"><code>data OI  -- abstract

foreign import ccall &quot;primPartOI&quot; partOI :: OI -&gt; (OI, OI)
                      ⋮

foreign import ccall &quot;primGetCharOI&quot; getChar :: OI -&gt; Char
foreign import ccall &quot;primPutCharOI&quot; putChar :: Char -&gt; OI -&gt; ()
                      ⋮
</code></pre>
<p>and:</p>
<pre class="lang-hs prettyprint-override"><code>partsOI         :: OI -&gt; [OI]
partsOI u       =  let !(u1, u2) = partOI u in u1 : partsOI u2 
</code></pre>
<p>How would this work? At run-time, <code>Main.main</code> receives an initial <code>OI</code> <a href="https://academic.oup.com/comjnl/article-pdf/31/3/243/1157325/310243.pdf" rel="nofollow noreferrer"><em>pseudo-data</em></a> value as an argument:</p>
<pre class="lang-hs prettyprint-override"><code>module Main(main) where

main            :: OI -&gt; ()
          ⋮
</code></pre>
<p>...from which other <code>OI</code> values are produced, using <code>partOI</code> or <code>partsOI</code>. All you have to do is ensure each new <code>OI</code> value is used at most <em>once</em>, in each call to an <code>OI</code>-based definition, foreign or otherwise. In return, you get back a plain ordinary result - it isn't e.g. paired with some odd abstract state, or requires the use of a <s>callback</s> continuation, etc.</p>
<p>Using <code>OI</code>, instead of the unit type <code>()</code> like Standard ML does, means we can avoid <em>always</em> having to use the monadic interface:</p>
<blockquote>
<p>Once you're in the <code>IO</code> monad, you're stuck there forever, and are reduced to Algol-style imperative programming.</p>
<p><a href="https://existentialtype.wordpress.com/2011/05/01/of-course-ml-has-monads" rel="nofollow noreferrer">Robert Harper</a>.</p>
</blockquote>
<p>But if you really <code>do</code> need it:</p>
<pre class="lang-hs prettyprint-override"><code>type IO a       =  OI -&gt; a

unitIO          :: a -&gt; IO a
unitIO x        =  \ u -&gt; let !_ = partOI u in x

bindIO          :: IO a -&gt; (a -&gt; IO b) -&gt; IO b
bindIO m k      =  \ u -&gt; let !(u1, u2) = partOI u in
                          let !x        = m u1 in
                          let !y        = k x u2 in
                          y

                      ⋮
</code></pre>
<p>So, monadic types aren't <em>always</em> needed - there are other interfaces out there:</p>
<blockquote>
<p>LML had a fully fledged implementation of oracles running of a multi-processor (a Sequent Symmetry) back in ca 1989. The description in <a href="https://cth.altocumulus.org/%7Ehallgren/Thesis/toc.html" rel="nofollow noreferrer">the Fudgets thesis</a> refers to this implementation. It was fairly pleasant to work with and quite practical.</p>
<p>[...]</p>
<p>These days everything is done with monads so other solutions are sometimes forgotten.</p>
<p><a href="http://lambda-the-ultimate.org/node/1665#comment-20339" rel="nofollow noreferrer">Lennart Augustsson</a> (2006).</p>
</blockquote>
<hr />
<p>Wait a moment: since it so closely resembles Standard ML's direct use of effects, is this approach and its use of <em>pseudo-data</em> referentially transparent?</p>
<p>Absolutely - just find a suitable definition of &quot;<em>referential transparency</em>&quot;; <a href="https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.469.6920&amp;rep=rep1&amp;type=pdf" rel="nofollow noreferrer">there's plenty to choose from...</a></p>
Why do we need monads?
<p>In my humble opinion the answers to the famous question <a href="https://stackoverflow.com/questions/44965/what-is-a-monad">"What is a monad?"</a>, especially the most voted ones, try to explain what is a monad without clearly explaining <em>why monads are really necessary</em>.  Can they be explained as the solution to a problem?</p>