{"id":21028225,"url":"https://github.com/exyi/csharp-io","last_synced_at":"2025-03-13T19:13:53.031Z","repository":{"id":83321617,"uuid":"240359192","full_name":"exyi/csharp-io","owner":"exyi","description":"IO\u003cT\u003e monad for C# based on async/await (PROOF OF CONCEPT)","archived":false,"fork":false,"pushed_at":"2021-04-07T14:06:14.000Z","size":10,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-20T14:50:37.970Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","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/exyi.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-13T20:56:04.000Z","updated_at":"2021-04-07T14:06:17.000Z","dependencies_parsed_at":null,"dependency_job_id":"3471261a-c250-4fa9-bc9e-47b4329adffc","html_url":"https://github.com/exyi/csharp-io","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/exyi%2Fcsharp-io","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exyi%2Fcsharp-io/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exyi%2Fcsharp-io/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exyi%2Fcsharp-io/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/exyi","download_url":"https://codeload.github.com/exyi/csharp-io/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243467024,"owners_count":20295309,"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-11-19T11:54:29.330Z","updated_at":"2025-03-13T19:13:53.004Z","avatar_url":"https://github.com/exyi.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# C# `IO\u003cT\u003e`\n\nTurns out, the async/await syntax is quite powerful and extensible. With few tricks, you can implement custom awaitable and asyncable (or how should I call it?) types with quite different behavior.\n\n### Why?\n\nC# devs nowadays basically use `Task\u003cT\u003e` to mark function which have side effects, which is quite nice (unless all functions are marked like that :D). Returning `Task\u003cT\u003e` however only marks the function as non-pure, but does not actually make them pure. And you can still call the `Task` function from a non-task function and not `await` it, hiding the effect. It's a shame, since it is actually very easy fix.\n\nThe problem is that `Task\u003cT\u003e`-returning function starts performing the side effects in the moment you call it and the `Task\u003cT\u003e` is not referentially transparent. For example, if the function `A(x)` and `B(y, z)` behave purely, `y = A(x); B(y, y)` should be equivalent to `B(A(x), A(x))`. This is clearly not the case for expressions `Task.WhenAll(SendHttpRequest(x), SendHttpRequest(x))` and `y = SendHttpRequest(x); Task.WhenAll(y, y)`. May seem like a small thing, but thinking in terms of results of already running operations is simply more complicated than thinking about the self-contained operations.\n\n### The `IO\u003cT\u003e` type\n\n`IO\u003cT\u003e` is a drop in replacement for `Task\u003cT\u003e` with the difference that it is evaluated at the point it's awaited, not when it's created. This means, you can for example read `n` lines using this one-liner\n\n```csharp\nvar lines = await IO.Sequence(Enumerable.Repeat(n, ReadLine));\n```\n\nIt is also compatible with `Task\u003cT\u003e` in both directions - you can `await` Tasks in IO methods and `IO` may be awaited in `Task` methods. Also, there is a tiny helper for \"do notation\". For example, ReadLine may defined as\n\n\n```csharp\nIO\u003cstring\u003e ReadLine = IO.Do(async () =\u003e await Console.In.ReadLineAsync())\n```\n\nYou can call if from `Task\u003cT\u003e` method:\n\n```csharp\n\npublic async Task Main() {\n    IO\u003cint\u003e readInt = ReadLine.Select(l =\u003e int.Parse);\n    var (a, b) = (await readInt, await readInt); // each await runs the action again\n\n    await Console.Out.WriteLineAsync((a + b).ToString());\n}\n\n```\n\n### Proof of concept\n\nThis thing is a proof concept, I just wanted to see if it's possible to use C#'s custom `AsyncMethodBuilder` for this purpose. Turns out it is and it is quite simple - have a look into the source, it's just ~200 lines of quite simple C# :) Maybe I'll try to explore if it may be used for other monads (like lists, Result or Reader/Writer/State), but that's quite certainly not going to be that intuitive.\n\nThis code should work reliably, if you find issues, feel free to discuss them in the issues. The performance is probably going to be very suboptimal, but this could be solved with a bit more work\u0026code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexyi%2Fcsharp-io","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexyi%2Fcsharp-io","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexyi%2Fcsharp-io/lists"}