Skip to content

Instantly share code, notes, and snippets.

@seisvelas
Last active April 2, 2021 03:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seisvelas/344468193115fed24973d8b109fdd017 to your computer and use it in GitHub Desktop.
Save seisvelas/344468193115fed24973d8b109fdd017 to your computer and use it in GitHub Desktop.
Sieve of Eratosthenes: Lisp vs JavaScript
const { range } = require('range');
const sieve = (field) =>
// initialize
(typeof field === 'number')
? sieve(range(2, field))
// recursive case
: (field.length)
? [field[0]].concat(
sieve(field.slice(1))
.filter(e => e % field[0])
)
// base case
: []
sieve(10) // [2, 3, 5, 7]
#lang racket
(define (sieve field)
; initialize
(cond [(number? field) (sieve (range 2 field))]
; base case
[(empty? field) '()]
; recursive case
[else (let* [(prime (first field))
(multiple? (λ (n) (not (zero? (modulo n prime)))))]
(cons prime (sieve (filter multiple? (rest field)))))]))
(sieve 10)
fn sieve(limit: u32) -> Vec<u32> {
let mut candidates: Vec<u32> = (1..limit+1).collect();
let mut i = 0;
while i < candidates.len() {
let mut j = i + 1;
while j < candidates.len() {
if candidates[j] % candidates[i] == 0 {
candidates.remove(j);
}
j += 1;
}
i += 1;
}
candidates
}
fn main() {
println!("{:?}", sieve(15)); // [1, 3, 5, 7, 11, 13]
}
@seisvelas
Copy link
Author

seisvelas commented Dec 9, 2019

My gf and I were playing with prime number schemes in the library (Vásconcelos, not like a code library lol) when I saw her unwittingly develop the Sieve of Eratosthenes. Then we made a functional version in JS and I noticed how Lispy it felt, so I tried it in a Lisp to compare.


Downsides of JS

  • Nested ternaries (if only JS had a version o switch that were an expression instead of a statement, like Lisp's cond)
  • No builtin range(start, end, step) function. Lots of hacks exist, I chose to use the npm range package. For some reason JS offers a fantastic variety of array methods but totally ignores other functional staples like range or even methods for other, non-array data types. Elixir provides an example of how much better this could be done.

Upsides of JS

  • Filter can take 1, 2 (Oxford comma incoming), or 3 arguments because unprovided arguments are left undefined in JS. Plus, filter (as well as map, reduce, and others are all methods of arrays. This makes programming with these methods terse AND more readable. I'm in the small minority of JS devs who think this is a good thing. A (likely superior) alternative is again provided by Elixir, which has separate functions for different numbers of possible arguments (eg to replicate JS's reduce, you'd have a reduce that takes one argument [just each element], a reduce with two arguments [for each element and its index], and finally a third that gives each element, its index, and a copy of the original array). To get similar functionality from Racket I see people generally use the ridiculously powerful for/list.
  • Compactness - even though the line count is higher, the JS version is much thinner in terms of line width, and just looks smaller and more digestible.

Advantages of Racket

  • The shape of this implementation feels Lispy. My girlfriend's original implementation with a while loop in JavaScript felt much more natural than this ternary/filter/recursive solution.
  • Range is a builtin. I don't like relying on a package for something so basic that I use so often. Although it's trivial to implement, I prefer to speak in the common idioms of a language's users.
  • Variable assignments and conditionals are all expressions.

Disadvantages of Racket

  • Function names are unnecessarily big. Why can't ** just alias to pow, for example? In our case, I dislike having to type modulo instead of just % (which these days is a common enough idiom for modulo operations).
  • #lang racket should be the default when I use the racket command/binary to run a file of unknown source code. What's weird is if I run Racket, it opens a Racket REPL, but when I run a Racket file, it suddenly becomes language agnostic. If there is no #lang line, Racket should be assumed.
  • I have to choose between calling it Racket, Scheme, or 'a Lisp'. I think my implementation is compatible with R7RS (the current Scheme standard). But I also like saying 'Lisp', to annoy the Common Lisp people

N.B. In both cases I abuse the weak typing for function arguments in order to be terse/lazy. I initially take a number for the field argument, but then take an array/list (for JS/Racket respectively). In reality, I only ever want an array or list. A more conventional solution is to make an initializer function which sets up the initial list, then passes it to a recursive function that takes care of the meat and potatoes of the algorithm. But since I'm just playing, I'm giving myself lots of slack. I say this in case someone reads this Gist in 5 years and is deciding to hire me or not.

@seisvelas
Copy link
Author

Khety (my daughter) is also working on this so while she tries it in Python, I tried it in Rust. So that's why a Rust version is pegged on here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment