Range Over Iterators in Fortran

Starting with version 1.23, Go has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example we had an AllElements method that returned a slice of all elements in the list. With Go iterators, we can do it better - as shown below.

module ListModule
   implicit none
   type :: element
      integer :: val
      type(element), pointer :: next => null()
   end type element
   type :: List
      type(element), pointer :: head => null(), tail => null()
   contains
      procedure, pass(this) :: Push => PushElement
      procedure, pass(this) :: All => AllElements
   end type List
contains
   subroutine PushElement(this, v)
      class(List), intent(inout) :: this
      integer, intent(in) :: v
      type(element), pointer :: newElement

      allocate(newElement)
      newElement%val = v
      newElement%next => null()

      if (.not. associated(this%tail)) then
         this%head => newElement
         this%tail => newElement
      else
         this%tail%next => newElement
         this%tail => newElement
      end if
   end subroutine PushElement

   subroutine AllElements(this, yield)
      class(List), intent(inout) :: this
      logical, external :: yield
      type(element), pointer :: e

      e => this%head
      do while (associated(e))
         if(.not. yield(e%val)) return
         e => e%next
      end do
   end subroutine AllElements
end module ListModule

All returns an iterator, which in Fortran can be represented with a multitasking technique, such as a subroutine that takes another function as a parameter.

The iterator function takes another function as a parameter, called yield by convention. It will call yield for every element we want to iterate over, and note yield’s return value for a potential early termination.

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as yield keeps returning true.

subroutine genFib(yield)
   logical, external :: yield
   integer :: a, b, t

   a = 1
   b = 1
   do
      if(.not. yield(a)) return
      t = a + b
      a = b
      b = t
   end do
end subroutine genFib

program main
   use :: ListModule
   implicit none
   type(List) :: lst
   logical :: yieldIterator
  
   lst%Push(10)
   lst%Push(13)
   lst%Push(23)
  
   call lst%All(yieldIterator)
end program

logical function yieldIterator(val)
   integer, intent(in) :: val
   print *, val
   yieldIterator = .true.
end function

program main
   implicit none
   logical :: yieldFib
   call genFib(yieldFib)
end program

logical function yieldFib(val)
   integer, intent(in) :: val
   if(val >= 10) then
      yieldFib = .false.
   else
      print *, val
      yieldFib = .true.
   end if
end function

The output would be:

10
13
23
1
1
2
3
5
8

Now that we can iterate over list elements and generate Fibonacci numbers iteratively in Fortran, let’s learn more about the language.