There is no doubt that the IEnumerable
type from C# is very powerful.
It's especially effective when the data we would like to iterate has yet to be aquired or calculated.
To state a matching example, the fibonacci sequence is one of them.
There we have a sequence with numbers depending on the numbers beforehand. So the next number is the sum of the two previous ones. I guess everyone is familiar with this sequence - here it is for numbers up to 100:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
When calculating this sequence and storing it using an Array
datatype, we would have to generate these numbers and allocate memory.
Furthermore, we would have to figure out how many fibonacci numbers we would like to generate beforehand,
because the fibonacci sequence is basically infinite, but our Array
is not.
Of course, one could do this without allocating a new Array
, but then you have to implement the generation code in the same method.
static void Main(string[] args)
{
// a loop for printing the numbers
foreach (var x in GetNumbersArray(12))
{
if (x > 100)
break;
Console.WriteLine(x);
}
}
static int[] GetNumbersArray(int i)
{
int[] result = new int[i];
var (a, b) = (0, 1);
// and another loop for generating the numbers
for (var counter = 1; counter < i; counter++)
{
// logic without allocating a new array
// would have to be implemented here
(a, b) = (a + b, a);
result[counter] = a;
}
return result;
}
Here the C# IEnumerable
type helps out. It combines the best of the two worlds. No extra memory has to be allocated and the computation runs as we request the next item. In addition, no loops takes place more than once (as it is the case with the Array
).
So the solution using the IEnumerable
would look like this:
static void Main(string[] args)
{
// request the next number by iterating
foreach (var x in GetNumbers())
{
// break the loop when we reach more than 100
if (x > 100)
break;
Console.WriteLine(x);
}
}
static IEnumerable<int> GetNumbers()
{
var (a, b) = (0, 1);
while (true)
{
(a, b) = (a + b, a);
// return the next number as soon as requested
// using the yield statement
yield return a;
}
}
No memory gets allocated to store the sequence, and the caller of the GetNumbers()
could decide when to break the loop.
Translating to go
In Go we do not have something like the IEnumerable
in C#, in fact we have no chance to create a custom type that allows us to be iterated by implementing an interface. In C# we could achieve this by using the IEnumerator
interface. Of course we do not have to reinvent the wheel, everything we have to do is rebuild the interface from scratch using custom types and access methods.
type Fibonacci struct {
a int
b int
}
// Get return the current value to print
func (f *Fibonacci) Get() int {
return f.a
}
// Next calculates the next value
// and returns false if there are no more values
func (f *Fibonacci) Next() bool {
f.a, f.b = f.a+f.b, f.a
return true
}
// NewFibonacci initializes a new Fibonacci type
func NewFibonacci() Fibonacci {
return Fibonacci{
a: 0,
b: 1,
}
}
func main() {
sequence := NewFibonacci()
for sequence.Next() {
if x := sequence.Get(); x < 100 {
fmt.Println(x)
} else {
break
}
}
}
This is now the same way as C# IEnumerator
is implemented under the hood. We are able to iterate using the Next()
method and access the current value using the Get()
method. It perfectly fits our needs and satisfies our requirements. No additional memory has to be allocated and no loop is executed twice. Using this blueprint, every usecase for IEnumerable
can be solved in Go. In fact, it is already in use in Go's standard library.
One example implementation would be the
bufio.Scanner
type - https://pkg.go.dev/bufio#Scanner
Top comments (2)
A nice example. I'm not familiar with C# but that looks relatable. Would you mind if i reference this article & create another article with same context but in reference to JavaScript??
No, I am, absolute looking forward to this! Thanks for your kudos.