{"id":22531371,"url":"https://github.com/owaismohsin001/junu-spaghetti","last_synced_at":"2025-03-28T05:18:53.522Z","repository":{"id":193433106,"uuid":"365355104","full_name":"owaismohsin001/junu-spaghetti","owner":"owaismohsin001","description":"Junu Spaghetti, a language that types spaghetti code","archived":false,"fork":false,"pushed_at":"2022-01-12T17:31:43.000Z","size":1757,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-02T06:13:26.633Z","etag":null,"topics":["ad-hoc-polymorphism","bidirectional-typechecking","parametric-polymorphism","programming-language","spaghetti-code","static-typing","structural-typing","union-types"],"latest_commit_sha":null,"homepage":"","language":"Haskell","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/owaismohsin001.png","metadata":{"files":{"readme":"ReadMe.md","changelog":"CHANGELOG.md","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}},"created_at":"2021-05-07T21:17:39.000Z","updated_at":"2023-12-03T21:29:35.000Z","dependencies_parsed_at":"2023-09-08T06:52:03.230Z","dependency_job_id":null,"html_url":"https://github.com/owaismohsin001/junu-spaghetti","commit_stats":null,"previous_names":["ameerwasi001/junu-spaghetti","zaidharoon001/junu-spaghetti","owaismohsin001/junu-spaghetti"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owaismohsin001%2Fjunu-spaghetti","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owaismohsin001%2Fjunu-spaghetti/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owaismohsin001%2Fjunu-spaghetti/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owaismohsin001%2Fjunu-spaghetti/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/owaismohsin001","download_url":"https://codeload.github.com/owaismohsin001/junu-spaghetti/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245972984,"owners_count":20702757,"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":["ad-hoc-polymorphism","bidirectional-typechecking","parametric-polymorphism","programming-language","spaghetti-code","static-typing","structural-typing","union-types"],"created_at":"2024-12-07T08:07:14.790Z","updated_at":"2025-03-28T05:18:53.497Z","avatar_url":"https://github.com/owaismohsin001.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Junu Spaghetti\nJunu spaghetti is a language aimed at typing spaghetti code, especially kind people write in JS, Python, and other dynamically typed programming languages because you often don't want to write robust highly scalable code but a one-off script but would still like a nice type system to avoid debugging those pesky runtime type errors. It attempts this by using union types, polymorphism, and structural typing. What follows is a workable overview of Junu Spaghetti.\n\n## Base types, and variables\nThe language has all the expected base types of a high-level programming language, such as\n```\nlet b = true\nlet n = 1\nlet s = \"Hello\"\n```\nYou may have noticed the lack of type annotations, this is because Junu generally has local inference and therefore can infer the type of most local declarations. These are typed exactly the way you would expect them to be typed. Here are the results of typing\n```\nb: Bool\nn: Num\ns: String\n```\n## Functions\nFunctions in Junu are first-class(and closures are also supported) meaning they can be passed to other functions, assigned to variables returned from functions. Following is an example of a function in Junu.\n```\naddNums(a: Num, b: Num) =\u003e Num {\n    return a+b\n}\n```\nand it's typed as \n```\naddNums: (Num, Num) -\u003e Num\n```\nNow, Junu can infer the return type of functions that don't recurse, either by themselves or through mutual recursion, which this function does not so let's omit the return type and see if it works.\n```\nlet addNums = (a: Num, b: Num) =\u003e {\n    return a+b\n}\n```\nAs you might have noticed, we are using anonymous class functions here which is a common way of asking for type inference on return types in Junu. You may also consider omitting the curly braces if your function consists only of a single expression like the function discussed above.\n```\nlet addNums = (a: Num, b: Num) =\u003e a+b\n``` \nYou may also want to forward declare functions if you plan to call them, in other functions before defining them. This is usually only used when mutual recursion comes into play, but our example here is not that.\n```\nf(Num) =\u003e Num\nlet g = (i: Num) =\u003e f(i)\nf = (i: Num) =\u003e i+1\n```\nThis will be typed as\n```\nf: (Num) -\u003e Num\ng: (Num) -\u003e Num\n```\n\n## Structural Types\nThese types are compared exclusively by their structures since they have no name. Following is an example of such a type\n```\nlet obj = {a: 1, b: 3, c: \"Hello\"}\n```\nThis will be typed as\n```\nobj: {a: Num, b: Num, c: String}\n```\nand any object even if aliased can be assigned to this variable\n\n## Type Unions\nType Unions are a form of subtyping which prefers composition over inheritance and is the heart and soul of this programming language. Unions here can be explicitly annotated, like this\n```\nlet x: Num | String = 1\nx = \"Hello\"\n```\nor inferred like such\n```\nlet x = 1\nx = \"Hello\"\n```\nboth of which will be typed as \n```\nx: (Num | String)\n```\nYou must note that a union can only be passed to a function that is either polymorphic over them or expects the exact the unions that are sent to it, this is done to preserve the soundness of the type system. \n\n## Narrowing Unions\nGiven a type, `Num | String`, there are ways to operate on it that can give you a singular type, and the main approach of doing that is using if statements, like such\n```\nlet f = (a: Num | String) =\u003e {\n    if a is Num {\n        println(a+2)\n    } else {\n        println(\"Hello \" + a)\n    }\n    return {}\n}\n```\nThis, when invoked with like this `f(\"World\")`, prints `Hello World` but when invoked like this `f(3)` prints `5`. As you might have noticed other than narrowing the type of `a` to `Num` in the first block, it narrowed down the type of the `else` block to `String` as well. This functions just as well with early returns too, for instance, the function above can be re-written as\n```\nlet f = (a: Num | String) =\u003e {\n    if a is Num {\n        println(a+2)\n        return {}\n    }\n    println(\"Hello \" + a)\n    return {}\n}\n```\nand be typed just as well. There also exist other operators such as the infix operator `notis` which is the opposite of `is`, and the unary `not` operator which negates an expression.\n\n## Named Types\nNamed types allow for generic recursive structures, they are much like a pattern in Haskell but they have a type. Here's the type they have. Here's an example of a named type, with the name `Pair`\n```\nnewtype Pair(a{}, b{}) = (a, b)\n```\nwhich can be instantiated as\n```\nlet pair = Pair(1, \"Hello\")\n```\nwhich will be typed as\n```\npair: Pair(Num, String)\n```\nand they can obviously be passed as generics and thus be nested\n```\nlet pair = Pair(1, Pair({}, \"Hello\"))\n```\nwhile being typed as\n```\npair: Pair(Num, Pair({}, String))\n```\nFor a recursive type, when you want to generalize over its length, you can simply specify all the type variables in order like `len` function below does.\n```\nlen(xs: Nil | Ls(n{})) =\u003e Num {\n    if xs is Nil { return 0 }\n    return len(xs.res)+1\n}\n\nlet xs = Ls(\"d\", Ls(\"ddjn\", Ls(\"ddjn\", {})))\nprintln(len(xs))\n```\nHere `xs` would be typed as\n```\nxs: Ls(String, Ls(String, ({} | Ls(String))))\n```\nbut will be generalized to `Ls(String)` when sending to a function, this generalization persists if it's returned from a function that generalizes it. When unions are put inside named types like\n```\nnewtype Tup(a{}, b{}) = (a, b)\nlet t = Tup(1, \"Hello\")\nt = Tup(1, 1)\n```\ntheir inner variables are turned into unions if that's a possiblility\n```\nTup = Tup(a{}, b{}) = fromList [(a,a{}),(b,b{})]\nt: Tup(Num, (Num | String))\n```\n\n## Type Aliases\nType aliasing is used to build recursive and mutually recursive structures, here's an example\n```\ntype Person = {name: String, age: Num, male: Bool, pet: Pet, parent: Nil | Person}\n```\nAnd objects of this kind can be interpreted as `Person` type.\n```\nlet st = {name: \"John\", age: 10, male: true, pet: {species: \"Cat\", name: \"Tom\", color: \"White\"}, parent: {}}\n```\nAlthough, its immediate inferred type will be\n```\nst: {age: Num, male: Bool, name: String, parent: {}, pet: {color: String, name: String, species: String}}\n```\nbut it can be sent to functions that accept `Person` type, like such\n```\nlet st = {name: \"John\", age: 10, male: true, pet: {species: \"Cat\", name: \"Tom\", color: \"White\"}, parent: {}}\nlet mutatePerson = (s: Person, name: String) =\u003e {\n    s.name = name\n    return {}\n}\nmutatePerson(s, \"Josh\")\n```\nwhich is accepted to be sound. Even if we alter the type like this\n```\nst.parent = duplicate(st)\n```\nbefore sending it, as long as it's coercible to `Person`, it remains acceptable.\n\n## Parametric polymorphism and constraints\nHere we will show how generics and parametric polymorphism works in this language. Following is an example of an `identity` function that can take any type and return that type.\n```\nlet identity = (a: x{}) =\u003e a\n```\nHere, this is typed simply as\n```\nidentity: (x{}) -\u003e x{}\n```\nwhere `x{}` can literally be any type, and a value of the same type will be returned because it's inferred as `(x{}) -\u003e x{}`. The empty curly braces denote that there are no constraints on what this type can be. Here are a few examples of its invocations.\n```\nlet n = identity(1)\nlet s = identity(\"Hello\")\n```\nSince functions are first-class, they can be passed to `identity` as well, like this \n```\nlet f = identity((p: Num) =\u003e p+1)\n```\nAll of which will be typed as \n```\nf: (Num) -\u003e Num\nn: Num\ns: String\n```\nPolymorphic functions can also constraint the values they are be willing to take, for example, we can define a function that can take `x`, if and only if `x` has a field called `name` of type `String`. What follows is an example,\n```\nlet getName = (obj: x{.name: String}) =\u003e obj.name\n```\nHere are examples of what its invocations can be\n```\nlet resA = getName({name: \"Ameer\"})\nlet resB = getName({name: \"John\", age: 20})\n```\nIt is important to note that this is not structural subtyping, since this function\n```\nlet idName = (obj: x{.name: String}) =\u003e obj\n```\nwhen invoked like such `let resB = idName({name: \"John\", age: 20})` would be typed as `{age: Num, name: String}`.\nNote: Row polymorphism along is planned although it's not yet implemented.\n\n## Arrays\nArrays in Junu are encoded entirely in its type system with the only aid coming from the language at runtime. This demonstrates the power of the system's union types quite effectively. You may initialize an array by either using the `Array` constructor or by using `insert` on an empty object. Here's an example of how type-safe heterogeneous arrays can be used in this language.\n```\nlet insert = (arr: Nil | Array(m{}), a: n{}) =\u003e concat(arr, Array(a))\nlet res = insert(insert(insert({}, 1), true), \"mooo\")\nres = concat(res, Array({p: true}))\n```\nAn empty array can is to be represented as type `Nil`. This array can be indexed with the `index` function and it should return a type that's the union of all types present in the array. The union can then be narrowed with if statements. Following is an example of how this can be used \n```\nif res notis Nil {\n    let x = index(res, 3)\n    if x is String {\n        println(x + \"hoo\")\n    } else if x is Num {\n        println(x+1)\n    } else {\n        println(x)\n    }\n}\n```\n\n## Open functions\nOpen functions are the ad-hoc polymorphism of Junu. Open functions have many instantiations but those instantiations have to conform to the interface set by open functions, and can only be invoked with types that they are already instantiated with. Here's an example\n\n```\nopen setName(t{}, String, a{.pet: b{.name: t{}}}) =\u003e b{.name: t{}} for a{.pet: b{.name: t{}}}\n\nimpl setName(s: String, area: String, person: Person) =\u003e {\n    person.name = s\n    return person.pet\n} for Person\n\nimpl setName(i: Num, area: String, intkeeper: {pet: {name: Num}}) =\u003e {\n    intkeeper.pet.name = i\n    return intkeeper.pet\n} for {pet: {name: Num}}\n```\n\nNow, this open function can only be called with either `{pet: {name: Num}}` or `Person`. Now let's call them with available\n```\nsetName(\"Junu\", \"Collatz\", {pet: {name: 1}})\n```\nIt's also important to know that +, -, and other operators are actually overloadable because they are implemented as open functions but since user-defined operators are not yet part of the language, it only makes sense that these are to be implemented by using names, and that's why logical names are assigned to these. For instance `add`, `sub`, `neq`, and `eq` are names for `+`, `-`, `!=`, and `==` respectively. Here's an example of how you can implement add for your own type that represents a 2d point.\n```\ntype Point = {x: Num, y: Num}\n\nimpl add(a: Point, b: Point) =\u003e {\n    return {x: a.x + b.x, y: a.y + b.y}\n} for Point\n\nprintln({x: 1, y: 3} + {x: 2, y: 7})\n```\nand this, as you'd expect would output\n```\n{x: 3, y: 10, }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowaismohsin001%2Fjunu-spaghetti","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fowaismohsin001%2Fjunu-spaghetti","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowaismohsin001%2Fjunu-spaghetti/lists"}