ThorSoft.Optics.Generator
1.0.0-preview-1
dotnet add package ThorSoft.Optics.Generator --version 1.0.0-preview-1
NuGet\Install-Package ThorSoft.Optics.Generator -Version 1.0.0-preview-1
<PackageReference Include="ThorSoft.Optics.Generator" Version="1.0.0-preview-1" />
<PackageVersion Include="ThorSoft.Optics.Generator" Version="1.0.0-preview-1" />
<PackageReference Include="ThorSoft.Optics.Generator" />
paket add ThorSoft.Optics.Generator --version 1.0.0-preview-1
#r "nuget: ThorSoft.Optics.Generator, 1.0.0-preview-1"
#:package ThorSoft.Optics.Generator@1.0.0-preview-1
#addin nuget:?package=ThorSoft.Optics.Generator&version=1.0.0-preview-1&prerelease
#tool nuget:?package=ThorSoft.Optics.Generator&version=1.0.0-preview-1&prerelease
Optics
Optics refers to a class of utilities aimed at simplifying handling of immutable data structures, such as record types.
Lenses
Motivation
A lens is a particular type of optic, that is designed for manipulating nested, immutable data structures. Conceptually, a lens is just a pair of a "getter" and "setter" methods.
public readonly record struct Lens<T,U>(Func<T,U> Get, Func<U,T,T> Set);
Typically, these methods refer to a property on a record type, in which case the accompanying lens can be defined thus:
record A(int Prop1, string Prop2);
var lens = new Lens<A, string>(
static x => x.Prop2,
static (v,x) => x with { Prop2 = v });
wherein the first lambda method returns the value of Prop2
on A
and the second lambda creates a copy of an instance
of A
with the value of Prop2
updated to v
.
What makes lenses particularly useful is their composability. That is given a lens Lens<A,B>
and another lens Lens<B,C>
,
the two lenses can be composed into a lens Lens<A,C>
as such
var lensAB = ...;
var lensBC = ...;
var lensAC = new Lens<A,C>(
x => lensBC.Get(lensAB.Get(x)),
(v,x) => lensAB.Set(lensBC.Set(v, lensAB.Get(x)), x);
The value of this kind of composition becomes apparent when dealing with deeply nested, immutable record types, especially when updating a deeply nested property, such as
record A(B PropB);
record B(C PropC);
record C(string Prop);
A instance = ...;
A updatedA = instance with
{
PropB = instance.PropB with
{
PropC = instance.PropB.PropC with
{
Prop = "my new value"
}
}
};
Given instead the three appropriate lenses, the update can be performed via
var lensAB = ...;
var lensBC = ...;
var lensCS = ...;
A instance = ...;
A updatedA = lensAB.Compose(lensBC).Compose(lensCS).Set("my new value", instance);
Source Generation
While this package does provide the base lens types and extension methods to operate on them, the more interesting part are the accompanying source-generators. While composing lenses is straightforward, creating the component lenses in the first part requires a fair bit of boilerplate code, which can be mitigated by using source generation.
The simplest form of source-generation is provided via the [GenerateLenses]
attribute, applicable
to record types themselves. The source-generator will then generate a nested class inside the attributed type
containing lenses for all directly defined properties on the type, such as
public partial record A(int Prop1, string Prop2);
// Generated
partial record A
{
public static class Lenses
{
public static Lens<A, int> Prop1 { get; } = ...;
public static Lens<A, string> Prop2 { get; } = ...;
}
}
With this generator, the aforementioned example could be rewritten via
var lensAB = A.Lenses.PropB;
var lensBC = B.Lenses.PropC;
var lensCS = C.Lenses.Prop;
A instance = ...;
A updatedA = lensAB.Compose(lensBC).Compose(lensCS).Set("my new value", instance);
Another option for generating deeply nested lenses directly is via the LensExtensions.Focus()
and Lens<>.Focus()
methods.
Both accept a lambda-expression that is intended to determine the path through the nested properties, such as
var lens = Lens<A>.Focus(a => a.PropB.PropC.Prop);
The source generator will intercept the call to Focus()
and return the appropriate Lens<A, string>
.
For more direct updates of single instances, the LensExtensions.Focus()
method can similarly be invoced on any record type instance
A instance = ...;
A updatedA = instance.Focus(a => a.PropB.PropC.Prop).Set("my new value");
Notice that the Set
method did not reqire to specify the instance argument. Instead, the instance Focus()
was invoked
on is propagated through Focus()
and inserted into the Set
call of the underlying lens directly.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.HashCode (>= 6.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.0-preview-1 | 179 | 4/15/2025 |