{"id":15059480,"url":"https://github.com/codefab-io/elmsharp","last_synced_at":"2025-08-14T04:23:08.965Z","repository":{"id":190610563,"uuid":"682592727","full_name":"CodeFab-io/ElmSharp","owner":"CodeFab-io","description":"An elm-architecture inspired runtime","archived":false,"fork":false,"pushed_at":"2023-09-28T09:57:49.000Z","size":1293,"stargazers_count":5,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T05:38:19.403Z","etag":null,"topics":["csharp","csharp-code","csharp-lib","csharp-library","elm","elm-architecture","elm-lang"],"latest_commit_sha":null,"homepage":"https://codefab.io","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/CodeFab-io.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}},"created_at":"2023-08-24T13:57:30.000Z","updated_at":"2024-07-22T15:44:53.000Z","dependencies_parsed_at":"2023-09-28T11:32:30.286Z","dependency_job_id":null,"html_url":"https://github.com/CodeFab-io/ElmSharp","commit_stats":null,"previous_names":["codefab-io/elmsharp"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CodeFab-io/ElmSharp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeFab-io%2FElmSharp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeFab-io%2FElmSharp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeFab-io%2FElmSharp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeFab-io%2FElmSharp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CodeFab-io","download_url":"https://codeload.github.com/CodeFab-io/ElmSharp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeFab-io%2FElmSharp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267392562,"owners_count":24079919,"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","status":"online","status_checked_at":"2025-07-27T02:00:11.917Z","response_time":82,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["csharp","csharp-code","csharp-lib","csharp-library","elm","elm-architecture","elm-lang"],"created_at":"2024-09-24T22:44:19.552Z","updated_at":"2025-07-27T17:09:57.169Z","avatar_url":"https://github.com/CodeFab-io.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ElmSharp\r\n\r\n👋 Welcome to ElmSharp.\r\n\r\nI came across the [Elm Language](https://elm-lang.org) a few years ago and it has deeply changed the way I approach software. Elm is multiple things: it is a language, it is a package ecosystem and more fundamentally it is an **architecture**.\r\n\r\nI do feel, however, that sometimes it is hard to describe to my fellow colleagues how Elm works and what are the rules of the game. So I decided: what better way to show-and-tell than to \"create Elm\" in csharp.\r\n\r\nWhy was I so moved by the elm architecture? My experience is that it brings a set of healthy constraints that lead you towards objectively better software: many decisions that would be made later in a software project must be made earlier and more consciously. The practices this architecture enforces (immutability, pureness of functions, unidirectional data flow) align very well with the ultimate goal of having **testable and reliable software**.\r\n\r\nThe elm architecture is [very well explained in the elm guide](https://guide.elm-lang.org/architecture/) so I will use the bullet points I find more important:\r\n\r\n* Your application has one single piece of **state**\r\n* This state is **immutable**: the only way to \"move state forward\" is via the `Update` function\r\n* The `Update` function is always triggered by a `Message`\r\n* The only way to have a side-effect in the world is via a `Command`\r\n* `Message`s can come from the `View`, from `Subscriptions` or from the result of a `Command`\r\n\r\nYou can think of a `Message` as a *fact*: \"the user clicked on this button\", \"time has elapsed\", \"this HTTP request has failed\", \"the time is now 12:24:00\".\r\n\r\nYou can think of a `Command` as an *intention* to affect or obtain information from the world: \"can you please run this HTTP request?\", \"can you please tell me the time?\", \"can you please give me a random number between 0 and 100?\", \"I would like a new Guid, please\".\r\n\r\n## 📚 What is the implicit loop of an ElmSharp application?\r\n\r\nJust like the Elm architecture, there in an implicit loop that ElmSharp's runtime handles for you. From a high level standpoint, it goes something like this:\r\n\r\n```mermaid\r\nsequenceDiagram\r\n    ElmSharp-\u003e\u003e+User: Init()\r\n    User-\u003e\u003e-ElmSharp: state=📦, cmd=🙏\r\n    Note over ElmSharp: ElmSharp remembers\u003cbr/\u003estate=📦, cmd=🙏\r\n\r\n    opt Subscription management\r\n        ElmSharp-\u003e\u003e+User: Subscriptions(state: 📦)\r\n        User-\u003e\u003e-ElmSharp: desiredSubs = [⌚, ⌨, ☎]\r\n        Note over ElmSharp: ElmSharp subscribes to\u003cbr/\u003ethe desired subscriptions,\u003cbr/\u003e if any\r\n        ElmSharp--\u003e\u003e+Subs: 👂 Activate subscription(📫)\r\n        Subs--\u003e\u003e-MailBox: ✉ Subscription event\r\n    end\r\n\r\n    loop while cmd != StopApp\r\n        ElmSharp--\u003e\u003e+Cmds: 🏃‍♂️ Run command (📫)\r\n        Cmds-\u003e\u003e-MailBox: ✉ Cmd result\r\n\r\n        ElmSharp-\u003e\u003e+User: View(state: 📦, 📫)\r\n        User--\u003e\u003eMailBox: ✉ User performed action\r\n        User-\u003e\u003e-ElmSharp: 🎨 View representation\r\n\r\n        Note over ElmSharp: ElmSharp renders the view\u003cbr/\u003e(only command line\u003cbr/\u003esupported, for the time being)\r\n\r\n        ElmSharp-\u003e\u003e+MailBox: ⌛ Wait for a message...\r\n        MailBox-\u003e\u003e-ElmSharp: ✉ Message\r\n\r\n        ElmSharp-\u003e\u003e+User: Update(msg: ✉, state: 📦)\r\n        User-\u003e\u003e-ElmSharp: state=🍺, cmd=🌍\r\n        Note over ElmSharp: ElmSharp remembers\u003cbr/\u003estate=🍺, cmd=🌍\r\n\r\n        opt Subscription management\r\n            ElmSharp-\u003e\u003e+User: Subscriptions(state: 🍺)\r\n            User-\u003e\u003e-ElmSharp: desiredSubs = [👮‍♂️, 🚨]\r\n            Note over ElmSharp: ElmSharp compares the delta\u003cbr/\u003ebefore previous subscriptions\u003cbr/\u003eand the new desired ones\u003cbr/\u003e subscribing to new ones and\u003cbr/\u003eunsubscribing from old ones\r\n            ElmSharp--\u003e\u003eSubs: ❌ Unsubscribe\r\n            ElmSharp--\u003e\u003eSubs: 👂 Activate subscription(📫)\r\n        end\r\n    end\r\n\r\n    ElmSharp--\u003e\u003eSubs: ❌ Unsubscribe\r\n\r\n    ElmSharp-\u003e\u003eUser: 🚪 Exit with StopApp status code\r\n```\r\n\r\nExplaining it on a textual level: when an ElmSharp application starts (User runs `await ElmSharp\u003cModel, Message\u003e.Run(...)`), ElmSharp internally sets up a mailbox (leveraging `System.Threading.Channels`). This mailbox has a `Write` mechanism, called the `dispatcher`. ElmSharp then calls the user's `Init()` function, which returns a `Model` instance and a `Command` to be executed. ElmSharp stores this instance of the `Model` as the current state (the words `Model` and state are used somewhat interchangeably in this document).\r\n\r\nNow that ElmSharp has the current state, it once again reaches to the user code via the `Subscriptions` function. This function will return a list of desired subscriptions, so that ElmSharp can perform the wiring up of these subscriptions. Wiring up a subscription consists of creating a new `CancellationTokenSource` per subscription and invoking the internal `Subscribe` method on the subscription instance, which receives a `CancellationToken` and the `dispatcher`. The subscriptions themselves are executed as non awaited `Task`s so they don't block ElmSharp's main loop. A subscription communicates with the user via the aforementioned mailbox (leveraging the `dispatcher`).\r\n\r\nElmSharp now enters a `while(true)` loop. Inside it, ElmSharp first looks at the `Command` returned by the user. If the command is the `StopAppCommand` we break out of the loop, unsubscribe for any existing subscriptions and return from the `await .Run(...)` method with the same status code as requested on the StopAppCommand. Otherwise, this is a normal `Command` and ElmSharp will invoke its `Run` method, much like what happened with the subscriptions, above. One big difference is that while a Subscription has access to the `dispatcher` and therefore can create multiple messages, a `Command` doesn't: a `Command` can only return the appropriate `Message` indicating its success or failures (semantics vary per command, ElmSharp doesn't distinguish between success or failure). This returned messaged is then dispatched to the mailbox. Commands also execute as non awaited `Task`, which means they don't block ElmSharp's loop.\r\n\r\nOne further step is the `View` function, where ElmSharp once again reaches to user code and provides the current state, as well as the `dispatcher`. This allows the user to build user interfaces which can themselves dispatch messages into ElmSharp's mailbox. The `View` aspect of ElmSharp is still an area under development, and for now only Console Applications are supported. Due to their nature, console applications do not leverage the `dispatcher` of the `View` function.\r\n\r\nFinally, ElmSharp will await for a message in the mailbox, which signals that something of interest has happened. Remember, a message can come from the `View`, or the result of a `Command` or from a `Subscription`. Once ElmSharp receives this message, it will invoke the user's `Update` function. This function receives this `Message` as well as the current `Model`. The job of this function, much like the `Init()` function is to return a new instance of the `Model` as well as any `Command` that should be executed.\r\n\r\nAs the last step, since the `Model` has potentially been modified by the `Update()` function, ElmSharp will once again invoke the `Subscriptions()` function, applying the logic described above . And the loop repeats.\r\n\r\n## 🤔 What does it mean creating an ElmSharp application?\r\n\r\nThere are two worlds in an ElmSharp application: the runtime world and the user world. You are the user, the creator of awesome ElmSharp applications.\r\n\r\nAs a user, your job consists of:\r\n\r\n* Creating a `Model`. For instance, if your application keeps track of \"todo\" items, your `Model` could be `public record Model(ImmutableList\u003cTodo\u003e Todos);`\r\n\r\n* Declaring the list of `Message`s that your application understands. For the simple todo tracking application, we can imagine a few messages: `TodoCreated`, `TodoMarkedInProgress`, `TodoMarkedCompleted` and `TodoDeleted`\r\n\r\n* Implementing an `Init` function which tells ElmSharp about the initial state when your application starts\r\n\r\n* Implementing the `Subscriptions` function, which is the way that you let ElmSharp know \"given my model is currently X, I want to subscribe to these interesting events about the world (or none)\"\r\n\r\n* Implementing the `View` function which is how you will represent your `Model` to the user. The whole view can only be dependent on data present in the `Model`\r\n\r\n* Implementing the `Update` function, which is how you make your model progress, in response to incoming `Message`s\r\n\r\n\u003e ℹ More advanced use cases will require you to implement your specific `Command` and `Subscription` but this is something we will cover in a later topic.\r\n\r\n## 🧠 Some ground rules\r\n\r\nAs with any architecture, we can only reap benefits if we follow the ground rules associated with it. For both Elm and ElmSharp's architecture there is one fundamental ground rule: **immutability**. In Elm this is trivial, because the language itself doesn't have any mutability \"escape hatches\". C# however, has plenty of those 😅. This means that just like TDD or SOLID enforce certain practices to reap any benefits, ElmSharp's architecture requires the `Model` to be fully immutable. Without this rule, there won't just be dragons, there will be d̵r̴a̵g̴o̸n̸s̶ ̷w̶h̸a̷t̴ ̶i̸s̴ ̴h̴a̵p̶p̶e̴n̷i̴n̴g̸,̴ ̷o̶h̴ ̷n̴o̴o̶o̴. You have been warned 🐲😁\r\n\r\n## ❓ How does the code look like?\r\n\r\nAssuming the following `GlobalUsings.cs` in your project `UserCode`:\r\n\r\n```csharp\r\n// 📃 GlobalUsings.cs\r\nglobal using Cmd = ElmSharp.ElmSharp\u003cUserCode.Model, UserCode.Message\u003e.Command;\r\nglobal using Sub = ElmSharp.ElmSharp\u003cUserCode.Model, UserCode.Message\u003e.Subscription;\r\n```\r\n\r\nThese are the signatures of the important functions:\r\n\r\n```csharp\r\n// MODEL (immutable; holds you application state)\r\npublic record Model(\r\n    ImmutableDictionary\u003cGuid, Todo\u003e Todos);\r\n\r\n// MESSAGE (immutable; communicates facts that happened)\r\npublic abstract record Message \r\n{\r\n    public sealed record TodoCreated(string TodoDescription) : Message { }\r\n\r\n    public sealed record TodoMarkedInProgress(Guid TodoId) : Message { }\r\n\r\n    public sealed record TodoMarkedCompleted(Guid TodoId) : Message { }\r\n\r\n    public sealed record TodoDeleted(Guid TodoId) : Message { }\r\n    // ...;\r\n}\r\n\r\n// INIT (pure function; provides the initial state of the app and commands to execute)\r\npublic static (Model, Cmd) Init() =\u003e /*...*/;\r\n\r\n// SUBSCRIPTIONS (pure function; allows you to obtain messages from non-user inputs)\r\npublic static ImmutableDictionary\u003cstring, Sub\u003e Subscriptions(Model model) =\u003e /*...*/;\r\n\r\n// VIEW (pure function; given a current model, return a visualization intention)\r\npublic static object View(Model model, Action\u003cMessage\u003e dispatch) =\u003e /*...*/;\r\n\r\n// UPDATE (pure function; the only way to move your state forward; gets triggered by incoming messages)\r\npublic static (Model, Cmd) Update(Message message, Model model) =\u003e message switch\r\n{\r\n    Message.TodoCreated info =\u003e\r\n        model.OnTodoCreated(info),\r\n\r\n    Message.TodoMarkedInProgress info =\u003e\r\n        model.OnTodoMarkedInProgress(todoId: info.TodoId),\r\n\r\n    Message.TodoMarkedCompleted info =\u003e\r\n        model.OnTodoMarkedCompleted(todoId: info.TodoId),\r\n\r\n    Message.TodoDeleted info =\u003e\r\n        model.OnTodoDeleted(todoId: info.TodoId),\r\n    //...\r\n}\r\n// ...\r\n\r\n// Using extension methods (pure functions) on Model, to allow for a cleaner looking Update function\r\ninternal static (Model, Cmd) OnTodoDeleted(\r\n    this Model model, \r\n    Guid todoId) =\u003e \r\n        (model with { Todos = model.Todos.Remove(todoId) }, Cmd.None);\r\n// ...\r\n```\r\n\r\nFor a simple project, you can expect your source tree to look something like this:\r\n\r\n```\r\nGlobalUsings.cs\r\nInit.cs\r\nMessage.cs\r\nModel.cs\r\nProgram.cs\r\nSubscriptions.cs\r\nUpdate.cs\r\nView.cs\r\n```\r\n\r\n---\r\n\r\n## 📚 Learning \u0026 Examples\r\n\r\n[🍀 Guessing Game](https://github.com/CodeFab-io/ElmSharp/blob/main/examples/GuessingGame/)\r\n\r\n---\r\n\r\nI hope you enjoy using ElmSharp, as much as I enjoyed creating it.\r\n\r\nMade by someone who ♥ building things.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodefab-io%2Felmsharp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodefab-io%2Felmsharp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodefab-io%2Felmsharp/lists"}