v1.0 - Download
gmFunctional is a script library to enable functional style programming in Game Maker.
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.
- Many useful built-in functions - Most commonly used functions (based on Haskell's) are included and written in native non-recursive GML.
- Low dependancy - Almost all included scripts can be used standalone (if you remove disposes).
- 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 (as lambdas).
- Ds_list / string duallity - The included suite of scripts accept both strings and ds_lists.
- Mathematical correctness - In theory, functionally written expressions are provable to some extent.
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 you will see that they will likely become very intuitive and natural by 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 learn you a haskell.
- 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 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.
- 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. Not sure what to do here yet.
- 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 if a list element is an integer or if it is another list.
- My documentation is a bit thin. I'll get round to making it better. Maybe.
- Probably loads more I forgot or I'm not aware of.
v1.0 - Download
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 howz are you x"," "),"len(.x) > 3") = ["hello", "howz"]
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"
Edited by OpticalLiam, 27 March 2010 - 03:22 PM.