ThorSoft.Optics.Generator 1.0.0-preview-1

This is a prerelease version of ThorSoft.Optics.Generator.
dotnet add package ThorSoft.Optics.Generator --version 1.0.0-preview-1
                    
NuGet\Install-Package ThorSoft.Optics.Generator -Version 1.0.0-preview-1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ThorSoft.Optics.Generator" Version="1.0.0-preview-1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ThorSoft.Optics.Generator" Version="1.0.0-preview-1" />
                    
Directory.Packages.props
<PackageReference Include="ThorSoft.Optics.Generator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ThorSoft.Optics.Generator --version 1.0.0-preview-1
                    
#r "nuget: ThorSoft.Optics.Generator, 1.0.0-preview-1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ThorSoft.Optics.Generator@1.0.0-preview-1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ThorSoft.Optics.Generator&version=1.0.0-preview-1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ThorSoft.Optics.Generator&version=1.0.0-preview-1&prerelease
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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