README.md
functional-fortran
Functional programming for modern Fortran.
Getting started
git clone https://github.com/wavebitscientific/functional-fortran
cd functional-fortran
mkdir build
cd build
cmake ..
make
ctest
Start using functional-fortran in your code by including the module:
use mod_functional
Why functional-fortran?
While not designed as a purely functional programming language,
modern Fortran goes a long way by letting the programmer
use pure
functions to encourage good functional discipline,
express code in mathematical form, and minimize bug-prone mutable state.
This library provides a set of commonly used tools in functional
programming, with the purpose to help Fortran programmers
be less imperative and more functional.
What's included?
The following functions are provided:
arange
- returns a regularly spaced arraycomplement
- returns a set complement of two arraysfilter
- filters an array using a logical input functionfoldl
- recursively left-folds an array using an input functionfoldr
- recursively right-folds an array using an input functionfoldt
- recursively tree-folds an array using an input functionhead
- returns the first element of an arrayinit
- returns everything but the last elementinsert
- inserts an element into an array, out-of-bound safeintersection
- returns a set intersection of two arraysiterfold
- iteratively reduces an array using an input functionlast
- returns the last element of an arraylimit
- limits a scalar or array by given lower and upper boundsmap
- maps an array with an input functionset
- returns a set given input arrayreverse
- returns array in reverse ordersort
- recursive quicksort using binary tree pivotsplit
- returns first or second half of an arraysubscript
- out-of-bound safe implementation of vector subscripttail
- returns everything but the first elementunfold
- unfolds an array with an input functionunion
- returns a set union of two arrays
All of the above functions are compatible with the standard Fortran 2008 kinds:int8
, int16
, int32
, int64
, real32
, real64
, real128
.
System requirements
functional-fortran builds and passes all tests with:
- GNU Fortran compiler v4.8.3 and higher
- Intel Fortran compiler v15.0.0 and higher
Example usage
Array functions
arange
is used to generate evenly spaced arrays,
given start and end values as input arguments:
write(*,*)arange(1,5)12345
arange
works with real numbers as well:
write(*,*)arange(1.,5.)1.000000002.000000003.000000004.000000005.00000000
Third argument to arange
(optional) is the increment,
which defaults to 1
if not given:
write(*,*)arange(1,15,3)1471013
Negative increments work as expected:
write(*,*)arange(3,1,-1)321
We can use floating-point increments:
write(*,*)arange(1.,1.5,0.1)1.000000001.100000021.200000051.299999951.399999981.50000000
Be mindful of floating-point arithmetic:
write(*,*)arange(1.,1.4,0.1)1.000000001.100000021.200000051.29999995
If start
is greater than end
and increment is positive,arange
returns an empty array:
head
returns the first element of the array:
write(*,*)head([1,2,3])1
tail
returns everything but the first element of the array:
write(*,*)tail([1,2,3])23
Similarly, last
returns the last element of the array:
write(*,*)last([1,2,3])3
init
returns everything but the last element of the array:
write(*,*)init([1,2,3])12
Subscript an array at specific indices:
write(*,*)subscript([1,2,3,4,5],[3,4])34
Unlike Fortran 2008 vector subscript, the subscript
function is out-of-bounds safe,
i.e. subscripting out of bounds returns an empty array:
write(*,*)subscript([1,2,3],[10])
We can prepend, append, or insert an element into an array using insert
:
! insert a 5 at position 0 to prepend:write(*,*)insert(5,0,[1,2,3])5123! insert a 5 at position 4 to append:write(*,*)insert(5,4,[1,2,3])1235! insert a 2 at position 2:write(*,*)insert(2,2,[1,3,4])1234
split
can be used to return first or second half of an array:
! return first half of the arraywrite(*,*)split(arange(1,5),1)12! return second half of the arraywrite(*,*)split(arange(1,5),2)345
The above is useful for recursive binary tree searching or sorting,
for example, see the implementation of sort
in this library.
sort
returns a sorted array in ascending order:
real,dimension(5) :: xcallrandom_number(x)write(*,*)x0.9975595470.5668246750.9659153220.7479276660.367390871write(*,*)sort(x)0.3673908710.5668246750.7479276660.9659153220.997559547
Use reverse
to sort in descending order:
write(*,*)reverse(sort(x))0.9975595470.9659153220.7479276660.5668246750.367390871
The limit
function can be used to contrain a value of a scalar
or an array within a lower and upper limit, for example:
! limit a scalar (5) within bounds 1 and 4write(*,*)limit(5,1,4)4! flipping the bounds works just as wellwrite(*,*)limit(5,1,4)4
limit
also works on arrays:
write(*,*)limit(arange(0,4),1,3):11233
map
, filter
, fold
, unfold
More functional: map
has the same functionality as pure elemental functions,
but can be used to apply recursive functions to arrays, for example:
pure recursiveinteger function fibonacci(n) result(fib)integer,intent(in) :: nif(n == 0)then
fib =0elseif(n == 1)then
fib =1else
fib = fibonacci(n-1)+fibonacci(n-2)endif
endfunction fibonacciwrite(*,*)map(fibonacci,[17,5,13,22])1597523317711
filter
returns array elements that satisfy a logical filtering function.
For example, we can define a function that returns .true. when input is an
even number, and use this function to filter an array:
pure logical function even(x)integer,intent(in) :: x
even =.false.if(mod(x,2) == 0)even =.true.
endfunction evenwrite(*,*)filter(even,[1,2,3,4,5])24
Functions can be chained together into pretty one-liners:
write(*,*)filter(even,map(fibonacci,arange(1,10)))2834
functional-fortran also provides left-, right-, and tree-fold functions,foldl
, foldr
, and foldt
, respectively. These functions recursively
consume an array using a user-defined function, and return a resulting scalar.
For simple examples of sum
and product
functions using folds, we can define
the following addition and multiplication functions that operate on scalars:
pure real function add(x,y)real,intent(in) :: x,y
add = x+y
endfunction add
pure real function mult(x,y)real,intent(in) :: x,y
mult = x*y
endfunction mult
We can then calculate the sum
and product
of an array by "folding" the
input using the above-defined functions and a start value
(second argument to fold*
):
! left-fold an array using add to compute array sumwrite(*,*)foldl(add,0.,arange(1.,5.))15.0000000! left-fold an array using mult to compute array productwrite(*,*)foldl(mult,1.,arange(1.,5.))120.000000
The above is a trivial example that re-invents Fortran intrinsics as a proof of concept. Intrinsic functions should of course be used whenever possible.
foldl
, foldr
, and foldt
return the same result if the user-defined
function is associative. See the Wikipedia page on fold for more information.iterfold
is an iterative (non-recursive) implementation of foldl
that is provided for reference.
Opposite to fold*
, unfold
can be used to generate an array
based on a start value x
, and a function f
, such that
the resulting array equals [x, f(x), f(f(x)), f(f(f(x))), ... ]
.
For example:
pure real function multpt1(x)real,intent(in) :: x
multpt1 =1.1*x
endfunction multpt1write(*,*)unfold(multpt1,[1.],5)1.000000001.100000021.210000041.331000091.46410012
set
, union
, intersection
, complement
Set functions: Function set
returns all unique elements of an input array:
write(*,*)set([1,1,2,2,3])123
Common functions that operate on sets, union
, intersection
, and complement
, are also available:
! unique elements that are found in either arraywrite(*,*)union([1,2,2],[2,3,3,4])1234! unique elements that are found in both arrayswrite(*,*)intersection([1,2,2],[2,3,3,4])2! unique elements that are found first but not in second arraywrite(*,*)complement([1,2,2],[2,3,3,4])1
Contributing
Please submit a bug report or a request for new feature here.