Up to Main Index Up to Journal for March, 2025
JOURNAL FOR THURSDAY 20TH MARCH, 2025
______________________________________________________________________________
SUBJECT: Lambda functions and higher order filter, map, reduce
DATE: Fri 21 Mar 04:30:34 GMT 2025
> A quick side note: Most of my free time recently has been spent trying to
> find a new job. The majority of the feedback has been absent, not even a
> rejection. So far, 9 weeks job hunting and not a single interview :( If any
> of my readers can help, I’m looking for a 100% remote position as a senior
> or lead Go developer. If possible, I’d like to continue my work integrating
> and building solutions using LLMs and AI.
Day-to-day I use Mote for scripting and quickly whipping up algorithms I want
to play with. Development of Mote mostly consists of writing the tests for the
new uint and float data types at the moment. While essential work, it’s also
monotonous and boring. However, I found myself needing Lambda, or Anonymous,
functions. So, for a change of pace I spent an afternoon last weekend adding
Lambda functions to Mote :)
A quick, simple example:
# displays the approximate square root of n[0.0…10.0] using the
# iterative Heron's / Babylonian method.
for x=0.0; x<10.0; x++
printf "√%g = %2.2f\n" x lambda x -> n
a b <- 1.0 n
for ; abs(a-b) > 1e-9;
a = (a+b) / 2.0
b = n/a
next
return a
end
next
When run, this produces:
√0 = 0.00
√1 = 1.00
√2 = 1.41
√3 = 1.73
√4 = 2.00
√5 = 2.24
√6 = 2.45
√7 = 2.65
√8 = 2.83
√9 = 3.00
The lambda function is invoked directly using “arg… -> param…” to pass
arguments to the lambda’s parameters. The ‘->’ operator is analogous to the
multiple assignment ‘<-’ operator. Also see the later examples of using
filter, map and reduce higher order functions where the lambda is not invoked
directly.
There are a few caveats with the current lambda implementation. The main one
is that a lambda function can only be in-lined if it is the last argument to
an operator, built-in or function. Otherwise it has to be assigned to a
variable and the variable used. Secondly, lambda do not capture their scope.
This means they currently cannot be used to create closures.
Having to have lambda as the last argument leads to some awkward code. For
example, it would be nice to be able to write:
sm = [string](
"add" lambda x y
return x+y
end,
)
Instead we have to write:
sm = [string]()
sm["add"] = lambda x y
return x+y
end
This is because the body of the lambda is left in-situ and routed around, like
a normal function definition. Taking the previous example this is rewritten
to the equivalent of:
sm = [string]()
sm["add"] = Λ0
goto ∙0∙G
Λ0: func x y
return x + y
end
∙0∙G:
Here we can see the generated guarding goto, which prevents the function from
being executed unless explicitly called. We can also see the internal name
generated for the lambda function ‘Λ0’.
For this first iteration, lambda functions work for a lot of use cases. The
limitations can be worked on and improved at a later date. One good use case
is to implement filter, map and reduce as higher order functions. A higher
order function[1] is just a function that takes a function as an argument or
returns a function. The three functions are described next with example
‘fn’ lambda functions:
• filter — applies a function, fn, to each element in an array. Returns an
array containing only those elements where fn returns true. The function
fn should be defined with a single parameter to receive the current
element’s value and return true or false.
lambda x
return x ~m `g$`
end
• map — applies a function, fn, to each element in an array. Returns an
array where each element is replaced with the result of calling fn. The
function fn should be defined with a single parameter to receive the
current element’s value and return the new value for the element.
lambda x
return x ~u `^.`
end
• reduce — applies a function to each element in an array, except the first
element. Returns a single accumulated value. The function fn should be
defined with two parameters: the currently accumulated value and the
current element’s value. fn should return the new accumulated value. The
accumulated value will initially be set to the value of the first element.
lambda acc x
return acc += " "+x
end
Here are my implementations of filter, map and reduce:
func filter data fn
list = dim data[0] 0
range ; v; data
is @fn v; list[] = v
next
return list
end
func map data fn
range x; d; data
data[x] = @fn d
next
return data
end
func reduce data fn
acc = delete data 0
range ; d; data
acc = @fn acc d
next
return acc
end
Here are some examples of using filter, map and reduce with lambda functions:
# String array of test data.
sa = []string "ant" "bat" "cat" "dog" "emu" "fly" "gnu" "hog"
# Apply filter selecting only values that end with a 'g'.
# Displays: ["dog", "hog"]
println @filter sa lambda x
return x ~m `g$`
end
# Apply map to uppercase the first letter of each value.
# Displays: ["Ant", "Bat", "Cat", "Dog", "Emu", "Fly", "Gnu", "Hog"]
println @map sa lambda x
return x ~u `^.`
end
# Apply reduce to data array to return a space separated string of values.
# Displays: ant bat cat dog emu fly gnu hog
println @reduce sa lambda acc x
return acc+" "+x
end
The filter, map & reduce functions work with regular, boring functions too :)
func ccat acc x
return acc+x
end
println @reduce []string("B" "y" "e" "!") ccat
When run, this code displays: Bye!
--
Diddymus
[1] https://en.wikipedia.org/wiki/Higher-order_function
Up to Main Index Up to Journal for March, 2025