prime number generators, part 2
programming,
nushell
date: 2025-07-16
latest post - view all
continued from prime number generators
hi what's up! the art uploading process is still underway but i have a bad case of zoomies and i'm never going to run out of ways to spend a whole day making very small improvements to my website. how about some decently formatted code blocks?
scheme
forth is where my discovering-new-programming-languages binge ended back in january of 2025. now it’s about 6 months later and i’m still apparently writing new prime number generators.
(define (gen-primes n)
;; build p-list from right to left, e.g. '(... 11 7 5 3 2)
(define (gen count p-list)
(cond ((<= count 0) p-list)
((empty? p-list) (gen (- count 1) '(2)))
(else (gen
(- count 1)
(cons (next-prime p-list (+ (car p-list) 1)) p-list)))))
;; use p-list to find the next prime
(define (next-prime p-list n)
(if (prime? n (- (length p-list) 1) p-list)
n
(next-prime p-list (+ n 1))))
;; determine if n is prime by comparing it with all primes in p-list
(define (prime? n i p-list)
(let ((p (list-ref p-list i)))
(cond ((= 0 (modulo n p)) #false)
((or (<= n (* p p)) (< i 0)) #true)
(else (prime? n (- i 1) p-list)))))
;; go!
(let ((p-list '()))
(gen n p-list)))
(display
(string-append
(string-join (reverse (map int->string (gen-primes 10000))) ", ")
"\n"))
“scheme is kind of like lua if instead of anonymous mutable hashmap you have cons.”
hey, it’s almost like fennel again! this time it’s purely functional though. meaning it’s all tail recursion and immutable linked lists here. scheme has a lot of the same tiny-language appeal as lua but now i’m in the deep end of functional programming without any of the anonymous mutable object known as “table” to help me. and, i think i like it? oh no?
now with a little more programming experience, i took advice from lynn and structured this one a little more smartly by putting the recursive helper functions inside the main gen-primes function and letting it be a nice black box.
the reason i decided to learn about scheme is because my text editor, helix, announced that they’re moving towards steel as the scripting language for their config and plugins. this made the emacs people excited and the neovim people really mad. i wanted to know what all the fuss was about, so this is the specific scheme implementation i used for this entry.
it might also be cool if i ever get advanced enough with bevy to need a scripting language… one day.
python
def gen_primes(n):
def gen(count, p_list):
if count <= 0:
return p_list
elif not p_list:
return gen(count - 1, [2])
else:
return gen(
count - 1,
[next_prime(p_list, p_list[0] + 1)] + p_list
)
def next_prime(p_list, n):
if is_prime(n, len(p_list) - 1, p_list):
return n
else:
return next_prime(p_list, n + 1)
def is_prime(n, i, p_list):
p = p_list[i]
if n % p == 0:
return False
elif p * p > n or i < 0:
return True
else:
return is_prime(n, i - 1, p_list)
# go!
gen(n, [])
print(', '.join(map(str, reversed(gen_primes(10000, [])))))
see, it’s basically a 1:1 version of my scheme example. why would you ever write python like this. this is my funny joke designed to make valerie cry and scream.
actually, python also hates this! python (rightfully) does not implement tail-recursion elimination, so this doesn’t actually complete. it gets 961 iterations deep and blows the stack.
bash
#!/usr/bin/env bash
function primes {
case "$1" in
''|*[!0-9]*)
echo "usage: primes <integer>";
exit 1 ;;
*) ;;
esac
declare -a plist=(2)
for (( i = 2; i <= $1; i++ )); do
next="${plist[-1]}"
isprime=false
while test "$isprime" == "false"; do
let next++
for p in "${plist[@]}"; do
if test $(( next % p )) -eq 0; then
isprime=false
break
fi
if test $(( p * p )) -gt $next; then
isprime=true
plist+=("$next")
break
fi
done
done
done
echo ${plist[@]} | sed "s/ /, /g"
}
primes 10000
i’ve been daily driving linux for like 15 years and only now have i started feeling the need to make shell scripts to automate tasks! i have to learn how this weird string language works so i can use it to do things like automate the process of uploading art to this site.
i can feel myself reaching the end of “learning something new by comparing programming languages for this specific use case” but bash is one of the weirdest and oldest i’ve ever tried, and i always found it a little scary to look at so it’s kind of delightful to come to understand it from the inside. it’s not so different from lua after all, is it?
this time it’s rue’s turn to be my local language expert. she gave me advice about sanitizing input.
nushell
#!/usr/bin/env nu
def primes [count] {
2..
| where {|n|
2..
| each {||} # converts a range into a finite list
| take while {|i| $i ** 2 <= $n }
| all {|i| $n mod $i != 0 }}
| take $count
}
primes 10000 | str join ", "
wow look it’s so tinyyyyy haha… actually it’s the most consise entry yet! it’s also much, much faster than the bash version.
since creating the last entry, one thing led to another and i’ve spent the last week or so with nushell as my main shell. i have a lot of thoughts about it but i think it’s easy to understand what’s cool about it just by looking at this and comparing it to bash.
like okay, you can type ls
and it returns a table
instead of a raw string. that’s cool i guess. what i actually enjoy
about nushell is the coziness. i’m actually writing mid-size
shell scripts and also voluntarily using the shell to interact with my
computer and do tasks? like one-liners with pipelines and everything. i
can actually use the terminal as a calculator without having to open
lua, even when the arithmetic i’m doing includes variables. i understand
what those interactive programming REPL people are raving about now,
because they finally made a real programming language that you can also
do computer in
this might be it, actually. between rust for the intensive stuff and nushell for quick scripts and miscellaneous housekeeping, i don’t think i’ll have a practical need to learn any more programming languages for a long time. of course, i have a lot of impractical needs too… as you can plainly see from the length of this document…