Higher Order Functions in VBA, the dirty way

I have been building a small library of VBA functions. I have a small section with higher order functions that I'd like to share. It uses strings as delegates/function pointers and Application.Run to actually call the functions.

This isn't by any means a true implementation; just a quick and dirty implementation without connecting external libraries or creating objects for every function. I haven't found real drawbacks yet. Application.Run always passes arguments by value, which would be a draw back, but is desirable in all of these implementations. Also not being able to declare lambdas is a buzz-kill.

Public Sub Map(ByVal delegate As String, ByRef sequence As Variant)

Dim i As Long
For i = LBound(sequence) To UBound(sequence)

sequence(i) = Application.Run(delegate, sequence(i))

Next i

End Sub
Public Function Mapped(ByVal delegate As String, ByVal sequence As Variant) As Variant

Mapped = sequence
Map delegate, Mapped

End Function
Public Function Fold(ByVal delegate As String, ByVal sequence As Variant, _
ByVal initial_value As Variant) As Variant

Fold = initial_value

Dim el As Variant
For Each el In sequence

Fold = Application.Run(delegate, Fold, el)

Next el

End Function
Public Function Reduce(ByVal delegate As String, ByVal sequence As Variant) As Variant

' Head and Tail return the first element and the rest, respectively.

End Function
Public Function Compose(ByVal delegates As Variant, ByVal initial_value As Variant) As Variant

Compose = initial_value

Dim delegate As Variant
For Each delegate In delegates

Compose = Application.Run(delegate, Compose)

Next delegate

End Function


Things I like

• This idea.
• You've explicitly declared ByVal and ByRef.
• All methods are short, clear, and concise.

Things to watch out for

• All of these functions take variants in as parameters, by necessity. Client code runs the risk of passing in a sequence that isn't enumerable. These really need a comment and maybe a VB_Description attribute describing what kind of arguments the functions expect.
• You also may want to consider handling runtime error 438 "Object doesn't support Method" for the functions that use For Each loops. There's not much you can do other than return a more meaningful error message though.
• If you change the loop in Map to a For Each and manually increment i, all of the functions will be compatible with collections as well as arrays.

Nitpicks

• Underscores have a special meaning. They indicate event procedures. So I would change initial_value to initialValue.
• While I quickly understood that el stood for element, there's no reason not to just spell it out.

you have extra newlines in your for loops, and other blocks of code, like this

For Each el In sequence

Fold = Application.Run(delegate, Fold, el)

Next el


Remove them

For Each el In sequence
Fold = Application.Run(delegate, Fold, el)
Next el


There isn't the need for them.

your functions and subs also look like this,

Public Function Mapped(ByVal delegate As String, ByVal sequence As Variant) As Variant

Mapped = sequence
Map delegate, Mapped

End Function


it looks like you think something needs to be added but you didn't put in any comments to say what you are thinking about...

Public Function Mapped(ByVal delegate As String, ByVal sequence As Variant) As Variant
Mapped = sequence
Map delegate, Mapped
End Function


is what this should look like.

• Am I correct in assuming this is just a personal preference? I did remove blank lines until I was instructed to do differently. To be honest, I rather like using new lines to delineate different logical steps. – cheezsteak Aug 15 '14 at 18:08
• @ptwales, that was all in the same Sub, your subs and functions don't have that much in them that they need to be separated to distinguish between steps. I would space between the functions and subs though, that makes sense. – Malachi Aug 15 '14 at 18:21
• I would definitely class it under personal preference. – Kaz Dec 18 '15 at 14:47