lyng/docs/EfficientIterables.md

3.8 KiB

Efficient Iterables in Kotlin Interop

Lyng provides high-performance iteration mechanisms that allow Kotlin-side code to interact with Lyng iterables efficiently and vice versa.

1. Enumerating Lyng Objects from Kotlin

To iterate over a Lyng object (like a List, Set, or Range) from Kotlin code, use the virtual enumerate method:

val lyngList: Obj = ... 
lyngList.enumerate(scope) { item ->
    println("Processing $item")
    true // return true to continue, false to break
}

Why it's efficient:

  • Zero allocation: Unlike traditional iterators, it doesn't create a LyngIterator object or any intermediate wrappers.
  • Direct access: Subclasses like ObjList override enumerate to iterate directly over their internal Kotlin collections.
  • Reduced overhead: It avoids multiple invokeInstanceMethod calls for hasNext() and next() on every step, which would normally involve dynamic dispatch and scope overhead.

2. Reactive Enumeration with Flow

If you prefer a reactive approach or need to integrate with Kotlin Coroutines flows, use toFlow():

lyngList.toFlow(scope).collect { item ->
    // ...
}

Note: toFlow() internally uses the Lyng iterator protocol (iterator(), hasNext(), next()), so it's slightly less efficient than enumerate() for performance-critical loops, but more idiomatic for flow-based processing.

3. Creating Efficient Iterables for Lyng in Kotlin

When implementing a custom object in Kotlin that should be iterable in Lyng (e.g., usable in for (x in myObj) { ... }), follow these steps to ensure maximum performance.

A. Inherit from Obj and use ObjIterable

Ensure your object's class has ObjIterable as a parent so the Lyng compiler recognizes it as an iterable.

class MyCollection(val items: List<Obj>) : Obj() {
    override val objClass = MyCollection.type

    companion object {
        val type = ObjClass("MyCollection", ObjIterable).apply {
            // Provide a Lyng-side iterator for compatibility with 
            // manual iterator usage in Lyng scripts.
            // Using ObjKotlinObjIterator if items are already Obj instances:
            addFn("iterator") { 
                ObjKotlinObjIterator(thisAs<MyCollection>().items.iterator()) 
            }
        }
    }
}

B. Override enumerate for Maximum Performance

The Lyng compiler's for loops use the enumerate method. By overriding it in your Kotlin class, you provide a "fast path" for iteration.

class MyCollection(val items: List<Obj>) : Obj() {
    // ...
    override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
        for (item in items) {
            // If callback returns false, it means 'break' was called in Lyng
            if (!callback(item)) break
        }
    }
}

C. Use ObjInt.of() for Numeric Data

If your iterable contains integers, always use ObjInt.of(Long) instead of the ObjInt(Long) constructor. Lyng maintains a cache for small integers (-128 to 127), which significantly reduces object allocations and GC pressure during tight loops.

// Efficiently creating an integer object
val obj = ObjInt.of(42L) 

// Or using extension methods which also use the cache:
val obj2 = 42.toObj()
val obj3 = 42L.toObj()

Note on toObj() extensions:

While <reified T> T.toObj() is convenient, using specific extensions like Int.toObj() or Long.toObj() is slightly more efficient as they use the ObjInt cache.

4. Summary of Best Practices

  • To Consume: Use enumerate(scope) { item -> ... true }.
  • To Implement: Override enumerate in your Obj subclass.
  • To Register: Use ObjIterable (or ObjCollection) as a parent class in your ObjClass definition.
  • To Optimize: Use ObjInt.of() (or .toObj()) for all integer object allocations.