{"id":19115415,"url":"https://github.com/polytypic/netoptics","last_synced_at":"2025-04-30T23:06:13.882Z","repository":{"id":78838705,"uuid":"256970563","full_name":"polytypic/NetOptics","owner":"polytypic","description":"Optics for the impoverished","archived":false,"fork":false,"pushed_at":"2020-05-24T22:07:20.000Z","size":129,"stargazers_count":5,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-30T23:05:27.452Z","etag":null,"topics":["dotnet","fsharp","functional-programming","isomorphisms","lenses","optics","prisms","traversals"],"latest_commit_sha":null,"homepage":"","language":"F#","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/polytypic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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,"zenodo":null}},"created_at":"2020-04-19T10:23:48.000Z","updated_at":"2023-05-09T18:16:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"a89735e3-4232-4f90-8652-b4a17424ad4e","html_url":"https://github.com/polytypic/NetOptics","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/polytypic%2FNetOptics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2FNetOptics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2FNetOptics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polytypic%2FNetOptics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/polytypic","download_url":"https://codeload.github.com/polytypic/NetOptics/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251795407,"owners_count":21645022,"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":["dotnet","fsharp","functional-programming","isomorphisms","lenses","optics","prisms","traversals"],"created_at":"2024-11-09T04:46:19.751Z","updated_at":"2025-04-30T23:06:13.875Z","avatar_url":"https://github.com/polytypic.png","language":"F#","readme":"# NetOptics\n\nThis is a prototype optics library for .NET in F# based on an approach that does\nnot require (an encoding of) higher-kinded polymorphism or ad-hoc polymorphism\nand can also be implemented and used in C# (in which the initial work was done)\nand other languages with similar features (first-class functions, rank-1\npolymorphism, mutable objects, ability to create a default value of any type).\n\n**NOTE:** I have no plans to productize this as a proper library.\n\n## Overview\n\nPlease study the [NetOptics.Optic.fsi](NetOptics.Optic.fsi) signature.\n\n**Features:**\n\n- Simple polymorphic types\n- Optics are just functions and can be composed with the standard function\n  composition operator `\u003c\u003c`\n- Isomorphisms (or adapters) are invertible\n- Lenses\n- Prisms\n- Folds\n- Traversals\n- Ability to remove focuses of an optic\n\n**Not supported:**\n\n- Applicative traverse over focuses of an optic\n\n### On type safety\n\nThe approach in this prototype is mostly type-safe. However, the number of\nfocuses or the class of an optic is not tracked in the types.\n\nFor example, the `view` operation requires the given optic to have at least one\nfocus on the target data structure. If there are no focuses, `view` will raise\nan exception. Likewise, the `invertI` combinator and the `review` operation\nrequire an (explicitly constructed) isomorphism or composition thereof. If the\ngiven optic is not an explicitly constructed isomorphism, `invertI` and `review`\nwill raise an exception.\n\n**NOTE:** I have not explored a design that would use phantom types to track all\nthe details required to avoid exceptions in the course of prototyping this\nlibrary, but based on previous experience I'm confident that it can be done.\n\n### On performance\n\nThis approach admits an implementation that does not perform large numbers of\nallocations when an optic is used. This means that performance should be good.\nHowever, limited inlining in .NET and F# will put a cap on performance.\n\n## On the implementation\n\nHere I will describe the [internal implementation](NetOptics.Optic.fs) briefly\nfor the interest of people who might want to implement this approach in their\nlanguage. This description is just a hint. Read the [code](NetOptics.Optic.fs)\nfor full details.\n\nThe [signature](NetOptics.Optic.fsi) of this library reveals that the optic type\n`t\u003c'S, 'F, 'G, 'T\u003e` is a function type, but does not expose the implementation\nof the `Pipe\u003c'S, 'T\u003e` type:\n\n```fsharp\ntype Pipe\u003c'S, 'T\u003e\n\ntype t\u003c'S, 'F, 'G, 'T\u003e = Pipe\u003c'F, 'G\u003e -\u003e Pipe\u003c'S, 'T\u003e\n```\n\nHere is how the `Pipe\u003c'S, 'T\u003e` type is defined internally:\n\n```fsharp\ntype [\u003cStruct\u003e] Context =\n  val mutable Hit: bool\n  val mutable Over: bool\n  val mutable Index: int\n  val mutable View: obj\n\ntype D\u003c'S, 'T\u003e = delegate of byref\u003cContext\u003e * 'S -\u003e 'T\n\ntype Pipe\u003c'S, 'T\u003e = P of D\u003c'S, 'T\u003e * inverted: bool\n```\n\nNow we can see that an optic is a function that takes a mapping function with a\nmutable context to a another mapping function with a mutable context.\n\n**NOTE:** The `D\u003c'S, 'T\u003e` type is an explicit `delegate` type and is required in\n.NET to use `byref\u003c_\u003e` arguments. The use of `byref\u003c_\u003e` is an optimization to\navoid a heap allocation for the context. In other languages (and also in .NET)\nyou could also just use a function type taking a mutable context object (with\nthe cost of an extra allocation in .NET).\n\nThe `inverted` flag of the `Pipe\u003c'S, 'T\u003e` type is used to track whether an\nisomorphism is being inverted by use of `review`. Isomorphisms propagate the\nflag during construction, but other optics set it to `false`. This allows\n`review` to check that inversion works and allows an isomorphism to avoid\nperforming potentially illegal operations on invalid data. During an inverse\noperation the input data given to the pipe is undefined (an\n`Unchecked.defaultof\u003c_\u003e`).\n\nThe mutable context is used by operations on optics, like `view` and `over`, to\ncommunicate with the optic being operated upon and to determine the result of\nthe operation. The context is created just before the optic is used and does not\nescape the use. This means that outside of the internal mechanism one cannot\nobserve the use of mutation and operations can be referentially transparent. In\nfact, the use of a mutable context is merely an optimization. If we would\ninstead define `Pipe\u003c'S, 'T\u003e` essentially as\n\n```fsharp\ntype Pipe\u003c'S, 'T\u003e = Context * 'S -\u003e Context * 'T\n```\n\nwe could implement optics with same behaviour without using any mutation even in\na pure language like Haskell.\n[Purity is an extensional property](https://eiriktsarpalis.wordpress.com/2017/03/06/f-and-purity/#comment-136).\n\nThe fields of the context are used roughly as follows:\n\n- `Over` specifies whether or not an `over` or a `view` style operation is being\n  performed. An `over` style operation must construct a return value, while a\n  `view` style operation can avoid the construction and return anything that\n  fits the type (like an `Unchecked.defaultof\u003c_\u003e`).\n\n- `Hit` is used differently depending on `Over`:\n\n  - A `view` style operation is stopped after `Hit` is set to `true`. No value\n    can be returned when a `view` style operation ends with `Hit = false`.\n\n  - During an `over` style operation, focuses that are `Hit` are removed. No\n    value can be returned when an `over` style operation ends with `Hit = true`.\n\n- `View` is an untyped placeholder for the result of simple `view` operations\n  and is an optimization to avoid an allocation.\n\n- `Index` is used by some of the operations and is an optimization to avoid an\n  allocation.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolytypic%2Fnetoptics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolytypic%2Fnetoptics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolytypic%2Fnetoptics/lists"}