Fresnel 0.0.5

dotnet add package Fresnel --version 0.0.5
                    
NuGet\Install-Package Fresnel -Version 0.0.5
                    
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="Fresnel" Version="0.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fresnel" Version="0.0.5" />
                    
Directory.Packages.props
<PackageReference Include="Fresnel" />
                    
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 Fresnel --version 0.0.5
                    
#r "nuget: Fresnel, 0.0.5"
                    
#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 Fresnel@0.0.5
                    
#: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=Fresnel&version=0.0.5
                    
Install as a Cake Addin
#tool nuget:?package=Fresnel&version=0.0.5
                    
Install as a Cake Tool

Fresnel

Fresnel is an optics library for F#

Fresnel aims for a long-term stable API but we haven't reached that point yet!

Why Optics?

Optics make it easier to perform deeply nested get, set and modify operations on immutable data-structures. In a sense, they are the functional programming equivalent of getters and setters.

If you have ever found yourself doing something like this, then optics may help!

Move the rigid body with ID 1 by 10 units in the X direction

{
  world with
    Bodies =
      world.Bodies
      |> Map.change
        1
        (function
        | Some rigidBody ->
          {
            rigidBody with
              RigidBody.Bounds.Center.X = rigidBody.Bounds.Center.X + 10
          }
          |> Some
        | None -> None)
}

With optics:

world
|> World.bodies_ +-> Map.item_ 1 +-> RigidBody.bounds_ +-> Bounds.center_ +-> Point.x_ ^% fun x -> x + 10

Why Not Optics?

In order to work, the example above needs optics definitions for the types we are working with:

[<RequireQualifiedAccess>]
module World =

  let bodies_ : Lens<World, Map<int, RigidBody>> =
    {
      Get = fun x -> x.Bodies
      Set = fun v x -> { x with Bodies = v }
    }

[<RequireQualifiedAccess>]
module Point =

  let x_ : Lens<Point, int> =
    {
      Get = fun p -> p.X
      Set = fun x p -> { p with X = x }
    }

  let y_ : Lens<Point, int> =
    {
      Get = fun p -> p.Y
      Set = fun y p -> { p with Y = y }
    }

[<RequireQualifiedAccess>]
module Bounds =

  let center_ : Lens<Bounds, Point> =
    {
      Get = fun x -> x.Center
      Set = fun v x -> { x with Center = v }
    }

  let radius_ : Lens<Bounds, int> =
    {
      Get = fun x -> x.Radius
      Set = fun v x -> { x with Radius = v }
    }

[<RequireQualifiedAccess>]
module RigidBody =

  let bounds_ : Lens<RigidBody, Bounds> =
    {
      Get = fun x -> x.Bounds
      Set = fun v x -> { x with Bounds = v }
    }

Writing these is a mechanical exercise, and indeed it can be automated using a Myriad plugin. However, this is always more work than nothing; the balance of effort will depend on how much structure manipulation is needed.

Also consider the learning curve of a new set of terms and operators; teams will need to grok ^., ^=, ^% and +->. As with all abstractions, there is a conceptual overhead to optics.

Reach for optics when the standard approach is starting to creak!

Usage

Conventions

  • Optics for a type are grouped into a module with the same name as the type
  • Each optic is named after either it's record field name or DU case name with a _ suffix

For example:

type Circle =
  {
    Radius : int
  }

module Circle =

  let radius_ : Lens<Circle, int> =
    {
      Get = fun x -> x.Radius
      Set = fun v x -> { x with Radius = v }
    }

Design

Operators

The operators were chosen to minimize the number of parentheses required for common usages.

For example:

circle
|> Circle.center_ +-> Point.x_ ^= 99

Here the optic composition has precedence over the |> or set (^=) and so parentheses are not needed.

Alternatives

Aether

Fresnel is directly inspired by Aether. Aether is mature and stable library; if you are happy with Aether, then you should stick with that!

However, Fresnel aims to improve on Aether in a few respects:

  • Optics are polymorphic, allowing data types to be changed through transformations
  • Composition of optics uses only one operator, which requires a bit less thinking
  • Operator symbols are slightly different, requiring fewer parentheses in common usages
  • Parameter order is arguably more intuitive and plays better with |>
  • Fable is supported out-of-the-box
  • Optional (a.k.a "affine traversal") optic is included
  • Traversal optic is included
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 netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen 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 (1)

Showing the top 1 NuGet packages that depend on Fresnel:

Package Downloads
Fresnel.Myriad

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.5 140 8/16/2026
0.0.4 120 7/4/2026
0.0.1 178 7/3/2026