A vida sem if, um desafio para escrever código sem estruturas de controle
Meu amigo Samuel Santos publicou no Twitter um exemplo de código JS em que a execução condicional de um trecho de código era disparada sem a necessidade de estruturas sintáticas. Era algo mais ou menos assim:
const n = 15;
const fizz = n % 3 == 0;
const buzz = n % 5 == 0;
fizz && (() => console.log("fizz"))()
buzz && (() => console.log("buzz"))()
!(fizz || buzz) && (() => console.log(n))()
Ele já era conhecido como Samuelse, por já ter banido o else em outros
momentos, pois afinal early return deixa o código mais limpo e com menos
voltas para entender.
Ao ver esse trecho de código e esse estilo de programar… pensei… “e se eu
fizesse um programa todinho sem estruturas sintáticas de controle de fluxo?”
Então eu lancei um auto-desafio, que chamei de controlless:
- vai ser em JS, sem tipos explícitos
- você pode criar valores constantes, sem
letnemvar, apenasconst - você tem total liberdade de usar OPERADORES, mas não pode usar controle sintático
- recursão é permitida
- só pode hazer um único
return - só posso criar arrow functions
Meu desafio vai ser escrever quicksort no estilo controlless.
Explicando o funcionamento do código do Sam
O Sam usou operadores binários para fazer a execução. Ao fazer a && b, ele
está avaliando uma expressão lógica em que, se a for truthy, o valor de b
é carregado. Por exemplo:
const awesomeFunction = () => console.log("AWESOME")
false && awesomeFunction()
Aqui o valor false é falsy, portanto o resultado da operação false && ???
será sempre falso, não se fazendo necessário computar o valor da função. Então
nada é impresso. Diferente disto daqui:
const awesomeFunction = () => console.log("AWESOME")
true && awesomeFunction()
que imprime AWESOME no console.
Se aproveitando desse operador, ele também criou uma função imediatamente chamada (IIFE, Immediately Invoked Function Expression) é uma expressão em que uma função é criada e imediatamente executada.
Por exemplo, para uma função que calcula quantos passos a função de Collatz vai
iterar até obter 1 como resposta. Para quem não sabe, essa função é a do
3n+1:
Essa simples função levou à conjectura de Collatz. A conjectura é da seguinte forma, para um valor :
Para qualquer que seja inteiro positivo, existirá algum tal que
. Ou seja, a aplicação repetidas vezes da função de Collatz
eventualmente chega no valor 1.
A quantidade de passos até chegar nesse valor é o acima descrito. Então,
assim, consigo definir uma função step(n) desse modo:
Por exemplo, step(3) = 7:
c(3) => 10
c(10) => 5
c(5) => 16
c(16) => 8
c(8) => 4
c(4) => 2
c(2)=> 1
São dados 7 passos até obter o valor 1.
Então, vamos fazer o cálculo da função step(n)? Um jeito de fazer é assim:
function collatz(n) {
if (n % 2 == 1) return 3*n + 1;
return n/2;
}
function step(n) {
if (n == 1) return 0;
return 1 + step(collatz(n));
}
Como step é uma função pura, podemos aplicar memoização a ela, e fazer com
que essa função consulte a memória:
const memoria = {}
memoria[1] = 0;
function collatz(n) {
if (n % 2 == 1) return 3*n + 1;
return n/2;
}
function step(n, memoria) {
if (memoria[n] != undefined) return memoria[n]
return memoria[n] = 1 + step(collatz(n), memoria);
}
Mas aqui eu mudei a assinatura de step para por a memória, e isso não é o que
eu gostaria. E também acontece de que eu crio uma variável solta memoria que
pode ser mexida de modo indevido. Então, para evitar isso, eu posso deixar tudo
englobado e retornar uma função da forma (n) => step(n, memoria)!
const step = (() => {
const memoria = {}
memoria[1] = 0;
function collatz(n) {
if (n % 2 == 1) return 3*n + 1;
return n/2;
}
function step_with_memory(n, memoria) {
if (memoria[n] != undefined) return memoria[n]
return memoria[n] = 1 + step_with_memory(collatz(n), memoria);
}
return (n) => step_with_memory(n, memoria)
})()
E agora o valor de step foi inicializado pelo retorno da função anônima. Essa
função por sua vez cria o estado memoria, cria duas funções internas e
retorna um lambda que mantém o acesso a memoria em sua clausura. E essa
função foi imediatamente chamada após sua declaração. Isso faz dela uma IIFE.
Escrevendo Collatz em controlless
Bem, antes de embarcar no desafio propriamente dito, podemos aqui aplicar o
desafio do controlless para a função step e para a função de Collatz.
A função implicitamente tem um if-else, porém a escrita não foi usado else
explícito porque eu havia colocado um early return. Agora, com controlless,
early return não é mais uma opção. Então, como expressar isso só com
operadores?
Bem, aqui eu tiro inspirações da minha leitura de dive into python! Na época
do Python 2, não existia o construto equivalente a cond? whenT: whenF, o
operador ternário tão comum em linguagens como C e seus descendentes. Então,
para contornar isso, pessoal se valia do fato de que avaliações booleanas em
Python eram feitas de modo semelhante a como hoje temos as avaliações de JS:
usando truthy e falsy! E no caso, ao fazer a && b, eu retorno a se
a for falsy ou retorno b se a for truthy. De modo semelhante com
o a || b. Inclusive usei esse mesmo tipo de lógica no artigo
Escrevendo as funções booleanas do cálculo lambda, em Java.
Naquela fatídica época do Python 2, o contorno era fazer a and b or c.
Explicando rapidamente:
- se
afortruthy, ele vai executara and b, retornandob - caso contrário,
a and bseráa, então ele retornarác
Tem um caveat aqui que é se
bfor falsy, então essa técnica tem pontos de falha, e também com Python mais moderno temos o construtob if a else c.
E, bem, podemos fazer isso com Collatz! No lugar do IIFE ser o que está do lado
direito do operador &&, podemos fazer algo como
(cond && lambda1 || lambda2)(), pois assim o resultado da operação booleana
será, necessariamente, ou o lambda1 ou o lambda2, e eu vou executar esse
resultado.
Ah, sabe o caveat descrito acima? Pois bem, no JS lambdas são truthy, estou livre dele!
Então, como podemos fazer o collatz? Assim:
const collatz = (n) => ((n % 2 == 1) && (() => 3*n + 1) || (() => n/2))()
E para steps? Bem, vamos implementar a versão sem memoização primeiro? Ela
envolve também um if-else. baseado em n:
const collatz = (n) => ((n % 2 == 1) && (() => 3*n + 1) || (() => n/2))()
const step = (n) => ((n == 1) && (() => 0) || (() => 1 + step(collatz(n))))()
E a versão controlless do step com memoização? Fica de modo muito parecido:
const step = (() => {
const memoria = {}
memoria[1] = 0;
const collatz = (n) => ((n % 2 == 1) && (() => 3*n + 1) || (() => n/2))()
const step_with_memory = (n, memoria) => {
const mn = memoria[n]
return (
mn != undefined &&
(() => mn) ||
(() => memoria[n] = 1 + step_with_memory(collatz(n), memoria))
)()
}
return (n) => step_with_memory(n, memoria)
})()
Ackermann-Peter em controlless
Pois bem, um dos desafios para controlless é uma operação Turing Completa. A
função de Ackermann-Peter é uma das funções recursivas que não é
primitiva-recursiva, que precisa de uma máquina com poder computacional grande
como a máquina de Turing para computar. Para ler mais sobre ela, pode consultar
esse post:
Trampolim para funções além do primitivo recursivo? Implementação para a função de Ackermann Peter.
A função é definida assim:
function ackermannPeter(m, n) {
if (m == 0) {
return n + 1;
} else if (n == 0) {
return ackermannPeter(m - 1, 1);
}
return ackermannPeter(m - 1, ackermannPeter(m, n - 1));
}
Aqui nós temos 2 elses encadeados! if-elif-else. Para deixar mais paupável,
vou tratar assim: if-else-(if-else), assim o resultado dessa operação será a
função que será chamada:
const ackermannPeter = (m, n) => (
(m == 0) &&
(() => n + 1) ||
(
(n == 0) &&
(() => ackermannPeter(m - 1, 1)) ||
(() => ackermannPeter(m - 1, ackermannPeter(m, n - 1)))
)
)()
Quicksort em controlless
Ok, vamos fazer um quicksort. Finalmente chegou a hora.
O Primeagen constantemente briga com sugestões de LLM para código porque, segundo ele, o quicksort precisa ser inplace, e as sugestões normalmente dependem do spread operator fazendo trabalho em memória extra. Por exemplo, isso ele diz que não é quicksort:
function pseudo_qsort(elements, cmp) {
if (elements.length == 0) return elements;
const pivot = elements[0];
return [
...pseudo_qsort(elements.slice(1).filter((el) => cmp(pivot, el) >= 0), cmp),
pivot,
...pseudo_qsort(elements.slice(1).filter((el) => cmp(pivot, el) < 0), cmp)
]
}
pseudo_qsort([1, 7, 3, 6, 3, 5], (a, b) => a - b)
Se eu usasse essa função como controlless, ela ficaria assim:
const pseudo_qsort = (elements, cmp) =>
(elements.length == 0) && elements || (() => {
const pivot = elements[0];
return [
...pseudo_qsort(elements.slice(1).filter((el) => cmp(pivot, el) <= 0), cmp),
pivot,
...pseudo_qsort(elements.slice(1).filter((el) => cmp(pivot, el) > 0), cmp)
]
})()
Aqui, como um vetor sem elementos é truthy, resolvi não englobar tudo em uma
expressão para executar e só executar se cair no caso else. Mas… esse não é
o quicksort que buscamos, pois queremos algo inplace!
Vamos para uma abordagem um pouco mais clássico, com coisas de controle:
const qsort = (elements, cmp) => {
const partition = (pivot, start, end) => {
if (start >= end) {
return end;
}
const head = elements[start];
if (cmp(pivot, head) >= 0) {
return partition(pivot, start + 1, end);
}
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
if (cmp(pivot, tail) < 0) {
return partition(pivot, start, end - 1);
}
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
}
const innerQsort = (start, end) => {
if (end - start <= 1) {
return;
}
const pivot = elements[end - 1];
const middlePartition = partition(pivot, start, end - 1);
const middleElement = elements[middlePartition];
elements[end - 1] = middleElement;
elements[middlePartition] = pivot;
innerQsort(start, middlePartition);
innerQsort(middlePartition + 1, end);
}
innerQsort(0, elements.length);
}
qsort([1, 7, 3, 6, 3, 5], (a, b) => a - b)
Criando um validador
Bem, eu preciso examinar se a abordagem deu certo. Então, que tal fazer um validador de que o sort realmente ordenou? Eu não preciso ordenar o array para validar isso, apenas comparar com o elemento seguinte. Se for estritamente maior, deu ruim. Como o exemplo vou fazer com números, não vou passar a função de comparação como argumento, mas isso é fácil de resolver.
Então, temos uma função sorted que recebe o array e retorna true se tiver
tudo ok ou retorna false caso tenha pelo menos um elemento fora de ordem!
Como estamos fazendo controlless, vamos fazer essa função em controlless
também? Tal qual o quicksort, vou começar com uma base ainda com algumas
statements de controle, depois adapto isso. Vamos receber um array, para já
lidar com o laço vamos passar também o ponto o qual estamos fazendo a
verificação:
function sorted(arr, start) {
if (start >= arr.length - 1) {
return true
}
if (arr[start] > arr[start + 1]) {
return false
}
return sorted(arr, start + 1)
}
Muito bem, agora vamos por em controlless? Basicamente se o start
ultrapassar o tamanho do vetor, concluímos. Caso contrário, se o próximo
estiver bagunçado, já sabemos que não precisa ir adiante. Podemos também, nessa
situação, verificar se está em ordem E TAMBÉM que o resto do vetor esteja
ordenado. Ou seja, no lugar de fazer
if (arr[start] > arr[start + 1]) {
return false
}
return sorted(arr, start + 1)
colocar isso aqui:
(arr[start] <= arr[start + 1]) && sorted(arr, start + 1)
Note que (arr[start] <= arr[start + 1]) é idêntico à negação de
arr[start] > arr[start + 1]. Portano, sorted poderia ser escrito assim em
controlless:
const sorted = (arr, start) =>
start >= arr.length - 1 || (
(arr[start] <= arr[start + 1]) && sorted(arr, start + 1)
)
Mas e como posso verificar que a função sorted está funcionando devidamente?
Bem, podemos fazer uma verificação que imprima caso não esteja funcionando. Ou
seja, para o teste da função sorted passar, ele precisa não imprimir nada.
Algo nesse sentido aqui:
function issorted(arr, expected) {
const s = sorted(arr, 0)
if (s != expected) {
console.log({s, expected, arr})
}
}
Exemplos de chamada que indicam que a implementação está retornando o resultado correto:
issorted([], true)
issorted([1], true)
issorted([1, 2], true)
issorted([2, 1], false)
issorted([1, 1], true)
issorted([1, 2, 1], false)
issorted([1, 1, 1], true)
issorted([1, 1, 2], true)
issorted([2, 1, 1], false)
Adaptando para controlless, vamos passar o resultado se está ordenado ou não
como argumento para a função, e a avaliação se devemos imprimir ou não vai
ficar fora dessa função:
const issorted = (arr, expected) => {
const loggar = (s) => console.log({s, expected, arr})
const s = sorted(arr, 0);
(s != expected) && loggar(s)
}
Beleza, mas na hora que eu for executar o teste, eu vou querer que imprima o
vetor original para ter uma ideia de como as coisas desandaram (e para poder
reproduzir ele especificamente). Vou pegar o issorted como base, assumir que
deveria retornar sempre true, e imprimir o vetor original:
const doStuff = (v) => {
const orig = [...v]; // crio uma cópia de v
qsort(v, (a, b) => a - b);
const logga = (orig, v) => console.log({v, orig})
const result = sorted(v, 0);
result || logga(orig, v);
return result;
}
Gerando os casos de teste
Bem, eu preciso de casos de teste para dar uma olhada por cima para validar se
deu certo. Além do caso [1, 7, 3, 6, 3, 5] que motivou o começo dessa seção
do artigo, seria legal se eu conseguisse gerar aleatoriamente, né?
Pois bem, o JavaScript tem um construtor de vetor que você passa uma
propriedade e uma função de geração,
Array.from.
No caso, se eu quiser gerar um array de 5 elementos, posso fazer isso:
Array.from({ length: 5 })
// [ undefined, undefined, undefined, undefined, undefined ]
Mas aqui ele só gera o array, nem define com o que ele de fato vai ser povoado.
Como eu quero com coisas aleatórias, posso passar a função Math.random:
Array.from({ length: 5 }, Math.random)
/*
[
0.4366370972945681,
0.4612824283143957,
0.15061762899680176,
0.7328764102523664,
0.7277451936950374
]
*/
Mas, como que ele gera esses elementos? Bem, na real a função que ele espera
recebe dois argumentos, (v, i), onde o v vem do iterador base que ele tá
usando para construir (no caso, como passamos { length: 5 }, é sempre
undefined), e o i é o índice começando de 0.
Bem, temos números, mas sinceramente? Números quebrados são feios. Tem como deixar eles inteiros? Claro!!
O Math.random() gera um número de ponto flutuante entre 0 e 1, então posso
multiplicar por uma constante e arredondar usando toFixed:
Array.from({ length: 5 }, Math.random).map(x=> x*100).map(x=>x.toFixed())
// [ '92', '47', '26', '66', '34' ]
Mas isso acaba por gerar strings de números inteiros… então só mapear usando
Number!
Array.from({ length: 5 }, Math.random).map(x=> x*100).map(x=>x.toFixed()).map(Number)
// [ 39, 87, 87, 44, 33 ]
Então, com isso, conseguimos gerar o caso de teste. Encadeando as coisas:
const v = Array.from({ length: 7 }, Math.random).map(x=> x*100).map(x=>x.toFixed()).map(Number)
doStuff(v)
Tá, mas isso não dá uma visão clara de quando dá certo, só notifica alguns problemas. Preciso ter uma visão mais clara quando deu ruim. Posso fazer então algo como:
const v = Array.from({ length: 7 }, Math.random).map(x=> x*100).map(x=>x.toFixed()).map(Number)
const sucesso = doStuff(v)
console.log({ sucesso })
Mas rodar uma vez perdida tá com nada, né? Vamos rodar isso várias vezes:
let success = true;
for (const i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) {
const v = Array.from({length:7}, Math.random).map(x=> x*100).map(x=>x.toFixed()).map(Number)
success &&= doStuff(v)
}
console.log({success})
Mas isso não vai bem na alma do controlless, né? Loop, let? Bem, vamos
resolver isso. Vamoz fazer uma função que vai até 0, no 0 ela para. Bem no
esquema da
função de cauda
passando o acumulador como argumento:
const testBench(i, acc) => {
return (i == 0 && (() => acc) || (() => {
const v = Array.from({length:7}, Math.random).map(x=> x*100).map(x=>x.toFixed()).map(Number)
const result = doStuff(v)
return testBench(i - 1, result && acc)
}))()
}
const success = testBench(10, true)
console.log({success})
Pegando caso específico
Bem, sabe um dos motivos que eu botei no doStuff para imprimir o vetor
original? Para servir de desafio, e por para rodar novamente. Vou fazer isso
através da linha de comando. Como minha chamada é:
node controlless.js
posso simplesmente identificar se tem 3 ou mais elementos passados no ARGV, como por exemplo:
node controlles.js 1 7 3 6 3 5
Como fazer isso? Usando a variável de sistema process.argv. Posso pegar o
slice que remove os dois primeiros elementos e verificar se tá vazio:
const args = process.argv.slice(2);
const success = (
(args.length > 0) && (() => doStuff(args.map(Number))) || (() => testBench(10, true))
)()
console.log({success})
Voltando pra terminar quicksort
Bem, antes de entrar nas tangentes, o quicksort estava assim:
const qsort = (elements, cmp) => {
const partition = (pivot, start, end) => {
if (start >= end) {
return end;
}
const head = elements[start];
if (cmp(pivot, head) >= 0) {
return partition(pivot, start + 1, end);
}
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
if (cmp(pivot, tail) < 0) {
return partition(pivot, start, end - 1);
}
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
}
const innerQsort = (start, end) => {
if (end - start <= 1) {
return;
}
const pivot = elements[end - 1];
const middlePartition = partition(pivot, start, end - 1);
const middleElement = elements[middlePartition];
elements[end - 1] = middleElement;
elements[middlePartition] = pivot;
innerQsort(start, middlePartition);
innerQsort(middlePartition + 1, end);
}
innerQsort(0, elements.length);
}
Basicamente qsort pode ser vista assim:
const qsort = (elements, cmp) => {
const partition = (pivot, start, end) => {
//...
}
const innerQsort = (start, end) => {
//...
}
innerQsort(0, elements.length);
}
Basicamente vai ser a definição de partition e innerQsort que vai indicar
se consegui escrever o qsort com controlless. Parece que innerQsort é
mais tranquilo de transformar, vamos começar com ele? Eu só computo algo se
end - start <= 1 for falso, caso conrário só deixo nada ocorrer:
const innerQsort = (start, end) => {
(end - start <= 1) || (() => {
const pivot = elements[end - 1];
const middlePartition = partition(pivot, start, end - 1);
const middleElement = elements[middlePartition];
elements[end - 1] = middleElement;
elements[middlePartition] = pivot;
innerQsort(start, middlePartition);
innerQsort(middlePartition + 1, end);
})()
}
Agora, o partition… bem, vou abusar nesse caso de execução de lambdas. Uma
eliminação de if por vez. Começamos com o parition base:
const partition = (pivot, start, end) => {
if (start >= end) {
return end;
}
const head = elements[start];
if (cmp(pivot, head) >= 0) {
return partition(pivot, start + 1, end);
}
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
if (cmp(pivot, tail) < 0) {
return partition(pivot, start, end - 1);
}
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
}
Vamos transformar os dois branches do primeiro if em lambdas, e fazer IIFE no
final:
const partition = (pivot, start, end) =>
((start >= end) && (() => end) || (() => {
const head = elements[start];
if (cmp(pivot, head) >= 0) {
return partition(pivot, start + 1, end);
}
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
if (cmp(pivot, tail) < 0) {
return partition(pivot, start, end - 1);
}
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
}))()
Agora só repetir para os demais…
const partition = (pivot, start, end) =>
((start >= end) && (() => end) || (() => {
const head = elements[start];
return (
(cmp(pivot, head) >= 0) &&
(() => partition(pivot, start + 1, end)) ||
(() => {
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
if (cmp(pivot, tail) < 0) {
return partition(pivot, start, end - 1);
}
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
})
)()
}))()
E só mais uma vez:
const partition = (pivot, start, end) =>
((start >= end) && (() => end) || (() => {
const head = elements[start];
return (
(cmp(pivot, head) >= 0) &&
(() => partition(pivot, start + 1, end)) ||
(() => {
const tail = elements[end - 1];
return (
(cmp(pivot, tail) < 0) &&
(() => partition(pivot, start, end - 1)) ||
(() => {
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
})
)()
})
)()
}))()
E finalmente eu posso mostrar o quicksort em controlless!
const qsort = (elements, cmp) => {
const partition = (pivot, start, end) =>
((start >= end) && (() => end) || (() => {
const head = elements[start];
return (
(cmp(pivot, head) >= 0) &&
(() => partition(pivot, start + 1, end)) ||
(() => {
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
return (
(cmp(pivot, tail) < 0) &&
(() => partition(pivot, start, end - 1)) ||
(() => {
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
})
)()
})
)()
}))()
const innerQsort = (start, end) => {
(end - start <= 1) || (() => {
const pivot = elements[end - 1];
const middlePartition = partition(pivot, start, end - 1);
const middleElement = elements[middlePartition];
elements[end - 1] = middleElement;
elements[middlePartition] = pivot;
innerQsort(start, middlePartition);
innerQsort(middlePartition + 1, end);
})()
}
innerQsort(0, elements.length);
}
Recapitulando o código
const qsort = (elements, cmp) => {
const partition = (pivot, start, end) =>
((start >= end) && (() => end) || (() => {
const head = elements[start];
return (
(cmp(pivot, head) >= 0) &&
(() => partition(pivot, start + 1, end)) ||
(() => {
const tail = elements[end - 1];
// pivot é estritamente menor do que tail
return (
(cmp(pivot, tail) < 0) &&
(() => partition(pivot, start, end - 1)) ||
(() => {
elements[end - 1] = head;
elements[start] = tail;
return partition(pivot, start, end)
})
)()
})
)()
}))()
const innerQsort = (start, end) => {
(end - start <= 1) || (() => {
const pivot = elements[end - 1];
const middlePartition = partition(pivot, start, end - 1);
const middleElement = elements[middlePartition];
elements[end - 1] = middleElement;
elements[middlePartition] = pivot;
innerQsort(start, middlePartition);
innerQsort(middlePartition + 1, end);
})()
}
innerQsort(0, elements.length);
}
const sorted = (arr, start) =>
start >= arr.length - 1 || (
(arr[start] <= arr[start + 1]) && sorted(arr, start + 1)
)
const issorted = (arr, expected) => {
const loggar = (s) => console.log({s, expected, arr})
const s = sorted(arr, 0);
(s != expected) && loggar(s)
}
issorted([], true)
issorted([1], true)
issorted([1, 2], true)
issorted([2, 1], false)
issorted([1, 1], true)
issorted([1, 2, 1], false)
issorted([1, 1, 1], true)
issorted([1, 1, 2], true)
issorted([2, 1, 1], false)
const doStuff = (v) => {
const orig = [...v]; // crio uma cópia de v
qsort(v, (a, b) => a - b);
const logga = (orig, v) => console.log({v, orig})
const result = sorted(v, 0);
result || logga(orig, v);
return result;
}
const args = process.argv.slice(2);
const testBench = (i, acc) => {
return (i == 0 && (() => acc) || (() => {
const v = Array.from({length:7}, Math.random).map(x=> x*100).map(x=>x.toFixed()).map(Number)
const result = doStuff(v)
return testBench(i - 1, result && acc)
}))()
}
const success = (
(args.length > 0) && (() => doStuff(args.map(Number))) || (() => testBench(10, true))
)()
console.log({success})