purescript-deprecated / purescript-generics

21 stars 20 forks source link

Added generic instance for Unit #28

Closed eskimor closed 8 years ago

paf31 commented 8 years ago

Technically, this instance is a bit dodgy, since this isn't the representation of the unit type, but perhaps we should make an exception for Unit? Things like gShow will be wrong though, in the sense that it will generate a string which doesn't evaluate to unit.

eskimor commented 8 years ago

Actually - why isn't Unit implemented as Unit = Unit ?

garyb commented 8 years ago

A lot of FFI code that purports to return Unit doesn't actually return anything at all, which is kinda ok, as there's only one possible inhabitant for unit anyway, so we can always ignore its actual value.

It will become a foreign type in the next Prelude release to reinforce this.

eskimor commented 8 years ago

The JSON representation, to be compatible with aeson should be [] actually. Then we have the current definition in the Prelude which is Unit {} and the definition it should look like for show, which is: Unit/unit? I think I am not in the position to make this decision, for me any representation would be fine.

paf31 commented 8 years ago

I don't think aeson compatibility is a concern in this library. And the JSON representation is determined in Prelude already.

eskimor commented 8 years ago

An addition to GenericSignature/GenericSpine like the one suggested here would maybe also be useful for Unit?

garyb commented 8 years ago

This is going to be resolved by #36 - we decided to give Unit special handling in generics so we don't have a dodgy representation using SigProd, etc. Thanks though!

eskimor commented 8 years ago

Hmmm - and what is the solution for "native types"? Like JSDate? In my opinion it makes perfect sense for them to have some Generic instance, like suggested here.

I mean it is certainly possible to work around this, like simply not using JSDate, but it makes things more awkward than they need to be. (For example if a JSDate is nested in some type you are using and you eventually need serialization only to find out, there is no Generic instance for this type and there can't be any.)

What is the rationale here? Or is there already a solution I don't know about?

Thank you for any pointers!

garyb commented 8 years ago

There is none, and JSDate is going in the bin :laughing: or, at least in terms of the datetime library it is.

There's no way we can express a mechanism for reinstantiating arbitrary JS types, so it's not something that is suitable for generics to express, not to mention that allowing JS types in generics would be hostile to other backends. There's another even bigger class of things it can't represent: types with functions in them, which rules out Lazy types too.

Generics can't be a magic wand for serialisation unfortunately, and it never was intended to be - it was more for implementing things like gShow, generic traversals, etc. which is why it focuses on the approach of dealing with types that are expressed in terms of sums and products. It just so happens that sometimes it is useful for reducing boilerplate there too. Another thing to remember is that generics is not a cost-free abstraction either.

The solution is to write your own functions using argonaut or something along those lines, making use of generics to avoid boilerplate for the types that it is possible to do so, and writing the rest yourself.

eskimor commented 8 years ago

@garyb : Thanks for the clarification. The problem with writing the rest yourself is that, this means in PureScript that you have to write it yourself for every type that contains a type with no Generic instance anywhere - recursively! In Haskell this is different, because you can just write the 'ToJSON' instance for the offending type and generics work again for any type depending on it.

This is because in PureScript you can not fallback to hand-written instances on a case-by-case basis, either the complete thing works - or nothing. This problem could be mitigated, by having some dummy Generic instance for native types like: "SigNative" - with no further information, than you could at least special case types containing a "SigNative" somewhere.

On the other hand if what you are telling me is, that I should not use PureScript generics for serialization, then this is kind of bad for me :-) as my work on argonaut was then not so well spent and I should have focused on writing a instance-code generator for purescript-bridge instead of relying on PS Generics. Which is probably the better solution anyway, but as I have some time pressure for my project I was going the way of the least resistance - and coming from Haskell Generics it seemed reasonable.

Anyway - thanks for clearing things up!

garyb commented 8 years ago

I'm not saying you shouldn't use generics for serialisation, only that it's not designed specifically for that so might not be a perfect 1:1 match.

SigNative is still a bit problematic, as you need to be able to deserialize every possible native type when one is encountered, and since there are infinite possible native types (since types with non-Object prototypes can be defined in JavaScript) that would perhaps be a little tricky. :smile:

I wouldn't say your work on Argonaut is wasted either, with argonaut+generics you should only have to fill in instances for the types that are products with non-generic types.

The "no functions" thing will continue to be a bit of a hurdle, but I'd also just say that I'd hope there are very few types that are wrappers around native types anyway, since we've seen how problematic that approach is with Date, for example. Certainly I'm not going to be writing any more code that takes that approach, instead favouring conversions to "real" PureScript types as much as possible. I'd prefer never to ecounter non-PS types in PureScript at all, aside from situations where they are required like DOM, Eff, Aff, etc.

eskimor commented 8 years ago

I see. If not using JS types is a good policy in your opinion, then the situation is actually not that bad :-) I might add an instance generator to purescript-bridge in the future, but I think the Generics solution is good enough for now (also I really have no time for rewriting things now ;-) ).

Btw. I did quite a few changes in my PR for argonaut-codecs which makes special casing possible (for full aeson compatiblity) as long as there are Generic instances. Is this still fine for merging?

eskimor commented 8 years ago

SigNative is still a bit problematic, as you need to be able to deserialize every possible native type when one is encountered, and since there are infinite possible native types (since types with non-Object prototypes can be defined in JavaScript) that would perhaps be a little tricky. :smile:

Well what it buys you is that you have a Generic instance for the containing type - and then you can special case it for your JSON serialization based on the type name of the signature, like I did in the PR. Of course this approach is not very efficient, but it works and should be fine for not too many special rules/small types.

To make it more clear what I mean: Your generic toJSON function when encountering a type named "SomeTypeIKnowItContainsADate" - can have special code for handling this type, this way special casing is possible even with PureScript generics on a case by case basis.

This was needed as otherwise for example generic aeson compatible encoding would not be possible for any type containing Either,Maybe, Tuple, for example - which would disqualify a lot of useful types.