{"id":19521446,"url":"https://github.com/vknabel/lithia","last_synced_at":"2025-06-10T22:33:10.992Z","repository":{"id":43354959,"uuid":"398394671","full_name":"vknabel/lithia","owner":"vknabel","description":"Early development, experimental functional programming language with an implicit but strong and dynamic type system. ","archived":false,"fork":false,"pushed_at":"2024-12-12T07:48:45.000Z","size":726,"stargazers_count":15,"open_issues_count":17,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-01-10T18:37:49.129Z","etag":null,"topics":["experimental","functional-programming","hacktoberfest","language","lithia","lithialang","programming-language"],"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/vknabel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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}},"created_at":"2021-08-20T20:36:18.000Z","updated_at":"2024-11-01T14:20:43.000Z","dependencies_parsed_at":"2024-01-09T20:43:16.133Z","dependency_job_id":"fc03e3eb-bb35-41f1-a8b4-d49fb6dc20a8","html_url":"https://github.com/vknabel/lithia","commit_stats":null,"previous_names":["vknabel/go-lithia"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vknabel%2Flithia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vknabel%2Flithia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vknabel%2Flithia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vknabel%2Flithia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vknabel","download_url":"https://codeload.github.com/vknabel/lithia/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234588061,"owners_count":18856856,"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":["experimental","functional-programming","hacktoberfest","language","lithia","lithialang","programming-language"],"created_at":"2024-11-11T00:32:16.162Z","updated_at":"2025-01-19T02:07:56.196Z","avatar_url":"https://github.com/vknabel.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cimg src=\"./assets/lithia.png\" width=\"50\"\u003e Lithia Programming Language\n\nLithia is an experimental functional programming language.\nIt tries to combine a dynamic but strong type system with great tooling support in mind into a simple programming language.\nEvery feature needs to contribute to these goals.\n\n## Is Lithia for you?\n\nLithia is not the next programming language to build your next big project with. If you like to learn, play around, [discuss](https://github.com/vknabel/lithia/discussions), design and eventually contribute to a new programming language, Lithia might be a great fit for you.\nI'd be really happy to hear your feedback! The goal is to create an ecosystem with great tooling support for Lithia.\n\nIf you search a production ready language, Lithia is not for you.\n\n### Roadmap\n\nCurrently Lithia is an early proof of concept. Basic language features exist, but the current tooling and standard libraries are far from being feature complete or stable.\n\n- [x] Module imports\n- [x] Testing library\n- [x] Easy installation\n- [x] Prebuilt docker image\n- [x] Prebuilt linux binaries\n- [x] Generated docs for stdlib\n- [x] Improved performance [#20](https://github.com/vknabel/lithia/pull/20)\n- [x] Stack traces [#20](https://github.com/vknabel/lithia/pull/20)\n- [x] Creating a custom language server\n- [x] ... with diagnostics\n- [x] ... with syntax highlighting\n- [x] ... with auto completion\n- [x] ... with symbols\n- [ ] ... with refactorings\n- [ ] ... with formatter\n- [ ] A package manager\n- [ ] A debugger\n- [ ] Custom plugins for external declarations\n- [ ] More static type safety\n\nNot all features end up on the list above. Improving the standard libraries and documentation is an ongoing process.\n\n### Breaking Changes\n\nUntil we reach **0.1.0** every update is considered breaking.\nUpcoming **0.x.Patch**-updates may fix bugs and add features. Existing Lithia source code will not break, but extensions may.\n**0.Minor.0** releases are breaking updates.\n\n#### What is considered a breaking change?\n\n- renaming, moving or removing declarations\n- adding cases to *enum*s, that do not contain `Any`\n- renaming, adding or removing fields to _data_\n- changing the order of fields to _data_\n\n#### What is not considered a breaking change?\n\n- renaming function parameters\n- moving parameters through definitions using currying\n- importing new modules\n\n## Installation\n\nIf you want to give Lithia a try, the easiest way to get started is using Homebrew. By default Lithia is now ready to go.\n\n```bash\n$ brew install vknabel/lithia/lithia\n```\n\nTo get syntax highlighting, use the [Lithia for VS Code extension](https://marketplace.visualstudio.com/items?itemName=vknabel.vscode-lithia).\n\n\u003e Not using Visual Studio Code? Get started with `lithia lsp --help`.\n\n### asdf\n\nIf you are using the [asdf version manager](https://asdf-vm.com), there is a [lithia plugin](https://github.com/vknabel/asdf-lithia)!\n\n```bash\n# Install the plugin\n$ asdf plugin add lithia https://github.com/vknabel/asdf-lithia.git\n\n# Show all installable versions\nasdf list-all lithia\n\n# Install specific version\nasdf install lithia latest\n\n# Set a version globally (on your ~/.tool-versions file)\nasdf global lithia latest\n\n# Now lithia commands are available\nlithia --version\n```\n\n### Docker\n\nTo give Lithia a try, you can use our docker container to start the REPL:\n\n```bash\n$ docker run --rm -it vknabel/lithia\n\u003e print \"Hello World\"\nHello World\n- Hello World\n\u003e\n```\n\nTo deploy your own application built with Lithia, create your own Dockerfile.\n\n```docker\nFROM vknabel/lithia:latest\n\nWORKDIR /app\nENV LITHIA_PACKAGES /app/packages\nCOPY ./packages /app/packages\nENV LITHIA_LOCALS /app/src\nCOPY ./src /app/src\nCOPY ./main.lithia /app/main.lithia\n\nRUN lithia main.lithia\n```\n\n## Which features does Lithia provide?\n\nLithia is built around the belief, that a language is not only defined by its features, but by the features it lacks, how it instead approaches these cases and by its ecosystem. Every feature comes with its own tradeoffs.\nAs you might expect there aren’t a lot language features to cover:\n\n- Data and enum types\n- First-class modules and functions\n- Currying\n- Module imports\n\nOn the other hand we explicitly opted out a pretty long list of features: mutability by default, interfaces, classes, inheritance, type extensions, methods, generics, custom operators, null, instance checks, importing all members of a module, exceptions and tuples.\n\n\u003e Curios? Head over to the generated [Standard Library documentation](./stdlib/README.md).\n\n### Functions\n\nLithia supports currying and lazy evaluation: functions can be called parameter by parameter. If all parameters have been provided and the resulting expression will be used, the functions itself will be called.\n\nTo reflect this behavior, functions are called braceless. Every parameter is separated by a comma.\n\n```\nfunc add { l, r =\u003e l + r }\n\nadd 1, 2 // 3\n\n// with currying\nlet incr = add 1 // { r =\u003e 1 + r }\nincr 2 // 3\n```\n\nAs parameters of a function call are comma separated, you can compose single arguments.\nAll operators bind stronger than parameters and function calls.\n\n```\nwhen True, print \"will be printed\"\n\n// here you can see lazy evaluation in action:\n// print will never be executed.\nwhen False, print \"won't be printed\"\n\n// parens required\nwhen (incr 1) == 2, print \"will be printed\"\n\n// when needed, single parameter calls can be nested\n// fun (a (b (c d)\nfun a b c d\n// fun (a b), (c d)\nfun a b, c d\n```\n\n### Data Types\n\nare structured data with named properties. In most other languages they are called `struct`.\n\n```\ndata Person {\n  name\n  age\n}\n```\n\nAs data types don’t have any methods, you declare global functions that act on your data.\n\n```\nfunc greet { person =\u003e\n  print (strings.append \"Hello \", person.name)\n}\n```\n\n### Enum Types\n\nin Lithia are different than you might know them from other languages.\nOther languages define enums as a list of constant values. A few allow associated values for each named case.\nA Lithia _enum_ is an enumeration of types.\n\nThere is syntactic sugar for value enumerations, to directly declare a case and the associated _enum_ or _data_ type.\n\n```\nenum JuristicPerson {\n  Person\n  data Company {\n    name\n    corporateForm\n  }\n}\n```\n\nInstead of a classic switch-case statement, there is a `type`-expression instead.\nIt requires you to list all types of the enum type. It returns a function which takes a valid enum type.\n\n```\nimport strings\n\nlet nameOf = type JuristicPerson {\n  Person: { person =\u003e person.name },\n  Company: { company =\u003e\n    strings.concat [\n      company.name, \" \", company.corporateForm\n    ]\n  }\n}\n\nnameOf you\n```\n\nIf you are interested in special cases, you can use the `Any` case.\n\n\u003e _**Attention:** If the given value is not valid, your program will crash. If you might have arbitrary values, you can add an `Any` case. As it matches all values, make sure it is always the last value._\n\n### Modules\n\nare defined by the folder structure. Once there is a folder with Lithia files in it, you can import it. No additional configuration overhead required.\n\n```\nimport strings\n\nstrings.join \" \", []\n```\n\nOr alternatively, import members directly. But use this sparingly: it might lead to name collisions.\n\n```\nimport strings {\n  join\n}\n\njoin \" \", []\n```\n\n### Current module\n\nSometimes you might want to pass the whole module as parameter, or to avoid naming collisions.\n\n```\nmodule current\n\nlet map = functor.map\n\ndoSomeStuff current\n```\n\nAs shown, a common use case is to pass the module itself instead of multiple witnesses, if all members are also defined on the module itself.\n\n### Module resolution\n\nTo create your own package of modules, you can create a `Potfile`. Every module defined by the package is a directory next to the Potfile, with `.lithia` files in it.\n\n\u003e To import a module relative to another source file, you always need to create a `Potfile` - otherwise you are only able to import global modules.\n\nThere are a few special cases:\n\n- the `src`-folder represents the root of the package. `src` is not required within the import.\n- if the `src`-folder is missing, the package root is next to the Potfile.\n- by convenience the `cmd`-folder is typically used for individual files rather than a module. You execute them with `$ lithia cmd/\u003cfile\u003e`.\n\n```\n.\n├── Potfile\n├── cmd\n│   ├── main.lithia\n│   └── test.lithia\n└── src\n    └── greet.lithia\n```\n\nIf there aren't any matching local modules, Lithia will search for a package containing source files at the following locations:\n\n- when in REPL, inside the current working directory\n- at `$LITHIA_LOCALS` if set\n- at `$LITHIA_PACKAGES` if set\n- at `$LITHIA_STDLIB` or `/usr/local/opt/lithia/stdlib`\n\n\u003e _**Nice to know:** the special module `prelude` will always be imported implicitly. It contains types like `Bool` or `List`. Beyond that Lithia treats the `prelude` as any other module. And you can even override and update the standard library._\n\nModules and their members can be treated like any other value. Pass them around as parameters.\n\n## Why is this feature missing?\n\n### Why no Methods?\n\nIn theory methods are plain old functions which implicitly receive one additional parameter called `self` or `this`.\n\nIn practice you aren‘t able to compose methods as you can compose free functions.\n\nAnother important aspect of methods is scoping functions with their data. Here the approach is to create more and smaller modules. In practice we create separate files for every class.\n\n```\ndata Account {\n  balance\n}\n\nfunc withdraw { debit, account =\u003e\n  Account account.balance - debit\n}\n```\n\n```\nimport accounts {\n  Account\n}\n\naccounts.withdraw 500, Account 1000\n\nwith Account 150, pipe [\n  accounts.withdraw 100,\n  accounts.withdraw 50,\n]\n```\n\n### Why no Interfaces?\n\nInterfaces allow one implementation per type. The only way to make the implementing types composable is to define more types, requiring more ceremony than plain old functions.\n\nInstead of an interface you create a new _data_ type, assign your implementation and pass it alongside to your argument. The instance containing the implementation is called a witness.\n\n```\ndata Greetable {\n  greeting ofValue\n}\n\nlet shortPersonGreetable = Greetable { person =\u003e\n  strings.append \"Hi \", person.name\n}\n\n\nfunc greet { greetable, object =\u003e\n  print greetable.greeting object\n}\n\ngreet shortPersonGreetable, someone\n\n```\n\nThe benefit of using witnesses instead of plain interface lies in flexibility as one can define multiple implementations of the protocol.\n\n```\nlet longPersonGreetable = Greetable { person =\u003e\n  strings.prepend \"Hello \" person.name\n}\n```\n\nAnd when it comes to composition, this approach really shines! That way we can define our own map or similar functions to transform existing witnesses.\n\n```\nimport strings\n\nfunc map { transform, witness =\u003e\n  Greetable { object =\u003e\n    transform witness.greeting object\n  }\n}\n\nlet uppercased = map strings.uppercased\n\nlet screamed = map strings.append \"!\"\n```\n\nAs seen above, we can rely on existing implementations, compose them and always receive the same data types until we have built complete algorithms!\n\n### Why no class inheritance?\n\nClasses and inheritance have their use cases and benefits, but as Lithia separates data from behavior, inheritance doesn’t serve us anymore.\n\nFor data we have two options:\n\n1.  Copying all members to another _data_. *enum*s must include this new data type.\n2.  Nesting the data. Useful if the data is used outside the default context and is great if you need to combine many different witnesses or data types as you would with multi-inheritance.\n\n```\ndata Base { value }\n\n// copying\ndata CopiedBase {\n  value\n  other\n}\n\n// nesting\ndata NestedBase {\n  base\n  other\n}\n```\n\nWhen regarding witnesses, we have a third option: modules. We create our witnesses and bind each data value directly to the module itself.\n\n```\nmodule strings\n\nlet map = functor.map\nlet flatMap = monad.flatMap\n\ndoSomething strings, \"\"\n```\n\nThe `controls` module explicitly embraces the use of modules as valid witnesses.\nAlongside `Functor` it defines a function `functorFrom` constructing a `Functor` from an enum `FunctorWitness` which allows modules, functors and even monads.\n\n```\nenum FunctorWitness {\n    Functor\n    Module\n    Function\n    Monad\n}\n\nfunc functorFrom { moduleWitness =\u003e\n    with moduleWitness, type FunctorWitness {\n        Functor: { witness =\u003e witness },\n        Module: { module =\u003e\n            Functor module.map\n        },\n        Function: { fmap =\u003e\n            Functor fmap\n        },\n        Monad: { monad =\u003e\n            Functor { f, instance =\u003e monad.pure (monad.flatMap f, instance) }\n        }\n    }\n}\n```\n\nThough the defaults should be used wisely: for example the `Result` type has two different valid implementations of `map`! On the other hand `List` has one valid implementation.\n\nOne additional feature of class inheritance is calling functionality of the super class. In Lithia the approach looks different, but behaves similar:\nWe create a whole new witness, which calls the initial one under the hood.\n\n### Why no dynamic type tests?\n\nMost languages allow type casts and checks. Lithia does only support the type switch expression for enums.\n\nThese checks are unstructured and therefore tempt to be used in the wrong places. Though type checks should be used sparingly. Lithia prefers to move required decisions to the edge of the code. Witnesses should implement decisions for the provided data and desired behavior.\n\nIf there is one type to focus on, the developer and the tooling can understand all cases much easier and faster.\n\n## License\n\nLithia is available under the [MIT](./LICENSE) license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvknabel%2Flithia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvknabel%2Flithia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvknabel%2Flithia/lists"}