Skip to content
Machine learning / embedPy

Closures and generators

Closures

Closures allow us to define functions that retain state between successive calls, avoiding the need for global variables.

To create a closure in embedPy, we must:

  1. Define a function in q with
    • 2+ arguments: the current state and at least one other (possibly dummy) argument
    • 2 return values: the new state and the return value
  2. Wrap the function using .p.closure, which takes 2 arguments:
    • the q function
    • the initial state

Functions without arguments The dummy argument is needed if we want the resulting function to take no arguments.

Example 1: til

We can define a closure to return incrementing natural numbers, similar to the q til function.

The state x is the last value returned

q)xtil:{[x;dummy]x,x+:1}

Create the closure with initial state -1, so the first value returned will be 0

q)ftil:.p.closure[xtil;-1][<]
q)ftil[]
0
q)ftil[]
1
q)ftil[]
2
q)ftil[]
3

Example 2: Fibonacci

The Fibonacci sequence is a sequence in which each number is the sum of the two numbers preceding it.
Starting with 0 and 1, the sequence goes x(n) = x(n-1) + x(n-2)

i.e. 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

The state x will be the last two values produced.

q)xfib:{[x;dummy](x[1],r;r:sum x)}

Create the closure with initial state 0 1, so the first value produced will be 1.

q)fib:.p.closure[xfib;0 1][<]
q)fib[]
1
q)fib[]
2
q)fib[]
3
q)fib[]
5
q)fib[]
8
q)fib[]
13

Example 3: Running sum

In this example, we will allow a numeric argument to be passed to the closure, removing the need for a dummy argument. The closure will keep track of all arguments passed so far, and return a running sum.

The state x will be the total so far.

q)xrunsum:{x,x+:y}

Create the closure with initial state 0, so the first value produced will be the first argument passed.

q)runsum:.p.closure[xrunsum;0][<]
q)runsum 2
2
q)runsum 3
5
q)runsum -2.5
2.5
q)runsum 0
2.5
q)runsum 10
12.5

Generators

Generators are objects that we can iterate over (e.g. in a for-loop) to produce sequences of values.
EmbedPy allows us to produce generators for use in Python functions and statements where they are required.

To create a generator in embedPy, we must

  1. Define a function in q (as per closures) with:
    • 2 arguments - the current state and a dummy argument
    • 2 return values - the new state and the return value
  2. Wrap the function using .p.generator, which takes 3 arguments:
    • the q function
    • the initial state
    • the max number of iterations, or :: to run indefinitely

Example 1: Factorials

The factorial (n!) of a non-negative integer n, is the product of all positive integers less than or equal to n.

We can create a sequence of factorials (starting with 1), with the sequence x(n) = x(n-1) * n

The state x will be a 2-item list comprising

  • the last value used in the product
  • the last value returned
q)xfact:{[x;dummy](x;last x:prds x+1 0)}

Create two generators, each with initial state 0 1.

q)fact4:.p.generator[xfact;0 1;4]     / generates first 4 factorial values
q)factinf:.p.generator[xfact;0 1;::]  / generates factorial values indefinitely

The resulting generators can be used as iterators in Python.

q).p.set[`fact4]fact4
q)p)for x in fact4:print(x)
1
2
6
24
q).p.set[`factinf]factinf
q).p.e"for x in factinf:\n print(x)\n if x>1000:break"  / force break to stop iteration
1
2
6
24
120
720
5040

Example 2: Look-and-say

The look-and-say sequence is the sequence of integers beginning as follows: 1, 11, 21, 1211, 111221, 312211, 13112221, 1113213211

  • 1 is read off as “one 1” or 11.
  • 11 is read off as “two 1s” or 21.
  • 21 is read off as “one 2, then one 1” or 1211.
  • 1211 is read off as “one 1, one 2, then two 1s” or 111221

The state x will be the last value produced.

q)xlook:{[x;dummy]r,r:"J"$raze string[count each s],'first each s:(where differ s)_s:string x}

Create a generator (to run for 7 iterations) with initial state 1, so the first value produced will be 11.

q)look:.p.generator[xlook;1;7]

This can be used as an iterator in Python.

q).p.set[`look]look
q)p)for x in look:print(x)
11
21
1211
111221
312211
13112221
1113213211

Example 3: Successive sublists

We can define a closure to extract successive sublists, of a given size, from a larger list.

The state x will be a 3-item list comprising

  • the list
  • the start index
  • the sublist size
q)xsub:{[x;d](@[x;1;+;x 2];sublist[x 1 2]x 0)}

To create a generator (to run for 6 iterations), extracting sublists of size 6 from .Q.A (list of 26 alphabetical chars) q q)sub:.p.generator[xsub;(.Q.A;0;6);6] This can be used as an iterator in Python.

q).p.set[`sub]sub
q)p)for x in sub:print(x)
ABCDEF
GHIJKL
MNOPQR
STUVWX
YZ

q)