#!/usr/bin/env catnip
# RUN: catnip docs/examples/advanced/05_nd_recursion.cat
# ND-Recursion : syntaxe des opérateurs
#
# Ces exemples illustrent la forme implémentée.

b = import('builtins')
is_list = (x) => { b.isinstance(x, b.list) }

# ~~ : ND-recursion

# Forme combinateur : seed initial + lambda avec recur
~~(
    0,
    (v, recur) => {
        if v < 10 { recur(v + 1) }
        else { v }
    }
)

# Forme déclaration : fonction ND-récursive
countdown = ~~(n, recur) => {
    if n > 0 { recur(n - 1) }
    else { "done" }
}

# Forme broadcast simple (équivalent à map)
data = list(1, 2, 3)
data.[~~(v, _) => { v * 2 }]

# Forme broadcast sur structure imbriquée : le callback reçoit l'élément du
# premier niveau tel quel, sous-liste comprise. C'est ce que ~> ne permet pas :
# lui ne voit que les feuilles.
nested = list(1, list(2, 3), 4)

# Doubler les scalaires, mais SOMMER les sous-listes
nested.[~~(v, _) => {
    if is_list(v) { reduce(v, sum) } else { v * 2 }
}]
# → [2, 5, 8]

# Pour appliquer la même opération partout, ~> descend seul et suffit
nested.[~>(x) => { x * 2 }]
# → [2, [4, 6], 8]

# ~> : ND-map (lift)

# Lift une fonction dans le contexte ND
lifted_abs = ~> abs

# Forme broadcast : map en contexte ND
data.[~> abs]

# ~[] : topos vide

# Élément neutre des opérations ND
empty = ~[]

# Modes d'exécution

# Mode sequential (défaut) : debug, petits calculs
pragma('nd_mode', ND.sequential)
~~(10, (n, r) => { if n <= 1 { 1 } else { n * r(n - 1) } })

# Mode threads : mémoïsation partagée, I/O bound
pragma('nd_mode', ND.thread)
pragma('nd_memoize', True)
~~(20, (n, r) => { if n <= 1 { n } else { r(n - 1) + r(n - 2) } })

# Mode processes : vrai parallélisme, CPU bound
pragma('nd_mode', ND.process)
pragma('nd_workers', 4)
list(5, 6, 7, 8).[~~(n, r) => {
    if n <= 1 { 1 }
    else { n * r(n - 1) }
}]