Skip to content

0.6.0 - foreach, iterator protocol and generic supertypes

Choose a tag to compare

@github-actions github-actions released this 10 Jul 19:20

This release adds multiple bigger features that build on top of each other:

  • wildcard type arguments
  • generic/parametric supertypes
  • iterator protocol
  • foreach syntax

Generic/Parameters Supertypes

It is now possible to inherit from interfaces that have type parameters:

interface Iterable<T> {}

class List<T> : Iterable<T> {}

This might seem like a simlpe change on the surface, but it reached quite deep and enables a ton of possibilities.

A concept for containers and iteration

This release also contains decision for how emerge handles containers and iteration. I decided to go with the same core principles as the D language, adapted to fit emerges idioms.

The key types in emerge are: emerge.core.range.Iterable, emerge.core.range.InputRange<T> and all its subtypes.

Foreach

With a concept for iteration in place it is now also possible to build a foreach syntax. That has now been added to the language:

numbers = [1, 1, 2, 3, 5, 8, 13, 21]
foreach n in numbers {
  StandardOut.put(n.toString())
  StandardOut.putEndOfLine()
}

Foreach is basically syntax sugar, but the important bit is that it defines an iterator protocol, similar to how JavaScript does it, which revolves all around emerge.core.Iterable and emerge.core.InputRange. In the above example, the foreach desugars to this:

_hiddenRange: mut _ = numbers.asRange() // part one of the iterator protocol: Iterable.asRange
do {
  n = try {
    _hiddenRange.front()
  } catch e {
    // part two of the iterator protocol: InputRange.front(), plus using the exception to signal end-of-data
    if e is EmptyRangeException {
      break
    } else {
      throw e
    }
  }

  StandardOut.put(n.toString())
  StandardOut.putEndOfLine()
  _hiddenRange.popFront() // part three of the iterator protocol: advancing to the next element
} while true 

Wildcard type arguments

It is now also possible to specify * as the argument to a type parameter. The compiler will then figure out the bound of that parameter
and the * is treated as out Bound:

// before
val someIterable: Iterable<out Any>

// after, more concise and easier to refactor
val someIterable: Iterable<*>

Other improvements

As usual, this release also contains bug fixes, improves the clarity of various diagnostic messages and refactorings. I want to point out one of these: when you import a non-existing type, you'll only get an error for that import instead of a gazillion errors for all the times you've used it:

import foo.bar.DoesNotExist

fn frobnicate(p: DoesNotExist, x: DoesNotExist) -> DoesNotExist {
  return if p.foo == x.foo p else x
}

Before, you would get 4 errors. One for the import and 3 for the usages. Now its only one.