xyncro / aether Goto Github PK
View Code? Open in Web Editor NEWOptics for F#
Home Page: https://xyncro.tech/aether
License: MIT License
Optics for F#
Home Page: https://xyncro.tech/aether
License: MIT License
With Optic.set
, the parameters are in the order I would expect. But the ^=
operator has the parameters backwards from Optic.set
, which is not what I would expect. As for Optic.map
and the ^%
operator, I'm not 100% sure which way around I'd expect the parameters to be, but I feel like they should at least be consistent between ^%
and Optic.map
. Let me use an example to show you what I mean:
type Data = { items: int list; code: string }
let items_ : Lens<_,_> = (fun d -> d.items), (fun l d -> { d with items = l })
let data = { items = [1;2;3]; code = "foo" }
data |> Optic.get items_ // This feels "natural"
data ^. items_ // As does this
data |> Optic.set items_ [4;5;6] // As does this
data |> ([4;5;6] ^= items_ ) // But this feels wrong
data |> (items_ ^= [4;5;6]) // This would feel more natural
data |> Optic.map items_ (List.map (*2)) // Whereas this feels "backwards"...
data |> (List.map (*2) ^% items_) // ... and this feels "natural"
It's probably too late to change this for Aether 8.x, but 9.x isn't released yet, right? At least I don't see a NuGet package for 9.0. I'd love to see the ^=
operator feel a bit more natural to use (even if it won't ever be really natural -- it "wants" to be a custom ternary operator, and F# makes it hard to define those).
Looking at the code ( https://github.com/xyncro/aether/blob/master/src/Aether/Aether.fs#L435 ) this appears to be deliberate, but it seems unexpected. What's the rationale?
Is your feature related to a problem? Please describe.
Currently, the tests are still on .NET Core 2.2, which is not supported anymore. .NET Core in general is only supported till the end of this year.
What solution do you propose?
We should consider moving towards .NET 6 support.
Thx for taking this into consideration. Super great library! For me, even the best lenses library for F# out there.
Per the advice given in a recent keynote by the Visual F# team, we should look at providing Aether (and Chiron) as portable libraries that can be consumed on .NET Core as well.
Title says it all :) https://github.com/fsharp/fsfoundation
d8b05fb removed them without providing Obsolete
warning
The page https://xyncro.tech/aether/reference/aether.html says:
The prism form of map access and the lens form of map access have different goals in usage and are mentioned in the Guides.
However, I can't find this (or Map
) mentioned in the guide. Could you elaborate on what you mean?
I have the following type definitions in F#. How can I create a lens from Parent to a given GrandChild (identifier by ID)?
type GrandChild = { Id: int }
type Child = { Id: ChildId; Children: GrandChild list }
type Parent = { Children: Map<ChildId, Child> }
Here is my ugly attempt, which seems like just the kind of thing lenses is used to avoid:
module Parent =
let grandChild grandChildId : Lens<Parent, GrandChild> =
(fun x ->
x.Children
|> Map.toList
|> List.collect (fun (_, c) -> c.GrandChildren)
|> List.find (fun gc -> gc.Id = grandChildId)
),
(fun v x ->
{
x with
Children =
x.Children
|> Map.map (fun _ c ->
{ c with
GrandChildren =
c.GrandChildren
|> List.map (fun gc -> if gc.Id = grandChildId then v else gc) }
)
}
)
I'm not even sure this follows the optic laws.
Part of my challenge here is that there is no correspondence between the child IDs and the grandchild IDs. When I'm at the parent level and want to update a specific GrandChild
, I have to check all Children
and all GrandChildren
.
I assume that there is an easier and more composable way of doing this, but it's escaping me.
If it would simplify anything, you can assume that the grandchild specified by grandChildId
is guaranteed to exist in the hierarchy (hence the use of Lens
instead of Prism
).
A great package, I'm enjoying it thoroughly, with the exception of compile errors.
It quite often happens the type checker substitutes the type alias for the actual type (espcially in the case of an inferred type and especially in the case of an error) and produces errors more difficult to parse than they would be if the lens was stored not as a tuple but rather a record:
expected: Lens<MyRecord, int>
vs
expected: (MyRecord -> int) * (int -> MyRecord -> MyRecord)
Which needless to say gets worse with the complication of either of the types involved.
Would this be something you'd be willing to consider changing or is it too compatibility breaking to be worth thinking about?
Are there any suggested practices for declaring lenses on generic records?
type Record<'a> =
{ Data : 'a }
static member data_ = (fun x -> x.Data), (fun v x -> { x with Data = v })
In this case, data_
is inferred to have type (Record<obj> -> obj) * (obj -> Record<obj> -> Record<obj>)
aka Lens<Record<obj>, obj>
.
Placing it as a module function without any explicit types sort of works:
module Record =
let data_ = (fun x -> x.Data), (fun v x -> { x with Data = v })
but it infers a too general type of (Record<'a> -> 'a) * ('b -> Record<'b> -> Record<'b>)
rather than the lens type of (Record<'a> -> 'a) * ('a -> Record<'a> -> Record<'a>)
.
It's possible to explicitly provide the types:
type Record<'a> =
{ Data : 'a }
static member data_: Lens<Record<'a>,'a> = (fun x -> x.Data), (fun v x -> { x with Data = v })
But obviously with longer signatures inside records this becomes incredibly unwieldy, as per this example from my own project:
let properties_ : Lens<StructureDescription<'a>, (string * Parser<'a -> 'a, unit>) list> = (fun x -> x.properties), (fun v x -> { x with properties = v })
What's the best way of declaring these?
Having support for Aether on the .Net 3.5 platform would be very useful.
I've tested the compilation and that works correctly, but it seems as though Aether does not provide a Nuget package for this version.
Is there any additional documentation on defining lenses?
is it possible to automatically create them for a given record, or do we need to manually define them in long-hand for each field?
Would it be possible to include this in the documentation directly?
FSharpPlus has an over
function to update a value for a lens. I find this very useful, e.g. for appending an element to a list targeted by an optic.
For Aether, it can be defined as simply as this:
let inline over optic update source =
Optic.set optic (update (Optic.get optic source)) source
I have this helper defined in my own prelude, but it would be great to have this in Aether.
Have you considered signing your assemblies with a strong name?
The problem with not signing your assemblies is that you alienate the inclusion of your library in a project that is strongly named. I can get around this temporarily, but I have to actually sign your dlls with my own key. This is a bit of a hassle, especially if I want to release my work as an open source project.
This happens in both Chrome and Edge.
Suppose I have a map like this:
type Employee =
{
JobTitle : string
Salary : int
}
with
static member Salary_ =
(fun x -> x.Salary), (fun v x -> { x with Salary = v })
let employees =
Map.empty
|> Map.add "alice" { JobTitle = "Manager"; Salary = 86 }
|> Map.add "bob" { JobTitle = "Customer Support"; Salary = 76 }
I want to create an optic for updating the salaries. Something like:
let myLens name =
Map.value_ name >-> Employee.Salary_
However, this optic is Lens<Map<string, Employee>, int option>
.
Instead, I would like a Lens<Map<string, Employee>, int>
, by providing a default int
value.
Something like:
// Not real code
let myLens name =
Map.value_ name >-> Employee.Salary_ >-> Lens.defaultValue 0
How should I do this?
Here is what I came up with, but I have a feeling this is already built into the library:
let composeOption (b : Lens<'b, 'c>) (a : Lens<'a, 'b option>) : Lens<'a, 'c option> =
let getA, setA = a
let getB, setB = b
let get = (fun x -> getA x |> Option.map getB)
let set = (fun (v : 'c option) (x : 'a) ->
match getA x with
| Some b ->
match v with
| Some c -> x |> setA (setB c b |> Some)
| None -> x
| None -> x
)
get, set
The website seems to be down or something
Is there any convention for declaring lenses?
Considering the following example:
type Foo =
{ aValue : int
bValue : int }
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Foo =
module Lens =
let aValue:Lens<_,_> = (fun foo -> foo.aValue), (fun aValue x -> { x with aValue = aValue })
let bValue:Lens<_,_> = (fun foo -> foo.bValue), (fun bValue x -> { x with bValue = bValue })
Several questions arise:
Lens
submodule?aValue_
in the Foo
module before, but that seems a bit opaque.)v
and x
? (The names are hidden as they're lambdas anyway the Lens<_,_>
)Having some conventions here might make these more discoverable, at least until they're introduced as a first class language feature.
In the Composition section of the Lenses guide, there's this example:
(* Lens<'a,'b> -> 'a -> 'b *)
let get (g,_) =
fun a -> g a
(* Lens<'a,'b> -> 'b -> 'a -> 'a *)
let set (_,s) =
fun b a -> s b a
Could (and should?) this be simplified to:
(* Lens<'a,'b> -> 'a -> 'b *)
let get (g,_) = g
(* Lens<'a,'b> -> 'b -> 'a -> 'a *)
let set (_,s) = s
The latter code snippet is functionally the same as the former. Is there any reason why the first code snippet is used instead of the second? Perhaps I'm missing the point it's trying to make.
I'd like to open Optic
so I can just do x |> set foo_ "bar"
instead of x |> Optic.set foo_ "bar"
, but Optic
is marked with RequireQualifiedAccessAttribute
.
This may be relevant for other modules as well; haven't checked.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.