{"id":16260159,"url":"https://github.com/twolodzko/gosch","last_synced_at":"2025-03-19T22:31:06.728Z","repository":{"id":50585227,"uuid":"448340947","full_name":"twolodzko/gosch","owner":"twolodzko","description":"Minimal Scheme implemented in Go","archived":false,"fork":false,"pushed_at":"2022-12-04T09:08:17.000Z","size":356,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-28T21:32:24.799Z","etag":null,"topics":["go","lisp","lisp-interpreter","programming-language","scheme"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twolodzko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-15T17:09:49.000Z","updated_at":"2023-12-31T01:15:49.000Z","dependencies_parsed_at":"2023-01-22T20:45:11.206Z","dependency_job_id":null,"html_url":"https://github.com/twolodzko/gosch","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twolodzko%2Fgosch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twolodzko%2Fgosch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twolodzko%2Fgosch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twolodzko%2Fgosch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twolodzko","download_url":"https://codeload.github.com/twolodzko/gosch/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244029429,"owners_count":20386406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","lisp","lisp-interpreter","programming-language","scheme"],"created_at":"2024-10-10T16:06:33.753Z","updated_at":"2025-03-19T22:31:06.447Z","avatar_url":"https://github.com/twolodzko.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Minimal Scheme implemented in Go\n\n**gosch** is pronounced the same as *gosh*, as in *\"oh gosh, why would anyone implement Scheme again?!\"*.\n\n\u003e *Do It, Do It Again, and Again, and Again ...*  \n\u003e \u0026emsp; — *The Little Schemer* by Friedmann and Felleisen\n\n![Lisp cycles XKCD #297: \"Those are your father's parentheses. Elegant weapons for a more... civilized age.\"](https://imgs.xkcd.com/comics/lisp_cycles.png)\n\n(source https://xkcd.com/297/)\n\n## Oh gosch, it's Scheme\n\nAs in classic Lisps, **gosch** recognizes the two main data structures *atoms* and *[pairs]* (*aka* [linked lists]).\nThe variety of available data types is limited to *atoms* like *numbers* (*integers* and *floats*) and\n*booleans* (`#t` and `#f`). There is also a limited support for *strings*. The implementation is [properly tail-recursive]\nas [required by Scheme].\nUnlike the classic Scheme, there is null type `nil` and procedures return it instead of undefined results,\nfor example `(if #f 'ok)` returns `\u003cnil\u003e`. There is no distinction between round and square brackets, so they can be\nused interchangeably. Unlike in some Lisps, all the lists are guaranteed to be [proper] and there are no dotted pairs.\nDots within lists are ignored, so using [dotted pair notation] would not raise errors.\n\n**gosch** implements the following procedures:\n\n- `(car pair)` returns the first element, and `(cdr pair)` returns the second element (tail) of the *pair*.\n- `(cons obj1 obj2)` creates pair where *obj1* is car and *obj2* is cdr. `(list obj1 obj2 ...)` is the same as\n  `(cons obj1 (cons obj2 (cons obj3 ...)))`.\n- `(define name value)` assigns *value* to a *name* in the current environment. `(set! name value)` if the *name* exists\n  in the current or enclosing environment, it sets it to the *value*, otherwise, it assigns *value* to a *name* in the\n  current environment.\n- `(lambda (arg1 arg2 ...) expr1 expr2 ...)` defines a [lambda expression] (*aka* function). There is also an equivalent,\nshorter way of writing `(define name (lambda (arg1 arg2 ...) expr1 expr2 ...))` as `(define (name arg1 arg2 ...) expr1 expr2 ...)`.\n- `(let ((name1 value1) (name2 value2) ...) expr1 expr2 ...)` evaluates *expr1*, *expr2*, ... in the local environment,\n  with *name1*, *name2*, ... variables present; returns the result of evaluating the last *exprN* expression.\n  `let*` is like `let`, but the *arg1*, *arg2*, ... arguments are evaluated sequentially, from left to right,\n  and the following arguments can depend on the preceding.\n- `(if condition if-true if-false)` and `(cond (test1 expr1) (test2 expr2) ...)` conditionals with special `else`\ncondition always evaluating to `#t`, e.g. `(cond (else 'yay))`.\n- `(begin expr1 expr2 ...)` evaluates *expr1*, *expr2*, ..., returns the result of evaluating the last *exprN* expression.\n- `(do ((var init update) ...) (test result ...) expr ...)` [loop iterator].\n- `(quote expr)` or `'expr` returns *expr* without evaluating it. While `quote` is commonly used for constructing lists,\n  [it is not the same] as `list`. `(quasiquote expr)` or `` `expr`` works like `quote`, but parts of the expression can\n  be evaluated using `(unquote expr)` or `,expr`, for example `` `(2 + 2 = ,(+ 2 2))`` will evaluate to `(2 + 2 = 4)`.\n- `(eval expr)` does the opposite to `quote` by evaluating *expr*, e.g. `(eval '(+ 2 2))` returns `4` rather than the\n`(+ 2 2)` list.\n- `(eq? obj1 obj2)` compares if two objects are equal, for pairs only checks if they point to the same memory location.\n- Logical `(not obj)`, `and`, and `or`, e.g. `(and obj1 obj2 ...)`.\n- Arithmetic operators `+`, `-`, `*`, `/`, e.g. `(+ x1 x2 ...)`, and `(% x1 x2)` for modulo.\n  Those procedures promote integers to floats if any of the arguments is a float. Division `/` always promotes arguments\n  to floats, for integer division use `//`.\n- Numerical comparison operators `\u003c`, `=`, `\u003e`, e.g. `(\u003c x1 x2 ...)`.\n- Checkers for the [disjoint types]: `pair?`, `number?`, `boolean?`, `string?`, `symbol?`, `procedure?`, and\n  other checkers: `integer?`, `float?`, `null?` (empty list) and `nil?` (null value).\n- `-\u003eint` and `-\u003efloat` transformations from any numeric types to integers and floats.\n- `(string expr ...)` converts *expr*s to string, `(display expr ...)` prints them, `(newline)` prints new line,\n  and `(error expr ...)` raises exceptions with *expr*s as a message. `(substring str start end)` cuts the *start:end*\n  slice of the *str* string. `(string-length str)` returns the length of a string.\n- You can run `(debug #t/#f)` to turn the debug mode on and off. In debug mode, all the evaluated expressions and\n  their enclosing environments are printed. `(timeit expr)` measures and prints evaluation time of the *expr*.\n\nComments begin with `;` and everything that follows, from the semicolon until the end of the line, is ignored.\n\n\n## Details of the Lisp design\n\n1. Everything is an *[S-expression]*.\n2. There are two kinds of S-expressions: *atom* and *pair* of S-expressions.\n3. Atoms are the basic data types like booleans, numbers, strings, etc.\n4. Pairs (lists) are implemented as [linked lists]:\n\n   ```go\n   type Pair struct {\n       This Sexpr\n       Next *Pair\n   }\n   ```\n   \n   As in every Lisp, they are written as `(this next)`. The pair has a head and tail,\n   that can be accessed using the `car` and `cdr` procedures.\n\n   ```\n     ( elem1 ( elem2 ( elem3 ( ... ))))\n   1    car  └───────── cdr ─────────┘\n   2            car  └───── cdr ────┘\n   3                    car  └ cdr ┘\n   ```\n\n   Pairs can be empty `()`, we call them the *null* lists.\n\n   Accessing the first element of the linked list, removing it, or prepending pair\n   with a new value have O(1) complexity, so those operations would happen the\n   most often in Lisps.\n5. A *procedure* (function) is also just a pair, where the name of the procedure\n   is the first element of the pair, and the arguments are the following elements.\n   For example, `(+ 1 2 3)` is a function that calculates the sum of the three numbers.\n6. There is a special kind of atom, a *symbol* that can be used by itself or\n   as a placeholder.\n7. When evaluating an S-expression, the following rules apply:  \n   1. a *symbol* is evaluated by looking up the value it points to in the\n      surrounding environment (see below).\n   2. other *atoms* are evaluated to themselves.\n   3. a *pair* is evaluated by evaluating each of its elements, and then\n      calling the procedure named by the first element of the pair with\n      arguments at the following elements of the pair.\n8. There are some procedures with special rules of evaluation, for example\n   `(quote sexpr)` returns `sexpr` without evaluating it; `if` and `cond`\n   will evaluate the arguments conditionally; `and` and `or` will evaluate\n   the arguments sequentially, possibly stopping before evaluating\n   every argument.\n9. Everything resides within some surrounding *environment*. By default,\n   this is a global environment, but there are procedures (`let`, `do`, and `lambda`)\n   that can create their environments. For example:\n\n   ```scheme\n   (define x 3)    ;; define x in global environment\n   (let ((y 4))    ;; define y in local environment\n        (+ x y))   ;; =\u003e 7\n   (+ x y)         ;; =\u003e ERROR\n   ```\n\n   The local environment has access to its objects, but also to the parent environment,\n   but not the other way around. We call it [*lexical scoping* or *closures*].\n10. `lambda` is a special procedure that returns a procedure. It is defined in\n   terms of its arguments and the body of the function to be executed.\n\n    ```scheme\n    (define add (lambda (x y) (+ x y)))\n    (add 2 5)       ;; =\u003e 7\n    ```\n\n11. Some procedures are [tail-call optimized], this includes `begin`, `if`, `cond`,\n    `let`, and `lambda`. While regular procedures are evaluated by returning the\n    result of the computation, in tail-call optimized procedures the last expression\n    in the body of the procedure is returned unevaluated. This transforms recursive\n    calls into a for-loop. The simplified code below illustrates this:\n\n    ```go\n    func Eval(sexpr Sexpr, env *Env) Any {\n        for {\n            switch val := sexpr.(type) {\n            case Symbol:\n                return getSymbol(val, env)\n            case *Pair:\n                fn := Eval(val.This, env)\n                switch fn := fn.(type) {\n                case Primitive:\n                    return fn(val.Next, env)\n                case TailCallOptimized:\n                    sexpr, env = fn(val.Next, env)\n                }\n            default:\n                return sexpr\n            }\n        }\n    }\n    ```\n\nThat's it. Nothing more is needed to build a minimal Scheme.\n\n\n [pairs]: https://web.mit.edu/scheme_v9.2/doc/mit-scheme-ref/Lists.html#Lists\n [linked lists]: https://en.wikipedia.org/wiki/Linked_list\n [disjoint types]: https://www.cs.cmu.edu/Groups/AI/html/r4rs/r4rs_5.html#SEC23\n [lambda expression]: https://www.cs.cmu.edu/Groups/AI/html/r4rs/r4rs_6.html#SEC30\n [properly tail-recursive]: https://github.com/kanaka/mal/blob/master/process/guide.md#step-5-tail-call-optimization\n [required by Scheme]: https://www.cs.cmu.edu/Groups/AI/html/r4rs/r4rs_3.html#SEC6\n [it is not the same]: https://stackoverflow.com/questions/34984552/what-is-the-difference-between-quote-and-list\n [S-expression]: https://en.wikipedia.org/wiki/S-expression\n [*lexical scoping* or *closures*]: https://en.wikipedia.org/wiki/Closure_(computer_programming)\n [tail-call optimized]: https://stackoverflow.com/questions/310974/what-is-tail-call-optimization\n  [loop iterator]: https://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.2.4\n [proper]: https://wiki.c2.com/?ProperList\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwolodzko%2Fgosch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwolodzko%2Fgosch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwolodzko%2Fgosch/lists"}