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