I wanted to define optics over a custom data type. Failed to do it with the approach given by the documentation, and then succeeded by doing it differently. I don't fully understand the consequences of this, but I get the sense something is wrong – either with what I do, or the documentation.
> open FSharpPlus.Lens ;;
> type Person<'TName> = { Name : 'TName; Age : int } ;;
type Person<'TName> =
{Name: 'TName;
Age: int;}
> (* Let's do it like in the documentation; that looks neat! *) ;;
> let inline _name f { Name = n; Age = a } = f n <&> fun n' -> { Name = n'; Age = a } ;;
val inline _name :
f:('a -> ^b) -> Person<'a> -> ^_arg4
when ( ^b or ('c -> Person<'c>)) : (static member ( <&> ) : ^b *
('c ->
Person<'c>)
-> ^_arg4)
> (* Okay, so that looks like it might have succeeded. *) ;;
> (* Let's find a person try to it on. *) ;;
> let me = { Name = "Chris"; Age = 39 } ;;
val me : Person<string> = {Name = "Chris";
Age = 39;}
> (* Oops, I meant to be slightly more anonymous. *) ;;
> setl _name "kqr" me ;;
setl _name "kqr" me ;;
-----^^^^^
/home/kqr/stdin(33,6): error FS0001: Expecting a type supporting the operator '<&>' but given a function type. You may be missing an argument to a function.
On the other hand, if I re-create the optic with the lens helper function, things seem to work better:
> let inline _name f = lens (fun p -> p.Name) (fun p n -> { Name = n; Age = p.Age }) f ;;
val inline _name :
f:('a -> ^b) -> (Person<'a> -> ^c)
when (FSharpPlus.Control.Map or ^b or ^c) : (static member Map : ( ^b *
('d ->
Person<'d>)) *
FSharpPlus.Control.Map
->
^c)
> setl _name "kqr" me ;;
val it : Person<string> = {Name = "kqr";
Age = 39;}
The first thing that calls my attention is this inferred function signature:
val inline _name :
f:('a -> ^b) -> Person<'a> -> ^_arg4
when ( ^b or ('c -> Person<'c>)) : (static member ( <&> ) : ^b *
('c ->
Person<'c>)
-> ^_arg4)
By looking at it, specially at the static member ( <&> ) part, it seems to me that you're not opening the FSharpPlus.Lens namespace, in the second part of the repro.
Because in that namespace are the F#+ lens operators defined, including the <&>.
Now if you're not very familiar with F# inference, this is tricky (sometimes even if you are experienced as well). F# inference works differently with operators and methods, if you mention a method that doesn't exists it fails compiling, unless you use the SRTP trait call syntax, but if it's an operator, it will succeed and it will create an SRTP trait call, as we can see in that inferred signature. So that's why it's sometimes good, but sometimes tricky and may hide an error, like forgetting to define the global operator or forgetting (as I think is your case) to open the namespace where the operator is defined. It happened to me many times !
So please, try your repro as it is but adding the above mentioned line before or right after this:
> (* Let's do it like in the documentation; that looks neat! *) ;;
The first line in the repro case is opening FSharpPlus.Lens.
The second code block is from the same session -- it's the very nest command.
In other words, FSharpPlus.Lens is just as opened in both cases.
(But thank you for the interesting information none the less!)
Sorry, it was a quick guess. I don’t have my computer now but will have it tomorrow evening and will try it.
I just tried your code and it works.
Looks like my guess wasn't that bad, you must be doing something wrong, alternatively you might be using an old version of the library.
Please see what I'm getting by running line by line:
#r @"/archives/repos/gusty/FSharpPlus.git/src/FSharpPlus/bin/Release/net45/FSharpPlus.dll" ;;
 --> Referenced '/archives/repos/gusty/FSharpPlus.git/src/FSharpPlus/bin/Release/net45/FSharpPlus.dll' (file may be locked by F# Interactive process)
open FSharpPlus.Lens ;;
type Person<'TName> = { Name : 'TName; Age : int } ;;
 type Person<'TName> =
 { Name: 'TName
 Age: int }
let inline _name f { Name = n; Age = a } = f n <&> fun n' -> { Name = n'; Age = a } ;;
 val inline _name :
 f:('a -> ^b) -> Person<'a> -> 'd
 when ^b : (static member Map : ^b * ('c -> Person<'c>) -> 'd)
Anyway, the documentation is not 100% accurate in that it doesn't mention explicitly that the namespace should be opened. We must correct it.
Let me know if you find what's wrong on your side, I'm curious now. Also let me know if you can't find the problem and need additional help.
I've tried it out with later version of f#+ and with 1.0.0 version.
```f#
works as above while with 1.1.0-CI00272 you get the following behaviour:
```f#
#I "/Users/mathieu/.nuget/packages/fsharpplus/1.1.0-CI00272/lib/netstandard2.0"; #r "FSharpPlus.dll";
> open FSharpPlus.Lens ;;
> type Person<'TName> = { Name : 'TName; Age : int } ;;
type Person<'TName> =
{ Name: 'TName
Age: int }
> (* Let's do it like in the documentation; that looks neat! *) ;;
> let inline _name f { Name = n; Age = a } = f n <&> fun n' -> { Name = n'; Age = a } ;;
val inline _name :
f:('a -> ^b) -> Person<'a> -> 'd
when ^b : (static member Map : ^b * ('c -> Person<'c>) -> 'd)
> let me = { Name = "Chris"; Age = 39 } ;;
val me : Person<string> = { Name = "Chris"
Age = 39 }
> setl _name "kqr" me ;;
val it : Person<string> = { Name = "kqr"
Age = 39 }
Okay, so if I'm reading this right, it means the stable 1.0.0 version on NuGet does not support defining optics with <&>, but the latest prerelease does?
And the prerelease version 1.1.0-CI00245 has release note "Fix some type inference problems" – so that might very well be the version that fixes it first.
Any chance of backporting this bugfix to 1.0.0? Or will 1.1.0 be released soonish?
I confirm the latest stable version which at the moment is 1.0.0 doesn't support this feature.
If you read more on the release notes, you'll find this was introduced 1 year ago in 1.1.0-CI00240.
We're planning to release a 1.1.0 version before the end of the year, which will have lot of new features while keeping backwards compatibility with 1.0.0
The pre-release versions have all features you see in the source code but are not 100% guaranteed free of reversions / regressions, anyway I'm very confident at this point that we're so close to 1.1.0 that no reversion will take place.
So my advice is to use the latest pre-release until 1.1.0 is out.
Also note that you can still use that syntax in version 1.0.0 but you need to open the FSharpPlus namespace and use the <!> operator instead.
Oh! That is by far sufficient! Thank you. If I submit a PR that updates the documentation to say as much, will it meet much resistance?
Please do so. More docs are never too much doc ;)
Recently ran into an issue similar to this. Should the documentation for <&> go in https://fsprojects.github.io/FSharpPlus/reference/fsharpplus-operators.html or should there be a new page that's dedicated to the Lens operators?
Ideally both.
A quick update: unfortunately we're still trying to get back the doc generation and trying to understand why from one day to another it started failing.
@wallymathieu is doing some efforts in order to get it working again.
Thank you for your transparency! It makes a huge difference to your users, even if that's not immediately obvious.
Doc generation is back !
PRs for the docs are very welcome as we're trying to improve it.
Also v1.1.0 RC1 is out ;)
I added a note in the docs, sorry again for the confusion. Hopefully no more devs fall in the trap.
Most helpful comment
Ideally both.
A quick update: unfortunately we're still trying to get back the doc generation and trying to understand why from one day to another it started failing.
@wallymathieu is doing some efforts in order to get it working again.