The Big Book of Small EK9 Programs

Adapted from Al Sweigart's The Big Book of Small Python Projects: 81 Easy Practice Programs (No Starch Press, 2021; copyright © 2021 Al Sweigart), which he makes freely available under a Creative Commons Attribution-NonCommercial-ShareAlike licence at inventwithpython.com/bigbookpython. Changes were made: each program was ported from Python to EK9 and remodelled in EK9's idioms.

Project 56 — Prime Numbers

This is project 56 of the book, ported to EK9. Its chapter is still being written; until then, this page shows the port itself: a run of it, the whole program, and how it keeps to the original.

A sample run

The player's answers are piped in:

0

Run it with:

ek9 primenumbers.ek9 < input.txt

It prints:

Prime Numbers, by Al Sweigart al@inventwithpython.com
Prime numbers are numbers that are only evenly divisible by
one and themselves. They are used in a variety of practical
applications, but cannot be predicted. They must be
calculated one at a time.

Enter a number to start searching for primes from:
(Try 0 or 1000000000000 (12 zeros) or another number.)
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 

The program

#!ek9
defines module bigbook.primenumbers

  defines constant

    SmallestPrime <- 2

    //The original prints primes until Ctrl-C. A golden needs an end, so the port shows this many.
    PrimesShown <- 15

  defines class

    //THE GAME's state: the two counts the search walks through, each a stream source.

    //Every whole number from a start upwards, for ever: the original's `while True: num += 1`
    //as a stream source. A stream only pulls what a later stage asks for, so `head` ends it.
    NumbersFrom
      current as Integer?

      default private NumbersFrom() as pure

      NumbersFrom() as pure
        -> start as Integer
        current :=? Integer(start)

      hasNext() as pure
        <- rtn as Boolean: true

      //A copy: `current += 1` changes the Integer in place, so handing out `current` itself
      //would change the number the stream had already passed on.
      next()
        <- rtn as Integer: Integer(current)
        current += 1

      default operator ?

    //The divisors worth trying: 2, 3, ... while divisor * divisor <= number, the same bound as
    //the original's range(2, int(math.sqrt(number)) + 1), with no Float square root.
    TrialDivisors
      number as Integer?
      divisor as Integer: Integer(SmallestPrime)

      default private TrialDivisors() as pure

      TrialDivisors() as pure
        -> forNumber as Integer
        number :=? forNumber

      hasNext() as pure
        <- rtn as Boolean: divisor * divisor <= number

      next()
        <- rtn as Integer: Integer(divisor)
        divisor += 1

      default operator ?

    //THE input idiom of this book, copied unchanged from lib/LineInput.ek9 (the reasons are
    //there): read with next(), and every input loop also stops when ended() is true.
    LineInput
      stdin as Stdin: Stdin()
      inputEnded as Boolean: false

      next()
        <- rtn as String: stdin.next()
        if not rtn?
          inputEnded: true

      ended() as pure
        <- rtn as Boolean: Boolean(inputEnded)

      override operator ? as pure
        <- rtn as Boolean: true

    //THE INPUT AND OUTPUT: everything the player sees or types goes through here, worded by Say.
    Console
      stdout as Stdout: Stdout()
      input as LineInput: LineInput()
      say as Say: Say("en")

      Console()
        -> words as Say
        say: words

      showIntroduction()
        stdout.println(say.introduction())

      //Asks until the game accepts a number to start from; unset only if the input ends first.
      nextStart()
        <- rtn as Integer: Integer()
        while not rtn? and not input.ended()
          stdout.println(say.askStart())
          if entry <- input.next()
            rtn: startOf(entry)

      //The original prints `str(num) + ', '` for each prime, so the line ends with ", " too.
      showPrimes()
        -> primes as List of Integer
        listing <- cat primes | map with listed | collect as String
        stdout.println(listing)

      override operator ? as pure
        <- rtn as Boolean: true

  defines function

    //THE GAME: the arithmetic, with no input or output.

    //A whole number of zero or more, or unset for anything else: Integer(text) is unset for
    //anything that is not a whole number, so one guard does the original's isdecimal() check.
    startOf() as pure
      -> entry as String
      <- rtn as Integer: Integer()
      if number <- Integer(entry.trim()) then number >= 0
        rtn: number

    //Prime when no trial divisor divides it. `head 1` stops at the first divisor found, which
    //is the original's early `return False`, so the test can be pure: no counter to mutate.
    isPrime() as pure
      -> number as Integer
      <- rtn as Boolean: number >= SmallestPrime
      if rtn
        divides <- (number) is Predicate of Integer as pure function
          r: number rem t == 0
        firstDivisor <- cat TrialDivisors(number) | filter by divides | head 1 | collect as List of Integer
        rtn: firstDivisor.empty()

    //The original's endless search, ended by `head`.
    primesFrom() as pure
      -> start as Integer
      <- rtn as List of Integer: cat NumbersFrom(start)
        | filter by isPrime
        | head PrimesShown
        | collect as List of Integer

  defines function

    listed() as pure
      -> prime as Integer
      <- rtn as String: `${prime}, `

    //THE language of the words, copied unchanged from lib/Language.ek9: BIGBOOK_LANG when it
    //is set (fr, zh, ...), otherwise en.
    language()
      <- rtn as String: EnvVars().get("BIGBOOK_LANG") ?: "en"

  //THE WORDS: every word the player sees.
  defines text for "en"

    Say

      introduction()
        `Prime Numbers, by Al Sweigart al@inventwithpython.com
Prime numbers are numbers that are only evenly divisible by
one and themselves. They are used in a variety of practical
applications, but cannot be predicted. They must be
calculated one at a time.
`

      askStart()
        `Enter a number to start searching for primes from:
(Try 0 or 1000000000000 (12 zeros) or another number.)`

  defines program

    //Prime Numbers (Big Book project 56). Finds the prime numbers from a number the player chooses.
    //The game decides; the Console asks and tells, in the player's words.
    PrimeNumbers()
      //A language with no words of its own is unset: ?: falls back to English.
      console <- Console(Say(language()) ?: Say("en"))

      console.showIntroduction()
      //No start means the input ended first: there is nothing to search from.
      if start <- console.nextStart()
        console.showPrimes(primesFrom(start))

About this port