{"id":36680791,"url":"https://github.com/tsoding/lamb","last_synced_at":"2026-01-12T10:59:03.939Z","repository":{"id":329172539,"uuid":"1117496690","full_name":"tsoding/lamb","owner":"tsoding","description":"Smallest Pure Functional Programming Language in C","archived":false,"fork":false,"pushed_at":"2025-12-20T01:40:45.000Z","size":192,"stargazers_count":120,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-21T07:26:21.111Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","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/tsoding.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-16T11:52:02.000Z","updated_at":"2025-12-21T05:38:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tsoding/lamb","commit_stats":null,"previous_names":["tsoding/lamb"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/tsoding/lamb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tsoding%2Flamb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tsoding%2Flamb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tsoding%2Flamb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tsoding%2Flamb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tsoding","download_url":"https://codeload.github.com/tsoding/lamb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tsoding%2Flamb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T10:58:46.209Z","status":"ssl_error","status_checked_at":"2026-01-12T10:58:42.742Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-01-12T10:59:03.009Z","updated_at":"2026-01-12T10:59:03.926Z","avatar_url":"https://github.com/tsoding.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003cimg src=\"assets/LAMB_NBG_LOGO.png\", width=\"200\"/\u003e\n    \u003ch1\u003eLamb\u003c/h1\u003e\n\u003c/div\u003e\n\nTiny Pure Functional Programming Language in C. Based on [Untype Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus) with Normal Order reduction.\n\n## Quick Start\n\n```console\n$ cc -o lamb lamb.c\n$ ./lamb ./std.lamb\n...\n,---@\u003e\n W-W'\nEnter :help for more info\n@\u003e pair 69 (pair 420 1337)\nRESULT: \\f.f 69 (\\f.f 420 1337)\n@\u003e xs = pair 69 (pair 420 1337)\nCreated binding xs\n@\u003e first xs\nRESULT: 69\n@\u003e second xs\nRESULT: \\f.f 420 1337\n@\u003e first (second xs)\nRESULT: 420\n@\u003e\n```\n\nIt's recommended to use Lamb with [rlwrap](https://github.com/hanslub42/rlwrap). Just do\n\n```console\n$ rlwrap ./lamb\n```\n\nand you get Bash-style history and command line navigation.\n\n## Syntax\n\nThe syntax is based on the [Notation of Untype Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus_definition#Notation) and provides few improvements over it.\n\n### Variables\n\nVariables are any alphanumeric names:\n\n```\n@\u003e :ast x\n[VAR] x\n@\u003e :ast hello69\n[VAR] hello69\n@\u003e :ast 69420\n[VAR] 69420\n@\u003e\n```\n\nYes, fully numeric sequences of characters are also considered names, because they are alphanumeric. This may change in the future.\n\n### Functions\n\nTo denote functions instead of small greek lambda `λ` we use backslash `\\` (this may change in the future, we are considering allowing `λ` as an alternative):\n\n```\n@\u003e :ast \\x.x\n[FUN] \\x\n+--[VAR] x\n@\u003e :ast \\x.\\y.x\n[FUN] \\x\n+--[FUN] \\y\n   +--[VAR] x\n@\u003e\n```\n\nThe body of the function extends as far right as possible. Use parenthesis to denote the boundaries of the function:\n\n```\n@\u003e :ast \\x.x x\n[FUN] \\x\n+--[APP]\n   +--[VAR] x\n   +--[VAR] x\n@\u003e :ast (\\x.x) x\n[APP]\n+--[FUN] \\x\n|  +--[VAR] x\n+--[VAR] x\n@\u003e\n```\n\nSince the variable names can be longer than 1 character we can't use that silly mathematician trick of stitching their names together by saying that `\\xy.x` means `\\x.\\y.x`, because in Lamb it just means it's a single function with a parameter `xy`:\n\n```\n@\u003e :ast \\xy.x\n[FUN] \\xy\n+--[VAR] x\n@\u003e\n```\n\nInstead we allow you to drop consequent backslashes turning the dot `.` into a parameter separator:\n\n```\n@\u003e :ast \\x.y.x\n[FUN] \\x\n+--[FUN] \\y\n   +--[VAR] x\n@\u003e\n```\n\n## Applications\n\nJust separate two lambda expressions with a space:\n\n```\n@\u003e :ast (\\x.x) x\n[APP]\n+--[FUN] \\x\n|  +--[VAR] x\n+--[VAR] x\n@\u003e (\\x.x) x\nRESULT: x\n@\u003e\n```\n\nYou can drop parenthesis if the expression is unambiguous:\n\n```\n@\u003e :ast f \\x.x\n[APP]\n+--[VAR] f\n+--[FUN] \\x\n   +--[VAR] x\n@\u003e\n```\n\nApplications are left-associative:\n\n```\n@\u003e :ast f a b c d\n[APP]\n+--[APP]\n|  +--[APP]\n|  |  +--[APP]\n|  |  |  +--[VAR] f\n|  |  |  +--[VAR] a\n|  |  +--[VAR] b\n|  +--[VAR] c\n+--[VAR] d\n@\u003e\n```\n\n### Magics\n\nMagics are special names that start with `#` and cannot be used as parameters of functions (that is they are always free). They perform various useful side effects.\n\n`#trace` - when applied to a lambda expression it becomes a potential redex. When reduced it forces the reduction of its argument, prints the reduced argument to the console, and then returns it. Useful for debugging.\n\n```\n@\u003e (#trace f) (#trace a) (#trace b) (#trace c)\nTRACE: f\nTRACE: a\nTRACE: b\nTRACE: c\nRESULT: ((f a) b) c\n@\u003e\n```\n\n`#void` - when applied to a lambda expression it becomes a potential redex. When reduced it forces the reduction of its argument and returns itself. Useful when you only care about the side effects of the evaluation but not the result which might be long and useless anyway:\n\n```console\n$ ./lamb ./std.lamb\n@\u003e xs = cons 69 (cons 420 (cons 1337 (cons foo (cons bar (cons bar nil)))))\nCreated binding xs\n@\u003e #void (trace_list xs)\nTRACE: 69\nTRACE: 420\nTRACE: 1337\nTRACE: foo\nTRACE: bar\nTRACE: bar\nRESULT: #void\n```\n\n## Bindings\n\nYou can take any expression and assign it to a name:\n\n```\n@\u003e id = \\x.x\nCreated binding id\n@\u003e\n```\n\nThen you can use that name instead of the expression:\n\n```\n@\u003e id 69\nRESULT: 69\n@\u003e\n```\n\nYou can reassign the existing bindings at any moment\n\n```\n@\u003e id = \\y.y\nUpdated binding id\n@\u003e\n```\n\nTo list all available bindings use `:list` command:\n\n```\n@\u003e a = 69\nCreated binding a\n@\u003e b = 420\nCreated binding b\n@\u003e c = 1337\nCreated binding c\n@\u003e :list\nid = \\y.y;\na = 69;\nb = 420;\nc = 1337;\n@\u003e\n```\n\n### Semantics of Bindings\n\nThe bindings are not evaluated until you use them in an expression.\n\nWhen you enter an expression which contains the names of the bindings into the REPL to evaluate, first things that happens is the binding names are substituted with their corresponding values before starting the evaluation. The bindings are substituted in a reversed order, so be careful with the order in which you create the bindings. Reassigning already existing bindings does not change their order. You have to `:delete` the binding first and create it again to affect its order.\n\nSince each binding is applied only once you CANNOT use them for recursion\n\n```\n@\u003e loop = loop\nCreated binding loop\n@\u003e loop\nRESULT: loop\n@\u003e\n```\n\nUse [Y combinator](https://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator) to organize the recursion:\n\n```\n@\u003e Y = \\f.(\\x.f (x x)) (\\x.f (x x))\nCreated binding Y\n@\u003e :debug  Y g\nDEBUG: (\\f.(\\x.f (x x)) (\\x.f (x x))) g\nDEBUG: (\\x.g (x x)) (\\x.g (x x))\nDEBUG: g ((\\x.g (x x)) (\\x.g (x x)))\nDEBUG: g (g ((\\x.g (x x)) (\\x.g (x x))))\nDEBUG: g (g (g ((\\x.g (x x)) (\\x.g (x x)))))\nDEBUG: g (g (g (g ((\\x.g (x x)) (\\x.g (x x))))))\nDEBUG: g (g (g (g (g ((\\x.g (x x)) (\\x.g (x x)))))))\nDEBUG: g (g (g (g (g (g ((\\x.g (x x)) (\\x.g (x x))))))))\nDEBUG: g (g (g (g (g (g (g ((\\x.g (x x)) (\\x.g (x x)))))))))\nDEBUG: g (g (g (g (g (g (g (g ((\\x.g (x x)) (\\x.g (x x))))))))))\n...\n```\n\n### Managing the Bindings Files\n\nYou can save current bindings to a file with `:save` command:\n\n```\n@\u003e id = \\x.x\nCreated binding id\n@\u003e const = \\x.y.x\nCreated binding const\n@\u003e true = const\nCreated binding true\n@\u003e false = \\x.y.y\nCreated binding false\n@\u003e :save main.lamb\nSaved all the bindings to main.lamb\n@\u003e :q\n$ cat main.lamb\nid = \\x.x;\nconst = \\x.y.x;\ntrue = const;\nfalse = \\x.y.y;\n$\n```\n\nYou can load the bindings with the `:load` command:\n\n```\n@\u003e :load main.lamb\nCreated binding id\nCreated binding const\nCreated binding true\nCreated binding false\n@\u003e :list\nid = \\x.x;\nconst = \\x.y.x;\ntrue = const;\nfalse = \\x.y.y;\n@\u003e\n```\n\nLoading the bindings clears out all the previously defined bindings.\n\nIMPORTANT! The bindings in the bindings file are separate with a semicolon `;`.\n\nRepeating `:load` or `:save` without an argument applies it the last saved or loaded file (a.k.a. the active file).\n\nUse `:edit` command to open and edit the bindings file in an external editor. The default editor is `vi` but you can set it with `$EDITOR` and `$LAMB_EDITOR` environment variable.\n\nWe provided a bunch of useful bindings in [std.lamb][./std.lamb].\n\nPassing a file as a command line argument to the interpreter acts as if you instantly `:load`-ed it.\n\n```console\n$ ./lamb ./main.lamb\nCreated binding id\nCreated binding const\nCreated binding true\nCreated binding false\n,---@\u003e\n W-W'\nEnter :help for more info\n@\u003e :list\nid = \\x.x;\nconst = \\x.y.x;\ntrue = const;\nfalse = \\x.y.y;\n@\u003e\n```\n\nThe file also automatically becames the active file. So `:load`, `:save`, and `:edit` will operate on it by default.\n\nThe interpreter can only work with one active file at a time right now.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftsoding%2Flamb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftsoding%2Flamb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftsoding%2Flamb/lists"}