NicoVIII.SimpleOptics 0.3.2

dotnet add package NicoVIII.SimpleOptics --version 0.3.2
NuGet\Install-Package NicoVIII.SimpleOptics -Version 0.3.2
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="NicoVIII.SimpleOptics" Version="0.3.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add NicoVIII.SimpleOptics --version 0.3.2
#r "nuget: NicoVIII.SimpleOptics, 0.3.2"
#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.
// Install NicoVIII.SimpleOptics as a Cake Addin
#addin nuget:?package=NicoVIII.SimpleOptics&version=0.3.2

// Install NicoVIII.SimpleOptics as a Cake Tool
#tool nuget:?package=NicoVIII.SimpleOptics&version=0.3.2

SimpleOptics

Release Last commit License

This is a F# library for simple optics which is highly inspired by Aether. I also had a look at FSharpPlus (but I didn't understand it tbh).

I created this library to learn what optics are and how they work.

Supported optics

  • Lenses
  • Prisms

What are optics

Optics are a way to work conveniently with deeply nested data structures in an immutable way. For that you define elemental optics and then compose them to get optics for more complex structures.

To further explain this, I chose a phone book as an example.

type Address = {
    street: string
    city: string
}
type User = {
    adress: Address
    phoneNumber: string
}

If you need to change the city of a user in plain F# you have to do something like this:

// user: User
let user' = { user with address = { user.address with city = "Berlin" }}

And this doesn't get better for more nested structures. But you can use Optics to do that more conveniently.

At first you define an optic called lens for the address field in User. We'll call it UserOptic.address. Then you can create a lens for city in Address, we'll call it AddressOptic.city. Then you can define a UserOptic for city by composing both lenses. If you have that boilerplate code you can simply set the city with the defined lens. With this library this looks like this:

open SimpleOptics

// user: User
let user' = Optic.set UserOptic.city user "Berlin"

And this does always looks the same for more nested structures. What changes is only the optics boiler plate.

Then there are Prisms. Those are for data which could not be there like an entry in a map. You can combine Lenses and Prisms and will always get a prism back.

How to use

For all examples you have to open the SimpleOptics namespace:

open SimpleOptics

Define optics

Like in Aether you define your optics as a pair of getter and setter. e.g.

// Lens<Address,City>
Lens((fun address -> address.city), (fun address city' -> { address with city = city'}))

// Prism<User,FirstName>
Prism((fun user -> user.firstName), (fun user firstName' -> { user with firstName = Some firstName' }))

Combine optics

To combine optics you can either use the compose function from the Optic module or use the >-> operator:

module UserOptic =
    let city = Optic.compose UserOptic.address AddressOptic.city
    let street = UserOptic.address >-> AddressOptic.city

Use optics

To use optics you can either use the functions from the Optic module or the equivalent operator:

    // Get
    let street = Optic.get UserOptic.street user
    let city = user ^. UserOptic.city

    // Set
    let user1 = Optic.set UserOptic.street "Main Street" user
    let user2 = (UserOptic.city ^= "Berlin") user

    // Map
    let user3 = Optic.map UserOptic.street (fun street -> street.ToUpper()) user
    let user4 = UserOptic.city ^% (fun city -> city.ToUpper()) user

Presets

There are a few generic presets for common structures you can use. To use them you have to open the SimpleOptics.Presets namespace:

open SimpleOptics.Presets

You can then use the defined presets. At the moment these are:

  • ListOptic
  • ArrayOptic
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  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. 
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
0.3.2 566 5/1/2022
0.3.1 368 4/30/2022
0.3.0 367 4/30/2022
0.2.0 418 4/17/2022
0.1.0 452 1/29/2022