{"id":13727037,"url":"https://github.com/jaredly/jerd","last_synced_at":"2025-04-30T18:09:55.094Z","repository":{"id":52378138,"uuid":"337937236","full_name":"jaredly/jerd","owner":"jaredly","description":null,"archived":false,"fork":false,"pushed_at":"2021-10-29T00:42:32.000Z","size":19623,"stargazers_count":37,"open_issues_count":2,"forks_count":1,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-30T18:09:46.751Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaredly.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-02-11T05:20:37.000Z","updated_at":"2025-04-25T00:06:29.000Z","dependencies_parsed_at":"2022-09-06T03:41:21.413Z","dependency_job_id":null,"html_url":"https://github.com/jaredly/jerd","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/jaredly%2Fjerd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Fjerd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Fjerd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredly%2Fjerd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaredly","download_url":"https://codeload.github.com/jaredly/jerd/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251758170,"owners_count":21638989,"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":[],"created_at":"2024-08-03T01:03:36.693Z","updated_at":"2025-04-30T18:09:55.069Z","avatar_url":"https://github.com/jaredly.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# jerd\n(rhymes with \"bird\" or \"herd\")\n\na language, I think\n\n## Development\n\nCli/core (in the `language` directory)\n- `yarn watch` in one tab\n- `yarn watch-tests` in another\n\nWeb/editor (in the `web` directory)\n- `yarn start`\n- open up `http://localhost:4343`\n\n## Basic cool things\n\n- Code is typed, hashed AST in a database, not text in a file\n    - reference toplevel terms by hash, not by name\n    - type checking happens exactly once per term\n- Algebraic effects (full purity, no )\n- Compiles to _readable_ typescript, glsl, and go\n- {...extend type} for ~row polymorphism\n    - fancy enums and records\n    - records can have default values\n- generics (with ~bounds)\n- compile-time macros\n\n## Inspired by\n\n- Unison (algebraic effects)\n- Elm (purity)\n- Roc ()\n- Reason (compile to readable typescript)\n\n\n\n\n\n\n\n\n\n## (intended) Features:\n\n- [x] algebraic effects\n- [x] compile to readable javascript\n- [x] code is hashed typed AST in a database, not text in files\n    - for convenience of reading and stuff, there will be a readable\n      file format. It will not be the main method of development though.\n      but it's also super useful for showing examples, and project templates,\n      and runtime tests.\n- [ ] typed macros\n    - also untyped macros I guess (?) for when you want to make changes before the typing has happened.\n    - but macros are written in this language, and so are referenced by hash, so you can run them once and store the results right? well I guess if they have access to the typing env, then it's possible for them to be less reproducible. I'll need to make sure they are deterministic.\n- [ ] modular implicits (type classes)\n- [ ] higher kinded types\n- [ ] structured editor\n    - you can annotate an AST node with a \"log me please\" thing that will be interpreted by the dev env, but doesn't impact the hash. For debugging.\n- [x] sum \u0026 product types (rich enums \u0026 structs/records)\n- [x] \"row polymophism\"-ish. subtyping on both enums and structs\n- [x] generics\n- [x] well type-checked interface with typescript\n- [ ] \"partial\" types for WIP development\n    - if you have a type error, you can still compile, but your function\n      will now require the \"type error\" effect, which isn't something you can\n      handle in user code, meaning you can only run this code inside of the\n      dev env.\n      inspured by that one language, that maybe was a color? or a nut?\n      had a structured editor.\n- [ ] affine types for safe mutability\n    - I might actually not do this... following roc's lead seems like the more reasonable approach.\n    - yeah, I don't think people are ready to think in linear types,\n\n\n\n\n## Inspirations\n\n- unison\n- rust\n- ocaml/rescript\n- shem/golem\n- scheme\n- clojure\n\n## What do I want to build?\n\nA web party thing for making fun animated gifs.\nYou will have a canvas\nand such.\n\nWhat's the method for interacting with the canvas?\nsome pure functional description of a scene?\nor mutable stuffs?\n\nhmm.\ninspired by http://www.quil.info/ probably?\nalso reprocessing to some extent.\n\n\n## Status\n\n- [x] working on getting a basic effects setup going\n- [x] need to support self-recursive functions.\n- [x] then probably need to support `let`, b/c that's important\n\nWhat am I trying to build?\nAs my example project?\nA webserver? idk.\nOh whats my react interop gonna look like?\nThe type of react fns will definitely be required pure. so that's fine.\nehm\nalso do I actually want to try to build a go backend?\ni wonder what i would do with it.\n\n\n## Some local type inference\n\nbecause I'll need it I assume.\n\nBasic idea:\n- whenever I come across an undetermined type, add a type arg to the local env\n- when I encounter a constraint on the type, add that constraint to a list\n- at the end, try to unify all constraints. This can be polynomial time or something, it's fine.\n\n\n## CPS how do we do it now.\n\nSo basically I'm thinking: Every function is either CPS or Pure.\nSome functions are generic, so can be both. And those will have a hash_XYZ_pure and hash_XYZ.\n\n## Code is referenced by-hash, not by-name\n\n- renames don't break things\n- reordering args (?) um doesn't break things? (maybe need annotation on the function definition to indicate visual reordering)\n- if you change a function, none of the things using it change, unless you rebase them explicitly.\n- same for if you change a type\n\n## Macros\n\nMacros get a typed-ast, and the typing environment.\nAll terms are evaulated \"top\" to \"bottom\", only explicit circular dependencies for terms declared at the same time.\nIncluding quasiquoting I think.\n\nWhat things are macros? is `match` a macro?\nDo macros always get a `continuation`? Or are they just allowed to `return`? .....\n....\n...\n..\n.\n\n\n\n## Effects\n\nOPEN ISSUES:\n- the handleSimpleEffects2 has this hack where if you don't pass any arguments, and therefore it only gets 2 args intead of three, it knows the first one should have been a null. This is a hack and I should fix it.\noptions include:\n- effects can't return void, they can return `unit`. which ends up being null.\n  but would that mean that any zero-arg calls have to be passing unit around?\n  that's a pain.\n  or that `k()` turns into `k(())`, which is also awkward.\n  is there a way to, at the place where I produce the `k`, make it a lambda\n  yeah idk.\n\n\n\nQuestion: can I unify \"effect definitions\" with something else?\n- record definition? not really. enum? also no\n- modular implicit? 🤔 there definitely seems to be something similar here.\n- but in this case the thing I'm accessing is Global\n  \u003e ok so the big deal here is ... hm so for unison, the big deal is mutation\n  \u003e because otherwise there's no mutation.\n  \u003e but I'll have single-owner mutation, right?\n  \u003e so async is the big win, I think?\n  \u003e I mean, hmmm\n  \u003e yeah if we have modular implicits too, some of the wins are less for effects.\n  \u003e but yeah, IO. so you can mock it out. especially to do the asynclyness\n\nOk is there another layer of \"dependency injection\" that I want to capture?\nmaybe, but I'll think about it later.\n\n\n\n\n\nso, continuation passing style\nwhat do handlers look like?\nam I still adding something to the \"global\" handler stack?\nseems like that wouldn't really gel.\ncome to think of it, the scheme version would probably\nnot be happy about using green-threads or whatnot.\nSo, I'm thinking the handlers are ... something that's passed along with context?\n\nyeah I should definitely figure out what I want to be doing there.\n\n\n\nOk so I need to capture\num\nwait ok so what do handlers look like? that's where cps might start, right?\n\n```\n\nok but I really need to graph this out.\nand maybe I can just construct a bunch of tests with the current runner?\nand add tracing?\n\n\n\n\nthe type of raise! is\nEffect\u003c'b\u003e =\u003e 'b\n\nthe type of handle!\u003cEffect\u003c'a\u003e\u003e\n(\n    () ={'a, e}\u003e 'b,\n    'a ={f}\u003e 'c, // impure case. where does 'b go here?\n    'b ={g}\u003e 'c  // pure case\n) ={e + f + g}\u003e 'c\n\n\n\n; ('a, () ={Store 'a, 'e}\u003e 'b) ={'e}\u003e 'b\n(define (withInitialValue v f)\n    (handle! f\n        (get () =\u003e k) =\u003e (withInitialValue v '(k v))\n        (set (v) =\u003e k) =\u003e (withInitialValue v '(k v))\n        (pure x) =\u003e x\n        )\n)\n\n; () ={Store int}\u003e int\n(define (example)\n    (raise! (set (+ (raise! (get)) 4)))\n    (raise! (get))\n    )\n\n(define test1 \n    (==\n        5\n        (withInitialValue 1 example)))\n\n\n// if there's a way to do pure\nconst test1 = 5 == withInitialValue_pure(1, example)\n\n// otherwise, everything has a continuation, ugh\nconst test1 = k =\u003e withInitialValue(1, example, [], res =\u003e k(5 == res))\n\nconst example = ()\n\n\n\n\n\n\nconst withInitialState = (v, f, handlers, k) =\u003e {\n\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n// This is the \"impure\" version; with higher handlers\nconst withInitialValue = (v, f, handlers, kont) =\u003e {\n    // ermmm maybe this is hard?\n    let res;\n    f([{\n        get: (k) =\u003e {\n\n        },\n        set: (v, k) =\u003e {\n\n        }\n    }, ...handlers], v =\u003e {\n        kont(v)\n    })\n}\n\nconst addFive = (handlers, k) =\u003e {\n    /* set(get + 5); 12 */\n    // ummm I think we need a new k? hm yeah.\n    // hm what happens to the old k?\n    handlers.get((v, k) =\u003e handlers.set(v + 5, (_, k) =\u003e k(12)))\n}\n\n\n\n\n\n\n\n\n\n// This is the \"pure\" version; no higher handlers\nconst withInitialValue = (v, f) =\u003e {\n    // ermmm maybe this is hard?\n    let res;\n    f([{\n        get: (k) =\u003e {\n\n        },\n        set: (v, k) =\u003e {\n\n        }\n    }], v =\u003e {\n        // this is the \"pure\" maybe?\n    })\n}\n\n\n```\n\n\n\nSo, is it easier to think of transforming to call/cc and then to cps?\nor just directly?\n\n```\nconst maybeThrow = (v, k) =\u003e {\n    if (v) {\n        raiseAbility(someAbility, k)\n    } else {\n        // BUT if we know for sure that doSomethingElse has no effects,\n        // then we can just do `const res = doSomethingElse(4)`\n        doSomethingElse(4, res =\u003e k(res))\n    }\n}\n```\n\nOk yeah that wasn't so bad.\nbut the global stack of abilities won't play nice with javascript's asyncness.\nSo I think we'll want a second parameter that's passed all along,\nthat is the \"handler stack\".\n\n```\nconst maybeThrow = (v, handlerStack, k) =\u003e {\n    if (v) {\n        raiseAbility(handlerStack, someAbility, k)\n    } else {\n        doSomethingElse(4, handlerStack, res =\u003e k(res))\n    }\n}\n```\n\nYeah I feel like that should work?\n\nOk so this means that we're going to need an IR that probably loses some type definition? but we want to do something source-mappy (a DFS traversal visit ID of the node in the TypedTree that we came from, probably).\n\nAlso the handler stack, each handler will be annotated with the effects\nthat it can handle. and it'll get skipped if it can't.\nAnd if none can, then we get to skip right to the builtin implementation potentially.\n\n----\n\nyeah ok let's see if we can get effects working\n\n\u003c\u003c\u003c\u003c\u003e\u003e\u003e\u003e\u003c\u003c\u003c\u003c\u003e\u003e\u003e\u003e\n\n\n## Javascript Runtime Representation\n\nThe goal is to enable easy interop with TS/Flow\n\n- string - string\n- int/float - number\n- array - array\n- list - linked-list via array tuples\n\nenum Something {\n    One(int, text),\n    Two{name: string, age: int},\n    Three,\n}\n\n```ts\ntype Something = \n    | {type: 'One', args: [int, text]}\n    | {type: 'Two', name: string, age: int}\n    | {type: 'Three'}\n```\n\nhrmmmmmm do I do hashes though intead of text names? hrmmmm how much do I care about\nbeing rename-resiliant?\n\nhrmmmm\ninterop vs ... trend setting or something\nlike, not having to refactor if you rename something is neat I guess\nbut is that such a huge issue?\nI mean sure, internally changing names of arguments or whatever, sure.\nthat doesn't need to be externally observable.\n\nbut if we want interop, which I do,\nthen yeah. not sure about that one.\n\nBecause if I want to go whole-hog, then it doesn't make sense\nto try to match a typescript type. just convert to \u0026 from at the border.\n\nok and then the record type is the same dealio.\n\nooooh ok so what if you could specify certain types as \"ffi\" types?\nlike \"nomangle\"?\nAnd so they would have the normal names.\nbut then you wouldn't be able to rename stuff without a migration.\nwhich is reasonable.\n\nOk so we have\n- normal (structural) types\n    - memory representation is an implementation detail. could be an array, or an object, or whatever.\n    - names are not used in computing the hash, nor do they appear in generated code (other than as comments probably).\n    - might do disc unions for typescript's checker's sake, but with `type: hash + idx` instead of a normal name.\n- unique structural types\n    - like normal ones, but with a few bytes of random hash to prevent conflicts I guess\n- ffi (nomangle) types\n    - layout done to mirror typescript/flow discriminated unions, names are used in hashing, and at runtime. so, super easy to get data in and out.\n\nBeyond that, what does FFI look like?\nHow do I declare an external function?\n- I mean probably with a type and a reference, right?\n    - the default is for all js-sourced args to be validated, because untrustworthy. also for js functions to be wrapped in a try/catch(?). You can annotate with `@unchecked` to opt out and just rely on typescript's type checking.\n\n// umm do I enforce npm versions? no that would cause so much sadness.\n```\nimport createElement from \"react\" : \u003cT\u003e(string) =\u003e ReactElement\n```\n\nhm so I need a way to declare foreign types too. that is, foreign opaque types.\n\nherm so when I say ffi, that does let messyness into the program. right? purity is lost.\nwhich we don't love.\nhmm.\nthe other option is to require that all ffi happen through effects.\nwhich gets in the way of react.createElement, thats for sure.\n\nhmmm we could have a `jsExn` effect that is an escape-continuation effect, that gets auto added to your functions if you're using javascript ffi.\nwell so the jsExn is one thing, and you could do a try/catch to \"remove\" it, which is nice.\nbut I feel like I still want a way to indicate that this function is \"unsafe\" in a general way.\nand maybe an effect isn't quite the right way to do it.\n\nbut as long as I'm tracking stuff, might as well track a lack of safety, right?\n\nhrmmm I wonder how escape-continuations would play with my cps stuff.\nfeels like it might be weird.\n\nSo:\n- if we are in a CPS context, escape continuations can use the same mechanisms as other ones\n- if we are not, they can use try/catch\n- buut if I've like resumed a normal k, and then I throw, probably weird things happen?\n  well I guess I would have re-set up the try/catch when I'm dealing with the new k, right? because you have to re-wrap in the handlers ....\n\nOk but like I definitely want to be able to write react components.\nand it would just be that they're unsafe? branded? I can live with that.\n\nok, so jsffi is a special escape-effect that can't be handled.\noperationally speaking, it's as easy as having all ffi functions have the jsffi effect on them.\n\n```\nffi type ReactElement ; // ffi types are Unique as well. but they can have type variables\nimport createElement from \"react\" : \u003cT\u003e(string) ={jsffi, jsexn}\u003e ReactElement\n```\n\nyou can\n`import createElement from \":global\"`\nif you want to probably\n\nor `import \"hello\" as goodbyw from \":global\" : \u003cT\u003e(string) ={jsffi, jsexn}\u003e string`\n\nand if you're importing something from `go`, then it would have the `{goffi}` effect on it.\nsame with scheme.\n\nDoes that make sense? what we don't want to allow things to be switched out?\nOf course, if things are implemented via a modular implicit ....\nhmm ....\nmaybe just `{ffi}` is what it should be called.\nor maybe ffi unifies the different runtimes?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredly%2Fjerd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaredly%2Fjerd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredly%2Fjerd/lists"}