Fresnel 0.0.5
dotnet add package Fresnel --version 0.0.5
NuGet\Install-Package Fresnel -Version 0.0.5
<PackageReference Include="Fresnel" Version="0.0.5" />
<PackageVersion Include="Fresnel" Version="0.0.5" />
<PackageReference Include="Fresnel" />
paket add Fresnel --version 0.0.5
#r "nuget: Fresnel, 0.0.5"
#:package Fresnel@0.0.5
#addin nuget:?package=Fresnel&version=0.0.5
#tool nuget:?package=Fresnel&version=0.0.5
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
1by10units 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
modulewith 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 | 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 | 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. |
-
.NETStandard 2.1
- FSharp.Core (>= 6.0.1)
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.