polytypic / NetOptics

Optics for the impoverished
MIT License
5 stars 0 forks source link

F# C# lens library #1

Open wallymathieu opened 4 years ago

wallymathieu commented 4 years ago

Hi! I've found the need for for a lens library usable from both c# and f#.

Fshapx extras has one implementation I've duplicated for c# usable lenses in with but with a different focus.

Could we cooperate on some approach? Would it make sense to write an RFC for f#+ to include c# compatible lenses?

polytypic commented 4 years ago

Hi!

Like is mentioned in the README, I've written a C# version — for reasons we are using C# 7.3 and .NET Framework 4.7.2 — of the same technique, but it is part of a proprietary code base. I notice now that the wording in the README might be slightly misleading when it says "can also be implemented and used in C#". What I mean is that one can implement the same approach in C# and have a usable optics implementation in C#. The C# version is written with only C# usage in mind, so it uses a delegate type for optics:

delegate OpticPipe<S> Optic<S, A>(OpticPipe<A> pipe)

Ideally that should be just a type alias for a Func, but there are no parameterized type aliases in C# 7.3 and using a Func one would have to write the type open:

Func<OpticPipe<A>, OpticPipe<S>> // Too verbose.
Func<O<A>, O<S>>                 // If OpticPipe is renamed to O.  Still ugly, IMO.
Optic<S, A>                      // Oh well.

You could also use the same delegate type for optics in F#, but then you'd lose the ability to use the standard << operator for composing optics. OTOH, using the F# function type from C# would be verbose (lack of parameterized type aliases). Perhaps there is some way to get a compromise that is usable from both C# and F# at the same time — perhaps via some type conversions on the C# side (conversion from F# function to a suitable wrapper on the C# side).

...

polytypic commented 4 years ago

...

By F#+, do you mean the FSharpPlus project? I think the goals of that project are somewhat incompatible with this approach. It already has an optics implementation that heavily builds on the overloading machinery of F#.

There are various pros and cons to each approach to optics. Nice things about the approach in this repo are that it gives you fairly flexible optics (isos, lenses, prisms, traversals) represented as standard functions (without really any type level wizardry) and allows using standard function composition for composing optics. Implementation can also be fairly efficient, compact, and straightforward. On the other hand, this approach does not distinguish between the optic kinds at the type level and does not support applicative traversals (like e.g. F#+ version or a higher-kinded encoding).

TBH, I don't have interest in spending time polishing open-source libraries at the moment. I've spent many years of my free time on such things already. Creating a quick demo/prototype like this repo for learning/knowledge transfer purposes is quite different. However, I can certainly try to help if you want to pursue implementing some sort C# <-> F# interoperable optics using this approach.

wallymathieu commented 4 years ago

Thanks for the answer! I'll try to understand this approach more fully since it looks interesting.

gusty commented 4 years ago

Nice things about the approach in this repo are that it gives you fairly flexible optics (isos, lenses, prisms, traversals) represented as standard functions (without really any type level wizardry) and allows using standard function composition for composing optics.

Also the F#+ implementation allows using standard function composition for composing optics.

polytypic commented 4 years ago

Also the F#+ implementation allows using standard function composition for composing optics.

Yes, it is very cool!

I came up with this technique, because we had to use C# 7.3. This F# version is really for learning/knowledge transfer purposes. I haven't seen anyone describe this approach before (someone might have, but I haven't seen it) and it seems to strike a very attractive compromise. I also believe this approach could be implemented in quite a few mainstream languages, which is a large part of why I made this repo public.

gusty commented 4 years ago

@polytypic for your information, F#+ use a lightweight SRTP overloaded functor for the current lenses implementation, by using the knowledge that no functor for primitive types is needed.

When I simplified the implementation to that, I also did a zero-srtp implementation, using interfaces, the branch is here: https://github.com/fsprojects/FSharpPlus/tree/light-lens it was an early exploration, but in the end I decided to go with the current light srtp implementation as it works very fast at both compile and runtime.

Of course, things get more complicated with traversals, there you need bi-generic functions I see no other way other than restricting traversals to specific traversables of a specific applicative otherwise.