Found out last night that Array.reduce in Godot 4.4 does not work the way I would have expected coming from a functional programming background.

I wanted to write a function which would fold over a list and return best suitable match based on some criteria or null if none of those results were appropriate. However, I found that when I used reduce, it would keep picking an inappropriate result (which happened to be at the front of the list).

According to the documentation for reduce, this is because of how the accum parameter works:

The method takes two arguments: the current value of accum and the current array element. If accum is null (as by default), the iteration will start from the second element, with the first one used as initial value of accum.

Which means that even though reduce has the same type signature as foldl, if accum happens to be null it turns into what would be a foldl1 in Haskell. The solution was to use a for loop instead.

A chatter Reaganomicon mentioned that JavaScript, F#, and Python’s functools also implement reduce in the same way.

JavaScript does implement reduce in a similar way:

The value resulting from the previous call to callbackFn. On the first call, its value is initialValue if the latter is specified; otherwise its value is array[0].

But, if you call reduce with null or undefined, the function works correctly (at least in Firefox 136.0):

console.log([1].reduce((acc, v) => null, null))
console.log([1].reduce((acc, v) => null, undefined))
console.log([1].reduce((acc, v) => null))

Prints:

null
null
1

This is probably because JavaScript functions can check how many arguments a user defined even if one of those arguments is undefined.

F# also does it correctly as it has both Array.fold folder state array and Array.reduce reduction array (which have the same type signatures as foldl and foldl1 respectively in Haskell)

And it turns out that functools in Python 3 also implements reduce correctly:

import functools
 
print(functools.reduce(lambda x, y: None, [1], None))
 
print(functools.reduce(lambda x, y: None, [], 1))

Prints:

None
1