Skip to content

Commit bd4d2a3

Browse files
klaeuferCopilot
andcommitted
Address multiple open GitHub issues (#2, #16, #31, #33, #35, #52, #54, #57, #58, #59, #60, #61, #62)
- #59: Add JLine 4.x code example for user-editable input with Try/Iterator - #60: Add 'Role of EOF' subsection explaining Ctrl-D/Ctrl-Z and batch pipelines - #61: Add 'Reporting progress' section covering stderr + progressbar library - #62: Add 'Fine-grained vs. Coarse-grained Concurrency' subsection with table - #16: Add 'Dependency Inversion Principle' subsection with abstraction continuum - #35: Add 'Reification' section with formal definition and effect-type survey - #2: Add 'for comprehensions with stateful steps' subsection (Option/Try idioms) - #31: Add Fibonacci/Sieve examples (imperative → recursive → corecursive) - #57: Add collection methods with meaningful product-type (Person case class) - #52: Add foldLeft vs. scanLeft subsection with examples and use-cases - #54: Expand Option/Either entry with prominent note box and code example - #58: Expand Pattern Matching section with exhaustiveness, PartialFunction, collect - #33: Promote key F-algebra definitions (functor, catamorphism, anamorphism) to formal note boxes throughout functional chapter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 66520fe commit bd4d2a3

4 files changed

Lines changed: 497 additions & 20 deletions

File tree

source/20-imperative.rst

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,27 @@ We can read the standard input as lines using this iterator:
517517
518518
This gives you an iterator of strings with each item representing one line. When the iterator has no more items, you are done reading all the input. (See also this `concise reference <https://alvinalexander.com/scala/how-to-open-read-text-files-in-scala-cookbook-examples>`_.)
519519

520+
521+
The role of EOF (end of file)
522+
`````````````````````````````
523+
524+
When reading from a file, the iterator naturally exhausts itself when the end of the file is reached.
525+
When reading from an interactive terminal, the user signals end-of-input by pressing **Ctrl-D** (Unix/macOS) or **Ctrl-Z** (Windows), which sends an *EOF* (end-of-file) marker to the standard input stream.
526+
This is the standard Unix convention for signaling "I am done providing input."
527+
528+
In batch pipelines, EOF is sent automatically when the upstream data source closes its output.
529+
For example, when you run:
530+
531+
.. code-block:: bash
532+
533+
$ echo -e "hello\nworld" | myapp
534+
535+
the shell closes the pipe—and thus sends EOF—as soon as ``echo`` finishes writing.
536+
Your Scala app's ``getLines()`` iterator will then return ``false`` from ``hasNext`` and the reading loop terminates cleanly.
537+
538+
A common mistake is to assume that reading "nothing" means EOF; in fact, an empty line is still a valid line (an empty string), and only the exhaustion of the iterator signals EOF.
539+
540+
520541
To break this iterator of lines down into an iterator of words, we can use this recipe:
521542

522543
.. code-block:: scala
@@ -664,9 +685,33 @@ Many REPLs, including the Python and Scala ones, allow the user to edit their in
664685
665686
To add this capability to a Java- or Scala-based command-line app, we can use the `JLine library <https://github.com/jline/jline3>`_, which is the Java equivalent of the `GNU Readline library <https://en.wikipedia.org/wiki/GNU_Readline>`_.
666687
If you want to make your command-line app convenient to use and give it a professional touch, consider using JLine instead of basic console input.
667-
JLine has excellent documentation; please look there for examples.
668688
669-
.. todo:: Determine whether JLine automatically suppresses prompts when redirecting stdin.
689+
To use JLine, add it to your ``build.sbt``::
690+
691+
"org.jline" % "jline" % "4.0.9"
692+
693+
Here is a minimal example that uses JLine to read lines with an editable prompt in Scala:
694+
695+
.. code-block:: scala
696+
697+
import org.jline.reader.LineReaderBuilder
698+
import org.jline.terminal.TerminalBuilder
699+
700+
import scala.util.Success
701+
import scala.util.Try
702+
703+
val terminal = TerminalBuilder.builder().system(true).build()
704+
val reader = LineReaderBuilder.builder().terminal(terminal).build()
705+
706+
Iterator.continually:
707+
Try(reader.readLine("Whatcha got? "))
708+
.takeWhile:
709+
line => line.isSuccess
710+
.foreach:
711+
case Success(line) => println(s"You said: $line")
712+
713+
The ``Try`` wrapper around ``readLine`` ensures that an ``EndOfFileException`` (thrown when the user presses Ctrl-D / EOF) causes the iterator to stop cleanly via ``takeWhile``.
714+
JLine automatically suppresses the prompt when stdin is redirected from a file or pipe, so this code works correctly in both interactive and batch modes.
670715
671716
672717
Finding good third-party libraries
@@ -752,6 +797,43 @@ To use log4s minimally, the following steps are required:
752797
[main] DEBUG edu.luc.cs.cs371.topwords.TopWords - howMany = 10 minLength = 6 lastNWords = 1000
753798
754799
800+
Reporting progress in console applications
801+
``````````````````````````````````````````
802+
803+
For long-running console applications—such as those processing large files or performing iterative computations—it is useful to report progress to the user so they know the application is alive and can estimate how much longer it will take.
804+
805+
The simplest form of progress reporting is to print periodic updates to ``stderr``:
806+
807+
.. code-block:: scala
808+
809+
var count = 0
810+
for item <- items do
811+
count += 1
812+
if count % 1000 == 0 then System.err.println(s"Processed $count items so far...")
813+
// process item ...
814+
815+
For a more polished, terminal-aware progress bar, the `progressbar <https://github.com/ctongfei/progressbar>`_ library provides a convenient solution.
816+
It displays a live-updating progress bar on the terminal but suppresses it automatically when output is redirected (non-interactive mode).
817+
818+
Add the dependency to your ``build.sbt``::
819+
820+
"me.tongfei" % "progressbar" % "0.10.1"
821+
822+
Then use it as follows:
823+
824+
.. code-block:: scala
825+
826+
import me.tongfei.progressbar.ProgressBar
827+
828+
val pb = ProgressBar("Processing", items.size.toLong)
829+
for item <- items do
830+
pb.step()
831+
// process item ...
832+
pb.close()
833+
834+
An example of progress reporting for a Monte Carlo simulation (where the total number of steps is known) can be found `here <https://github.com/LoyolaChicagoBooks/introds-scala-examples/blob/main/montecarlo-scala/GenerateDarts.scala#L28>`_.
835+
836+
755837
.. _subsecConstantSpace:
756838
757839
The importance of constant-space complexity

source/30-objectoriented.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,36 @@ The Thin Cake idiom provides basic DI in Scala without the need for a DI framewo
395395
To recap, ``common.Main`` cannot run on its own but declares by extending ``TreeBuilder`` that it requires an implementation of the ``buildTree`` method.
396396
One of the ``TreeBuilder`` implementation traits, such as ``FoldTreeBuilder`` can satisfy this dependency.
397397
The actual "injection" takes place when we inject, say, ``FoldTreeBuilder`` into ``common.Main`` in the definition of the concrete main object ``fold.Main``.
398+
399+
400+
The Dependency Inversion Principle
401+
````````````````````````````````````
402+
403+
The `Dependency Inversion Principle <https://en.wikipedia.org/wiki/Dependency_inversion_principle>`_ (DIP), one of the SOLID design principles, states:
404+
405+
.. note::
406+
407+
- High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces/traits).
408+
- Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
409+
410+
Applying these ideas to the process tree and iterators examples, we can map the roles as follows:
411+
412+
- **High-level modules**: ``common.Main``, ``TreeBuilderSpec``—these orchestrate the overall application logic.
413+
- **Low-level modules**: ``MutableTreeBuilder``, ``FoldTreeBuilder``—these are specific implementations of a building-block behavior.
414+
- **Abstractions**: the ``TreeBuilder`` trait—this is the shared contract that both the high-level and low-level modules depend on.
415+
- **Details**: the concrete implementations such as ``MutableTreeBuilder``—these know *how* to build a tree but are not imported by high-level modules directly.
416+
417+
Rather than thinking in a strict binary high/low-level classification, it can be more illuminating to arrange the building blocks along a *continuum* from high-level (closer to application intent) to low-level (closer to implementation mechanics):
418+
419+
::
420+
421+
fold.Main / mutable.Main ← concrete main objects (highest level, wires everything together)
422+
common.Main ← application behavior (high-level client)
423+
TreeBuilder ← abstraction / contract
424+
FoldTreeBuilder / MutableTreeBuilder ← implementation details (low-level providers)
425+
426+
The key insight: ``common.Main`` depends only on the *abstraction* ``TreeBuilder``, not on any concrete implementation.
427+
The concrete ``fold.Main`` object is the only place where the two sides are connected ("injected").
428+
This arrangement means we can swap in a different implementation—or a test double—without changing ``common.Main`` at all.
429+
430+

0 commit comments

Comments
 (0)