Programming thread

  • 🐕 I am attempting to get the site runnning as fast as possible. If you are experiencing slow page load times, please report it.
On every step on the way I felt like I was missing a lot of information and books like Learn you a Haskell (supposedly for beginners) did not make things much clear for me. I had quite a hard time wrapping my head around how this language worked and it only clicked together after almost failing the class completely.
Even as a teenager I was able to do stuff in languages like PHP and Delphi with minimal supervision and research. I can't imagine being a clueless 12 year old again and starting off with Haskell.
People well tell you Haskell or anything in that vein is the bare minimum and if you can't understand any of it then you're a pajeet. In other news, John Carmack appears to have mentioned finding SICP quite challenging:
On a Scheme-related note, I've also seen a lot of threads where people are complaining about how they just can't understand continuations, which started with Scheme and later spread to other languages like Ruby. They're not pajeets because a pajeet wouldn't even know what a continuation is, much less tried to use it. Having said that, there are much less mind-bending applications of FP that are in regular use. For example, Python, R and SQL are in wide use in data science and all three have FP features (especially with extensions in the latter case).
 
On a Scheme-related note, I've also seen a lot of threads where people are complaining about how they just can't understand continuations, which started with Scheme and later spread to other languages like Ruby. They're not pajeets because a pajeet wouldn't even know what a continuation is, much less tried to use it.
oh continuations are easy they just turn the stack into a value so you can jump to it later using syntax that looks a lot like a function but it's NOT a function because you actually need delimited continuations (a slightly different but equally confusing can of worms) for that
perfectly reasonable and easy to understand language feature which is why you can use scheme and never touch continuations and nobody will hate you for it because continuations are the kind of shit you use to make scheme into <other language> where <other language> has weird concurrency features or evaluation rules
 
oh continuations are easy they just turn the stack into a value so you can jump to it later using syntax that looks a lot like a function but it's NOT a function because you actually need delimited continuations (a slightly different but equally confusing can of worms) for that
perfectly reasonable and easy to understand language feature which is why you can use scheme and never touch continuations and nobody will hate you for it because continuations are the kind of shit you use to make scheme into <other language> where <other language> has weird concurrency features or evaluation rules
I sense you're being funny here but that is exactly what Racket did after it became Racket and was no longer PLT Scheme. Ex:
 
never read it although isn't it a scheme book? scheme isn't quite as autistic as haskell is imo and could feasibly be used for ultimate beginners
I link it primarily because it goes over all of the core functional concepts and teaches recursion well. They literally start off beginners with this in many prominent universities.
 
never read it although isn't it a scheme book? scheme isn't quite as autistic as haskell is imo and could feasibly be used for ultimate beginners
Yes and because Scheme is very balkanized you will want the 2htdp teachpacks:
I have not read HtDP in full but having seen some of the content and solution code people posted it seems less extreme than SICP, but still worth reading. If I read it I am sure I would come away from it more knowledgeable.
I link it primarily because it goes over all of the core functional concepts and teaches recursion well. They literally start off beginners with this in many prominent universities.
A lot of that has changed over the years, not all for the better:
 
For me it sounds just like closure, but instead of having some variable you just take stack state.

From this it looks like GOTO and closure interpretations are kinda valid.
I tried to get DeepSeek to generate a "goto" example but it wouldn't run. That's the first and probably last time I'll ever "vibe code".

(On a serious note, DeepSeek is typically very good for answering "how do I do x in y language/library?" queries. This one is probably a little too obscure.)
 
Continuations, I'm told, can implement many flow control structures:
I wish I could offer you an explanation of how it all works
Haskell Wiki said:

Helpful metaphors, images​

Here is a collection of short descriptions, analogies or metaphors, that illustrate this difficult concept, or an aspect of it.

