Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shadowing as a workaround for orphans #23

Open
mausch opened this issue Apr 4, 2014 · 3 comments
Open

Shadowing as a workaround for orphans #23

mausch opened this issue Apr 4, 2014 · 3 comments
Labels

Comments

@mausch
Copy link
Contributor

mausch commented Apr 4, 2014

I've been thinking about a workaround for orphan instances. One way would be to shadow the original inline function and add another dictionary/lookup type.

For example, say you want to use Fleece to serialize a System.Collections.Generic.HashSet<T>. This type is currently not defined in the ToJSON class in Fleece, so it would be an orphan. Here's the shadowing:

open System.Collections.Generic
open System.Json
open Fleece
open ReadOnlyCollectionsExtensions

type MyJSON = MyJSON with
    static member inline ToJSON (x: _ HashSet) =
        JArray ((Seq.map toJSON x).ToReadOnlyList())

let inline iToJSON (a: ^a, b: ^b, z: ^z) =
    ((^a or ^b or ^z) : (static member ToJSON: ^z -> JsonValue) z)
let inline toJSON x = iToJSON (ToJSONClass, MyJSON, x)

(* original definitions:
let inline iToJSON (a: ^a, z: ^z) =
    ((^a or ^z) : (static member ToJSON: ^z -> JsonValue) z)
let inline toJSON (x: 'a) : JsonValue = iToJSON (ToJSONClass, x)
*)

let set1 = HashSet [1;2;3]
let setJSON = toJSON set1
let stringJSON = toJSON "abc" // instances defined in ToJSONClass still work
printfn "%s" (setJSON.ToString())

I'm almost certain this can also be done with the FsControl convention, but surely you can figure out how faster than me :)
The obvious downsides:

  • You have to look up the original definition of the function you want to shadow.
  • You have to redefine any other functions that depend on the function you're shadowing (annoying, but otherwise things would break horribly)

Do you see any other downsides? What do you think?

@gusty
Copy link
Owner

gusty commented Apr 5, 2014

When I started this project and was hitting my head against the wall trying to workaround orphan instances I came to the conclusion that a way to implement a poor man orphan instances is by shadowing functions and then re-wiring the overloads to the original definition by adding and additional parameter.
After all this is the same I was doing when someone in SO asks how to define the (+) operator for an existing class.
My memory is not so good now but I think I found some problems with this approach, I have the impression that some constraints did not propagate properly in some cases.
Anyway at that time there were some limitations, it was the (buggy) F# version 2.0 and I had to rely in the ternary operator (?<-) and when version 3 came to life I did some additional tests but I think it was already with the current convention.
Now that you came to the same idea but with working examples I think it worth reviewing this and try to answer these questions:

1 - Does it work always, or at least in most cases?
2 - Is it possible to do this with the current convention?

If the latter is not possible it might be another good point to switch to your proposed convention although default methods seems to be preferable feature over orphan instances.

@gusty
Copy link
Owner

gusty commented Nov 6, 2015

I had a look at the proposal, tested your script and did some tests on my own.
My conclusion is that it's a good workaround but it doesn't work in all cases, particularly when you have loops in the overload resolution (nested instances) which is a common scenario in application like Fleece, indeed there you have many nested definitions so you can try in your example changing set1 to this:

let set1 = (1, HashSet [1;2;3])

and it will fail, because the tuple instance does the original call, without the new (orphan) instance.

Right now in the coming version 2 of FsControl a new convention is being used, which is very similar to the one used in Fleece but usually has an extra parameter for defaults.

I found that doing a slight modification of that convention, that allows you to keep passing on the extra parameter through all calls, it's possible to workaround it. Here's a working solution.

@gusty gusty added the question label Nov 6, 2015
@gusty
Copy link
Owner

gusty commented Jan 5, 2017

Interestingly the above mentioned technique is being used now in F#+ to specify default methods for the MonadPlus/Alternative Invokables when used by generic computation expressions.

The idea there is that monads that are not MonadPlus should use a generic 'sequencing' implementation based on bind and return.

This shows that this technique is not only good as a workaround for orphans, it also allows you to create a different version of the Invokable class and augment it with other defaults.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants