Hacker News new | past | comments | ask | show | jobs | submit login
The evolution of a Scheme programmer (2020) (erkin.party)
144 points by ducktective 28 days ago | hide | past | favorite | 41 comments



Isn't this missing the joke? The Haskell article code ended with the "Tenured Professor" implementation (`fac n = product [1..n]`) as a nod to the old trope of not needing to implement anything. This article ends with `(define factorial (dynamic-require 'math/number-theory 'factorial (lambda () lambda (n) (error 'factorial "Cannot import library: ~a" 'math/number-theory)))))`.

Surely there is a lazier implementation than a define and 2x lambdas to use a library function. Although I did get a chuckle about not feeling a need to add brackets to the Scheme example.


Thankfully, the tenured professor version works in Julia like so:

    prod(1:63)
For bigger numbers,

    factorial(n) = 1:n .|> BigInt |> prod


It's not the best algorithm for computing factorials though. A better strategy is to pair small and large factors together in order to avoid bignum computations (or to reduce the size og bignum computations).

https://people.eecs.berkeley.edu/~fateman/papers/factorial.p...

Also: It's more fun to use the name `fact` in benchmarks...


The rabbit-hole goes much deeper than that.

https://en.wikipedia.org/wiki/Factorial#Computation


But the extra code is for handling the error, you could just `require` it and be done if you want to assume the library is available.


Normal Racketeers would just `(require math/number-theory)` and be done. No idea why they're loading the module at runtime like that instead, unless it's a deliberately over complicated joke solution like some of the others. Which would as you noted miss the point of the original.


We might add an evolution of the display of parentheses...

B&W ()

B&W and optional bold ()

B&W and optional bold/larger ()

Full color rainbow parentheses.

B&W ()

One or two of the Lisp editors I use also support rainbow parentheses, but after an initial phase of experimentation, I have disabled that feature.


I like my parens in a muted color with the currently active one highlighted.

Rainbow colors are extremely distracting and overloading to me.


I also came back to B&W because I’ve noticed that with structural editing I think more in terms of s-expressions than parentheses and distinguishing them isn’t that much of a problem anymore.


I use these colours for rainbow brackets in all other languages https://github.com/Release-Candidate/vscode-scheme-repl/blob... but for LISPs I dimmed them (their saturation) down for normal editing (when not "recording" animations/videos).


This is the first time I’ve ever seen rainbow parentheses, it seems like such a useful thing. What wound up leading you back to B&W?


Not the parent but speaking for myself. I read Lisp code by looking at the indentations. Parens don't matter unless they're wrong, and they're rarely wrong because my editor matches them as I type, and then I just tell it to indent everything properly. If the indentation looks goofy after that step I notice it immediately and only then do I look for paren problems.

Lisp is by far my favorite language but if I had to write it with a "dumb" text editor it would quickly stop being my favorite. Most editors are smart these days, and this is why it's hard to explain to newbies that the parens don't get in your way: When you build lisp code your editor manages the parens for you and they become essentially invisible. So color solves a problem that doesn't exist (for me).


Probably because you get used to them and different colours become distracting. Highlighting of active parentheses is more useful


Too much visual clutter with very little actual benefit. I found that I don't need/want to find matching parentheses by a mix of position AND color.


Using an actual rainbow seems a bad idea. I will try to set up a Viridis (or similar perceptually continuous) color scheme next time I dabble in lisps.


Golden opportunity to slip an "animated" option in there. I'm pretty sure there are editors with a little jump to the other side of a form to show where it is.


In Zmacs on the Lisp Machine, the s-expression under the mouse cursor gets an outline. This is updated while the mouse is moving.


That was a tasty read! The Y-combinator implementation gave me a slight headache, but I averted my eyes and all was OK.

Scheme as a language and from a clean/simplicity point of view has always appealed to me, but I have mostly used Common Lisp because I have so much legacy code.


I use Scheme (these days more Racket or Shen) for doing 'language experiments' but if anything gets to production, it'll be CL. Like you said, legacy code, but also; it's just more practical. You give up some elegance, but in exchange you get something which you can build practically anything in, in modern times.


I don't know why I can use continuations all day long but I cannot wrap my head around the Y-combinator. My brain seems to fall into a well-bounded category: C^¬(YC).


Has someone tried to evaluate all the solutions in terms of memory/CPU usage? Or do all the differences end up optimised away?


I like how in F# things are simpler:

let factorial n = [1..n] |> List.reduce (*)


(defn fac [n] (->> (range 1 (inc n)) (reduce *))) or in Clojure.


Zen of Python

> There should be one-- and preferably only one --obvious way to do it.

> Although that way may not be obvious at first unless you're Dutch.


What is the preferred way to write factorial in Python?


I'd say the most idiomatic way is to use a simple for-loop:

def factorial(n):

    result = 1

    for i in range(2, n + 1):

        result *= i

    return result
The recursive version would be fine as well, but I would say it's less idiomatic in Python, and less efficient.

def factorial(n):

    if n == 0 or n == 1:

        return 1

    else:

        return n * factorial(n - 1)
Beyond that, I cannot think of any obvious way to do this (without getting unreasonably convoluted).

In my opinion, it's great that there are such few options to express this operation, and that both cases are eminently readable.


> In my opinion, it's great that there are such few options to express this operation, and that both cases are eminently readable.

There are lots of ways to do this in Python. That's not only fine, it's inevitable.

Here's a burnt-out PhD level one using highly pythonic paradigms:

    def fac(n): 
        return eval("*".join(str(1+a) for a in range(n))


I have never seen a codebase that uses eval, it's considered more toxic than goto.

Of course there are more ways, but I would definitely qualify this as "unreasonably convoluted".


> it's considered more toxic then goto

Again I ask, considered by who? This is the kind of language that wikipedians would call "weasel words". Of course you shouldn't misuse eval or goto, and it's fine to have a rule of thumb to discourage it.

What's not fine, and strikes me as no better than superstition, is to make vague "everybody knows"-type statements that convey almost no information. If it's bad you should be able to say (or provide reference to) specifically how it's bad and what convinced you this was the case.

And yes, I deliberately wrote a perverse function because it was funny, but if you did a real fold instead of my fake one with strings then it wouldn't be so perverse. And wasn't it more pythonic to use a generator?

Not embracing Perl's tmtowtdi is fine, but until you solve the halting problem some people will write "x+x" and some will write "2*x" and some will write "x<<2" and those can't sensibly be unified.


I find both Haskell's `fac n = product [1..n]` and F# `let factorial n = [1..n] |> List.reduce ()` more readable and easier to comprehend. You could say using `product` is cheating, but `fac n = fold () [1..n]` definitely isn't!

Your idiomatic Python version uses three variables, two of which mutate. I'm a simple man - go easy on me please! The `n + 1` took me a while to figure out too.


How about:

  from math import prod # from Python 3.8 onwards
  
  def factorial(n):
      return prod(range(n+1))


Since the first element in range() will be 0, should the last line be ...?

    return prod(1,range(n+1))


Oops yes! And unfortunately it still doesn't work for n=0

Edit: prod(range(1, n+1)) does work


My oops too. The extra 1 argument belongs with range, as you have it here.


Definitely preferable, wasn't aware of it.


Preferred by who? `math.factorial` is a good start.


Obviously preferable, it was just to illustrate the "one obvious way of doing something".


I'm delighted to see there are actually several ways and you good people are discussing them.

This is exactly why I asked the question!


And then there’s Raku; say [*] 1..$n


(2020)


Thanks for the heads-up. I was almost about to be misinformed by an outdated, no-longer-relevant article about a language that's been more or less the same for the last few decades.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: