gmFunctional is a script library to enable functional style programming in Game Maker, much like Python or Haskell. It is suitable for whenever you wish to process strings or lists, or do mathematical computations. It reduces the amount of code you need to write as most of the leg work is handled for you.

Download gmFunctional v1.0







Features

  • Many useful built-in functions
    Most commonly used functions (based on Haskell’s) are included and written in fast native non-recursive GML.
  • Low dependancy
    Almost all included scripts can be used standalone.
  • Low verbosity
    Intuitive single word script names enhance readabillity and reduce code length.
  • Chainable functions
    Complex functions can often be written as one-liners.
  • Higher-Order functions
    Common recursion patterns are captured by the functions Map, Filter and Fold.
  • Anonymous (Lambda) functions
    Anonymous functions can be passed by value as strings.
  • Currying (function composition)
    Functions can be composed to create new ones.
  • Ds_list / string duallity
    The included suite of scripts accept both strings (lists of characters) and ds_lists.
  • Mathematical correctness
    In theory, functionally written expressions are provable to some extent.

Functional Programming

Functional programming is a different programming paradigm than most programmers are used to, especially those that have only used GML. In GML (and other imperative languages) you describe how to perform a computation, as steps of an algorithm. In functional programming you declare (or define) what a result is by a set of equations (the functions). At first, coming from imperative programming, functional code will probably seem very unintuitive and unreadable. When you eventually get your head around how functional programming works it will likely become very intuitive and natural in the end. I am not going to go into any more detail about functional programming as it’s a huge topic. If you wish to learn how to program functionally I strongly reccommend that you learn Haskell with the aid of the brilliant tutorial .


Important Notes

  • The term function in this library means either a script or a lambda.
  • The term list in this library means either a ds_list or a string (where a string is a list of characters).
  • The term element in this library means an element of a list.
  • It should be assumed (for most library functions) that once a function is applied to a list, that list is disposed and destroyed when the gc is run.
  • But know that some functions in reality modify the given list and return it (for performance reasons), others create new lists (and dispose the old one). See Issues.
  • This library is only meant for functional programming style , it is far from truly functional (as GM is way too limited).
  • Functions that take lists always do so as the first argument (unlike Haskell). The reason is to do with how the higher-order functions (you’ll see).
  • The garbage collector (gc) should be run at the end of your computation to destroy unused (disposed) lists.
  • Explicit recursion with scripts is doable, but always use higher-order functions (map, filter, fold) instead where possible.
  • Use scripts in higher-order functions (over lambdas) where possible as they are significantly faster to execute.
  • When using strings as lists, the first element (character) index is 0 (not 1 as it is in GM string functions)
  • Lambda expressions are in the form of strings. They may contain anything that can be excuted by GM.
  • They use special variables preceded by a dot to be matched as arguments.
  • Arguments passed to the function correlate to the special variables ".a", ".b", ".c" through to ".o" (argument0 through to argument15).
  • Aliases are given for ".a" = ".x" = ".n" and ".b" = ".y" and ".c" = ".z" .
  • GM8 can give a notable performance boost when using this library.

Known Issues

The fact that some functions modify old lists (for performance reasons) means there may be unwanted side effects with a few functions (such as tail() ) if you are not careful. This is a tricky issue that I’m not sure what I’m going to do about yet (other than assume non-mutabillity and sacrifice performance).

It is also kind of inconsistent in which functions dispose the lists given to them and which don’t. I’m not sure why, for some reason it feels natural that some functions do dispose and some don’t. There’s currently no real strategy to it. This means there’s probably going to be memory leaks.

Performance is acceptable, but explicit recursion via scripts and the execution of lambdas may be fairly slow.

Currying (function composition) via o(a,b) has issues with internal scope. It works for some things, but for others it needs fixing.

Support for nested lists is extremely tentative. As lists are passed as integers, its virtually impossible to programatically tell in GM if a list element is an integer or if it is another list.


Usage Examples (GML)

list ( 5 , 1 , 2 , 3 , 4 , 5 ) == [ 1 , 2 , 3 , 4 , 5 ]
map ( list ( 3 , 1 , 2 , 3 ) , add , 10 ) == [ 11 , 12 , 13 ]
map ( "hello how are you" , "chr(ord(.x)-.y)" , 1 ) == "gdkk gnv `qd xnt"
map ( list ( 3 , 1 , 2 , 3 ) , ".x/2" ) == [ 0.5 , 1 , 1.5 ]
filter ( list ( 10 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) , factor , 3 ) == [ 3 , 6 , 9 ]
filter ( list ( 10 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) , even ) == [ 2 , 4 , 6 , 8 , 10 ]
filter ( split ( "hello how are you x" , " " ) , "len(.x) > 3" ) == [ "hello" , "how" ]
foldl ( "abcdefghijk" , ".x + .z + .y" , " " ) == "a b c d e f g h i j k"
foldl ( list ( 10 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) , add ) == 55
foldr ( "hello how are you" , cnc ) == "uoy era woh olleh"
head ( list ( 6 , 1 , 2 , 3 , 4 , 5 , 6 ) ) == 1
tail ( list ( 6 , 1 , 2 , 3 , 4 , 5 , 6 ) ) == [ 2 , 3 , 4 , 5 , 6 ]
head ( "hello" ) == "h"
tail ( "hello" ) == "ello"
cnc ( list ( 5 , 1 , 2 , 3 , 4 , 5 ) , list ( 5 , 6 , 7 , 8 , 9 , 10 ) ) == [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]
range ( 5 ) == [ 0 , 1 , 2 , 3 , 4 , 5 ]
range ( 5 , 20 , 2 ) == [ 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 ]
sequence ( 0 , 10 , ".n*.n + 2*.n" ) == [ 0 , 3 , 8 , 15 , 24 , 35 , 48 , 63 , 80 , 99 , 120 ]
sequence ( 0 , 10 , multiply , 10 ) == [ 0 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 , 100 ]
split ( "hello how are you" , " " ) == [ "hello" , "how" , "are" , "you" ]
f ( ".x + .y" , 1 , 2 ) == 1 + 2 == 3
f ( add , 1 , 2 ) == 1 + 2 == 3
elem ( "hello world" , "w" ) == true
drop ( list ( 10 , 34 , 3 , 43 , 53 , 3 , 45 , 4 , 42 , 12 , 8 , 33 , 1 ) , 3 ) == [ 53 , 3 , 45 , 4 , 42 , 12 , 8 , 33 , 1 ]
take ( list ( 10 , 34 , 3 , 43 , 53 , 3 , 45 , 4 , 42 , 12 , 8 , 33 , 1 ) , 3 ) == [ 34 , 3 , 43 ]
any ( list ( 3 , true , false , false ) ) == true
any ( list ( 3 , false , false , false ) ) == false
all ( list ( 3 , true , false , false ) ) == false
all ( list ( 3 , false , false , false ) ) == false
all ( list ( 3 , true , true , true ) ) == true
str ( map ( range ( 97 , 96 + 26 ) , "chr(.x)" ) ) == "abcdefghijklmnopqrstuvwxyz"

Download gmFunctional v1.0