Imperative metaphors​

  • In computing, a continuation is a representation of the execution state of a program (for example, the call stack) at a certain point in time (Wikipedia's Continuation).
  • At its heart, call/cc is something like the goto instruction (or rather, like a label for a goto instruction); but a Grand High Exalted goto instruction... The point about call/cc is that it is not a static (lexical) goto instruction but a dynamic one (David Madore's A page about call/cc)

Functional metaphors​

  • Continuations represent the future of a computation, as a function from an intermediate result to the final result ([1] section in Jeff Newbern's All About Monads)
  • The idea behind CPS is to pass around as a function argument what to do next (Yet Another Haskell Tutorial written by Hal Daume III, 4.6 Continuation Passing Style, pp 53-56. It can also be read in wikified format).
  • Rather than return the result of a function, pass one or more Higher Order Functions to determine what to do with the result. Yes, direct sum like things (or in generally, case analysis, managing cases, alternatives) can be implemented in CPS by passing more continuations.
I like the 1st functional metaphor given, since it's easy to imagine how one could use the concept of passing computation states around. I use simple monadic continuation structures often when writing pure-functional parsers to pass intermediate states between recursive function calls.

Suppose you wanted to parse this JSON: [1, 2, 3, [4, 5, 6], 7]. Tokenized into a LISP-style list (and somewhat simplified for the sake of explanation), we have '('LBRACK 1 2 3 'LBRACK 4 5 6 'RBRACK 7 'RBRACK).

To parse the inner array functionally, you want to treat it as its own independent JSON object without making assumptions about the surrounding state. Recall that JSON is nothing more than a composition of standalone JS objects, so each element in the array should be possible to parse completely independently of one another. In other words, we should be able to call our main parse-json function on all elements of the arrays, as well as the arrays themselves, and have it all parse properly. So we need some way to call the array-parsing function on the inner array—in the middle of parsing the main outer array—and continue on parsing the rest of the outer array (i.e. the remaining '(7 'RBRACK)). Instead of doing crazy tricks to navigate our way through the list of tokens to find the end of the inner array, we can simply return the list with the remaining tokens from our inner array parsing function call in a pair/tuple along with the typical parsed array. So you're essentially passing the computation state following the parsing of the inner array along with the parsed array, which you then use to continue parsing from.

Instead of guessing/predicting the later state, simply pass it back to the caller. EZPZ.

To help illustrate, here's a quick and shitty JSON parser I wrote completely from scratch in Racket a few months ago for fun:
For the "continuations", I used Racket's structs. They're like normal lists, but with named members so you don't have to make use of shit like (cadddr res).
I didn't bother rigorously adhering to a spec or making exclusively good decisions. Caveat emptor.

I hereby dedicate this work to the public domain. Do what you like with it.

Code:
#lang racket

;; (char-numeric?) includes other unicode numerals.
;; We only use American Numerals™ here.
(define (char-digit? c)
  (and (char? c)
       (char>=? c #\0)
       (char<=? c #\9)))

;; A simple wrapper for (char-whitespace?) with a type check.
(define (char-ws? c)
  (and (char? c)
       (char-whitespace? c)))

(define (char->token c)
  (match c
    [#\[ 'LBRACK]
    [#\] 'RBRACK]
    [#\{ 'LCURLY]
    [#\} 'RCURLY]
    [#\: 'KSEP]
    [#\, 'VSEP]
    [#\" 'QUOTE]
    [#\' 'APOS]
    [x x]))

;; Simple parser continuation.
;; lst: remaining tokens.
;; res: result upon return.
(struct cont (lst res))

(define (unwrap cn)
  (match cn
    [(cont _ r) (car r)]))

(define (bad-brack brack ex)
  (eprintf "Unexpected bracket: ~v. Expected: ~v.\n" brack ex))

(define (begin-arr tokens)
  (define (end-arr lst res)
    (cont lst `((arr ,(reverse res)))))

  (let loop ([tokens tokens]
             [bs '()]
             [res '()])
    (match tokens
      ['() (cont '() `(,(reverse res)))]
      [`(RBRACK ,_ ...) (end-arr (cdr tokens) res)]
      [`(VSEP ,_ ...) (loop (cdr tokens) bs (cons (void) res))]
      [_ (let ([lres (parse-json/r tokens)])
           (match (cont-lst lres)
             [`(VSEP ,r ...) (loop r bs (cons (unwrap lres) res))]
             [`(RBRACK ,_ ...) (loop (cont-lst lres) bs (cons (unwrap lres) res))]
             [x (eprintf "Expected separator or closing bracket after value. Found ~v\n" x)]))])))

(define (char->digit c)
  (- (char->integer c) 48))

(define (begin-numeric tokens)
  (define (get-base n)
    (if (zero? n) 1
        (add1 (floor (log n 10)))))

  (define (end-numeric lst res)
    (cont lst `(,res)))

  (let loop ([tokens tokens]
             [dec #f]
             [res 0])
    (match tokens
      ['() (cont '() '(res))]
      [`(,(? char-digit?) ,_ ...) (loop (cdr tokens)
                                        dec
                                        (+ (* res 10)
                                           (char->digit (car tokens))))]
      ;; Decimals.
      ;; Process RHS as its own int value and then add it to res after scaling.
      ;; dec acts as a flag, helping to avoid processing additional decimal points.
      [`(#\. ,_ ...) (if (not dec) (let* ([rhs (loop (cdr tokens) #t 0)]
                                          [ex (get-base (unwrap rhs))])
                                     (end-numeric (cont-lst rhs)
                                                  (+ res (/ (unwrap rhs)
                                                            (expt 10. ex)))))
                         (error "Unexpected additional decimal."))]
      [_ (end-numeric tokens res)])))

(define (begin-str tokens)
  (define (quote-match? q qs)
    (and (not (null? qs))
         (symbol=? q (car qs))))

  (define (end-str lst res)
    (cont lst `(,(list->string (reverse res)))))

  (let loop ([tokens tokens]
             [qs '()]
             [res '()])
    (match tokens
      ['() (error "Reached EOF while parsing string value.")]
      [`(,(or 'APOS 'QUOTE) ,_ ...) (cond
                                      [(null? qs) (loop (cdr tokens) (cons (car tokens) qs) res)]
                                      [(quote-match? (car tokens) qs) (end-str (cdr tokens) res)]
                                      [else (loop (cdr tokens) qs (cons (car tokens) res))])]
      [`(#\\ ,(or #\' #\" #\\) ,r ...) (loop r qs (cons (cadr tokens) res))]
      [_ (loop (cdr tokens) qs (cons (car tokens) res))])))

;; key-value pair.
(struct kvp (k v))

(define (begin-obj tokens)
  (define (end-obj lst res)
    (cont lst `((obj ,(reverse res)))))

  (define (parse-kvp tokens)
    (match tokens
      [`(,(or 'APOS 'QUOTE) ,_ ...) (let* ([kres (parse-json/r tokens)] ; parse key
                                           [vres (parse-json/r (cdr (cont-lst kres)))]) ; parse value
                                      (cont (cont-lst vres)
                                            `(,(kvp (unwrap kres)
                                                    (unwrap vres)))))]
      [_ (eprintf ("Expected key-value pair. Found ~v\n" tokens))]))

  (let loop ([tokens tokens]
             [bs '()]
             [res '()])
    (match tokens
      ['() (end-obj tokens res)]
      [`(RCURLY ,_ ...) (end-obj (cdr tokens) res)]
      [`(RBRACK ,_ ...) (bad-brack (car tokens) 'NONE)]
      [_ (let ([lres (parse-kvp tokens)])
           (match (cont-lst lres)
             [`(VSEP ,r ...) (loop r bs (cons (unwrap lres) res))]
             [`(RCURLY ,_ ...) (loop (cont-lst lres) bs (cons (unwrap lres) res))]
             [x (eprintf "Expected separator or closing brace after kvp. Found ~v\n" x)]))])))

(define (parse-json/r tokens)
  ;; Check for special keyword values like Infinity, NaN, and bools.
  (define (parse-special tokens)
    (match tokens
      ;; Infinity
      [`(#\I #\n #\f #\i #\n #\i #\t #\y ,r ...) (cont r '(+inf.f))]
      ;; -Infinity
      [`(#\- #\I #\n #\f #\i #\n #\i #\t #\y ,r ...) (cont r '(-inf.f))]
      ;; NaN
      [`(#\N #\a #\N ,r ...) (cont r '(+nan.f))]
      ;; true
      [`(#\t #\r #\u #\e ,r ...) (cont r '(#t))]
      ;; false
      [`(#\f #\a #\l #\s #\e ,r ...) (cont r '(#f))]
      [x (eprintf "Unexpected token: ~v\n" x)]))

  (match tokens
    ['() (cont '() '())]
    ;; Arrays.
    [`(LBRACK ,_ ...) (begin-arr (cdr tokens))]
    ;; Objects.
    [`(LCURLY ,_ ...) (begin-obj (cdr tokens))]
    ;; Strings.
    [`(,(or 'QUOTE 'APOS) ,_ ...) (begin-str tokens)]
    ;; Positive numbers.
    [`(,(? char-digit?) ,_ ...) (begin-numeric tokens)]
    ;; Negative numbers.
    [`(#\- ,(? char-digit?) ,_ ...) (let ([lres (begin-numeric (cdr tokens))]) ; cdr to drop negative sign.
                                      (cont (cont-lst lres) `(,(* (unwrap lres) -1))))]
    [_ (parse-special tokens)]))

(define (parse-json tokens)
  ;; Unwrap parse result.
  (unwrap (parse-json/r tokens)))

(define (tokenize str)
  (define (quote-match? q qs)
    (and (not (null? qs))
         (char=? q (car qs))))

  (let loop ([chars (string->list str)]
             [qs '()]
             [res '()])
    (match chars
      ['() (reverse res)]
      ;; Avoid tokenizing string contents.
      [`(,(or #\" #\') ,_ ...) (cond
                                 [(null? qs) (loop (cdr chars)
                                                   (cons (car chars) qs)
                                                   (cons (char->token (car chars)) res))]
                                 [(quote-match? (car chars) qs) (loop (cdr chars)
                                                                      (cdr qs) ; drop opening quote from stack.
                                                                      (cons (char->token (car chars)) res))]
                                 [else (loop (cdr chars) qs (cons (car chars) res))])]
      ;; Drop whitespace if not in string.
      [`(,(? char-ws?) ..1 ,r ...) (if (null? qs) (loop r qs res)
                                       (loop (cdr chars) qs (cons (car chars) res)))]
      [_ (if (null? qs) (loop (cdr chars) qs (cons (char->token (car chars)) res))
             (loop (cdr chars) qs (cons (car chars) res)))])))

(define (print-ast ast)
  (define (print-inner ner)
    (define (print-ws ner)
      (unless (null? (cdr ner))
        (printf " ")))

    (match ner
      ['() (void)]
      [`(,(? pair?) ,_ ...) (print-ast/r (car ner))
                            (print-ws ner)
                            (print-inner (cdr ner))]
      [`(,(kvp k v) ,_ ...) (printf "(~v: " k)
                            (print-inner `(,v))
                            (printf ")")
                            (print-ws ner)
                            (print-inner (cdr ner))]
      [_ (printf "~v" (car ner))
         (print-ws ner)
         (print-inner (cdr ner))]))

  (define (print-ast/r res)
    (match res
      ['() (void)]
      [`(arr (,r ...)) (printf "(arr (")
                       (print-inner r)
                       (printf "))")]
      [`(obj (,r ...)) (printf "(obj (")
                       (print-inner r)
                       (printf "))")]
      [_ (printf "~v" res)]))

  (print-ast/r ast)
  (printf "\n"))

;; Tests
(print-ast (parse-json (tokenize "[12.4, 2,3,, 4,      5]")))
(print-ast (parse-json (tokenize "[1, 2,'ab[c, NaN]ef' , -Infinity, 5, true]")))
(print-ast (parse-json (tokenize "[1, 2,'ab\\\"cd\\\"ef' , 4, 5]")))
(print-ast (parse-json (tokenize "['true\"false\"',, [1,false,  -2.14 ,3] ,88]")))
(print-ast (parse-json (tokenize "[1, 2,'ab[c\\\\d]ef' , {\"Inf'in'ity\": NaN}, ['true', [1,false,   2.148 ,NaN],88], 5, true]")))
(print-ast (parse-json (tokenize "'some_text'")))
(print-ast (parse-json (tokenize "[{'some_key': 14,\"another_key\": [8, 1, 2], 'yet_another_key': {'abc': 123}}, 420,{'wt':false}]")))

You can extend this idea by passing functions as arguments to be called later on, as with a traditional continuation. It's all centered around this idea of passing encapsulated states and results around between functions.
Imagine passing functions+environments around this way to make whatever you're doing entirely tail-recursive. That's continuation-passing style in a nutshell.
 
Last edited:
10 PRINT "NIGGER"
20 GOTO 10

GOTO is just BASIC for what ASM chuds call JMP.
Reminds me, what's a good guide for translating constructs you have in C to assembly? The most "assembly" I did was getting to at least year 20 in Human Resource Machine after which I quit because of the lack of labeled subroutines and the visual programming style looking like literal spaghetti.
 
Reminds me, what's a good guide for translating constructs you have in C to assembly? The most "assembly" I did was getting to at least year 20 in Human Resource Machine after which I quit because of the lack of labeled subroutines and the visual programming style looking like literal spaghetti.
Unironically, gcc -O0 and objdump -M intel -d. I always object when people say C is close to the metal, but you can translate its constructs very straightforwardly. It's just that the big optimizing compilers no longer do so. Also obligatory: Assembly is not hard, just tedious.
 
To help illustrate, here's a quick and shitty JSON parser I wrote completely from scratch in Racket a few months ago for fun:
I appreciate the effort but I haven't really written parsers yet (and no regex does not count as I'm sure you know). I'd have to take understanding all of that one step at a time.
Unironically, gcc -O0 and objdump -M intel -d. I always object when people say C is close to the metal, but you can translate its constructs very straightforwardly. It's just that the big optimizing compilers no longer do so. Also obligatory: Assembly is not hard, just tedious.
A book on C I remember using, which was written in the 80s or early 90s, described C as a "glue language" which means that there once was a time when C was basically seen the way Python or Lua are seen today. Convenient but kinda sluggish.
 
Yes and no. Per the Chomsky hierarchy, regex describes a regular language. It's the lowest tier of parser to be sure but I would argue it is one.

I meant for something a little more heavy-duty than some shitty web scraper. I do have quite a few resources to try but currently I am trying to lock in my gains from DataCamp by recording everything I learn in locally hosted Jupyter Notebooks (which are actually a lot more fun to write and use than I thought they'd be).
 
A book on C I remember using, which was written in the 80s or early 90s, described C as a "glue language" which means that there once was a time when C was basically seen the way Python or Lua are seen today.
If you read old programming texts from the Bell Labs days, you'll often see C described as a high-level language. It's funny how high the relative "level" has shifted over the years.

Yes and no. Per the Chomsky hierarchy, regex describes a regular language. It's the lowest tier of parser to be sure but I would argue it is one.
It's close to parsers in the sense that the pattern matching and lexical analysis can be boiled down to finite-state automata. State machines. Note that a parser's analysis may involve multiple automata working in tandem.
 
Last edited:
I sense you're being funny here but that is exactly what Racket did after it became Racket and was no longer PLT Scheme. Ex:
i am being half funny and half not funny
scheme control flow operators + macros = fuck you we have erlang or go or prolog or (weird dynamic typed version of) haskell or whatever at home
this just sounds as retarded as goto
correct
scheme continuations are just a time traveling super goto and you should be wary of them
however when you consider that a loop is just syntax sugar over a regular goto and you have the ability to make your own sugar with the super gotos...
If you read old programming texts from the Bell Labs days, you'll often see C described as a high-level language. It's funny how high the relative "level" has shifted over the years.
it technically does need a runtime and has to have more structure than the raw hardware itself provides so you can do stuff like call functions
"low-level" these days seems to mainly mean "doesn't have a garbage collector"
javascript is actually pretty lightweight if you write the most retarded implementation of the language possible
the massive engines are due to how impossible it is to statically optimize javascript so you have to have 12 massive compilers for normal functions, weird functions, big functions, functions that are called after their environment gets messed up, little functions, and stupid functions that use deprecated language features that fuck up all the other compilers
what i'm saying is: nice late binding stupid
 
1749689071116.webp

Erm, chuddy? That's a heckin deprecated feature. Not very wholesomerino for you to use that! Enjoy reading the docs again, stalker child!
 
Back