In computer science, a generator is a routine that can be used to control the iteration behaviour of a loop. All generators are also iterators.

A generator is very similar to a function that returns an array, in that a generator has parameters, can be called, and generates a sequence of values. However, instead of building an array containing all the values and returning them all at once, a generator yields the values one at a time, which requires less memory and allows the caller to get started processing the first few values immediately.

In short, a generator looks like a function but behaves like an iterator.

From Wikipedia

Note: A list is a collection of values, while a generator is a recipe for producing values.

To understand generators in Go, you have to understand the concept ofGo channels. Also, since the first implementation of Julia uses channels, let's first understand Channels in Julia
go
js
dart
python
julia
package main

import "fmt"

/*
Let's create a function which returns the channel, <-chan,
whenever called until the channel is closed. We can then 
call the function in a for loop, and receive data, not all 
at once, but in every iteration.
*/
func gen() <-chan int {
  c := make(chan int)

  go func() {
    for i := 0; i < 5; i++ {
      c <- i
    }
    close(c)
  }()

  return c
}

func main() {
  g := gen()
  for number := range g {
    fmt.Println("The data is", number)
  }
}
function *gen(): Generator<number> {
  for (let i = 0; i < 5; i++){
    yield i;
  }
}

let iterator = gen()
let results: IteratorResult<number, any>;

for (const result of iterator) {
  console.log(`The data is ${result}`)
}

// Below is an iterator implemented using generator
// classes are taught in the next pages
class Sequence {
    start: number
    end: number 
    interval: number
    constructor(start = 0, end = Infinity, interval = 1) {
        this.start = start;
        this.end = end;
        this.interval = interval;
    }
    * [Symbol.iterator]() {
        for( let index = this.start; index <= this.end; index += this.interval ) {
            yield index;
        }
    }
}

let oddNumbers = new Sequence(1, 15, 2);

for (const num of oddNumbers) {
    console.log(num);
}
// Generators as iterable
void main() {
    var numbers = gen();
    for (final num in numbers){
      print("The data is $num");
    }
}
/*
* To implement a synchronous generator function, 
* mark the function body as sync*, and use yield 
* statements to deliver values
* */
Iterable<int> gen() sync* {
  for (var i = 0; i < 5; i++){
    yield i; 
  }
}
// Generators as stream == Asynchronous Generators
void main() async {        
    var numbers = gen();
    await for (final num in numbers){
      print("The data is $num ==");
    }
}
Stream<int> gen() async* {
    for (var i = 0; i < 5; i++){
        yield i; 
    }
}
from typing import Iterator

def gen() -> Iterator[int]:
    for i in range(5):
        yield i

numbers = gen()

for num in numbers:
    print(f"The data is {num}")

# Generators can also be implemented this way
square_numbers = (i**2 for i in range(5))
print(type(square_numbers)) # <class 'generator'>
for num in square_numbers:
    print(num)
"""
Generators are a great way to optimize memory. 
However, if list is smaller than the the running 
machine's available memory, then list comprehensions 
can be faster to evaluate than the equivalent generator expression.
"""
function generator()
    Channel() do channel
        for i in 0:5
            put!(channel, i)
        end
    end
end

g = generator()

for num in [g...]
    println("The data is $num")
end

# Generators can also be implemented this way
g = (i for i in 0:5)
for num in [g...]
    println("The data is $num")
end