-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Typesafe marshal #34
base: master
Are you sure you want to change the base?
Typesafe marshal #34
Conversation
What is the reason for having two |
Thanks for the suggestions! I've added a commit updating the laws. One thing that the pure marshaling is used for is this quasiquoter: https://github.com/ghcjs/ghcjs-ffiqq It makes sense to have pure marshaling for immutable things. So, in javascript this applies to the boolean / number / string types. Pure marshaling doesn't make sense for objects, because they can change. It does make sense to have pure marshaling like |
Okay, now I understand the differentiation. Thanks. Follow-up question: Is pure marshaling a necessity or an optimization or something else? |
With pure conversions you can operate directly on immutable (either guaranteed by the underlying type, like JavaScript string or number, or by the typesystem) data types from pure Haskell code. Normally the impure conversions should be a strict superset of the pure conversions, since you should be able to get an impure one from a pure one by adding I don't really like the fact that it's two typeclasses. Can we do better? Since purity is really a property of an instance, it would be better to indicate for every instance whether the conversion is pure, eliminating the second class. I played with the following code earlier, but without injectivity of the
An alternative route that actually does work: Using GADTs, and keeping the
|
Bikeshedding opportunity: rename Looks prettier, but will probably require the use of more qualified imports. I don't really like having half the world prefixed with |
So this makes me think that the motivation is convenience? If so, is it really worth the effort to differentiate between these types? |
The only realistic option would be to drop pure conversions, which would force much more code to be in IO, unless we have a separate way to handle all the pure conversions. The pure conversions were originally introduced for for example things like this get much less nice if you need IO everywhere, and IO adds considerable overhead, preventing many optimizations:
And I also think it's quite usful to operate on |
Fair enough. Thanks for the responses. I hope you don't mind me continuing to flesh out my understanding on this topic here. What are the scenarios in which we need to convert types? Let me propose the ones that come to mind:
Since the most general form of conversion for all types is via We can always convert via Now, this prompts the question of whether we need to treat all primitives in the same way. Do we need to convert any primitive JS type polymorphically? (This would be 3 on the list above, if true.) If the answer is yes, we should have a type class (or some other ad-hoc mechanism) for that. If the answer is no, then I would question the use of an ad-hoc mechanism for something we don't need. The The use of the primitive JS type polymorphism in ghcjs-ffi appears to be a way to reduce the number of symbols one needs to know to use the interface. That is, you can use the overloaded What if, instead of using a type class to select the conversion instance, you used a parameter to the quasiquoter function that specialized the type? You could, for example, use the TH This approach removes any concern with type inference and avoids polluting the namespace with type-specific functions. Finally, you wouldn't need to define a class of pure JS type conversions. Feel free to shoot holes in my understanding or ideas. |
I do welcome comments. And we should definitely steer towards simplicity unless we have a good reason for more type classes. I'm really short on time though, with preparations for Haskell eXchange taking most of my time at the moment, so I'll come back to this later. |
I think this is a good way to get rid of the pure marshaling classes: class PureMarshal a
instance PureMarshal JSRef
instance PureMarshal Text
-- etc
pFromJSRef :: PureMarshal a => JSRef -> a
pFromJSRef = unsafePerformIO . fromJSRefUnchecked
{-# INLINE pFromJSRef #-}
pToJSRef :: PureMarshal a => a -> JSRef
pToJSRef = unsafePerformIO . toJSRef
{-# INLINE pToJSRef #-} Inlining should make this perform just as well for the common case, right? This leaves out the |
I initially had it this way, but then backpeddled because it seemed weird to have
At some point, I'm hoping to have a typescript FFI quasiquoter - see this https://github.com/mgsloan/ghcjs-typescript#alternative-approach-to-typescript-ffi . |
Nope, at least not with And I really don't like this approach, it's too fragile. You basically insert |
Hmm, good point. Darn, I'd like something really simple! Your GADTs approach is pretty reasonable, but it would obviously be quite a breaking change. I'd like to bundle such a change with the change to using exceptions instead of I don't think associated type families should be used, as they don't play well with GND - https://ghc.haskell.org/trac/ghc/ticket/8165 - but things ought to work fine with using unassociated type families for this. The boilerplate for defining new ref types would get rather big, though: {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Typeable (Typeable)
import GHCJS.Marshal
import GHCJS.Marshal.Pure
import GHCJS.Types
newtype Wrapper = Wrapper JSRef
deriving (Typeable, IsJSRef, ToJSRef, FromJSRef)
type instance JSType Wrapper = Wrapper
type instance IsPureFromJSRef Wrapper = True
type instance IsPureToJSRef Wrapper = True If the goal is to reduce the redundancy / complexity of having 4 type classes, then this doesn't seem like a very good idea, as it just turns 2 type classes into type families, which need values for every type which supports marshaling. With the current situation, you only need to mention the pure marshaling stuff when you actually have pure marshaling. So, my conclusion is that there's no good way to avoid the extra type classes for pure conversions. |
Of course! No problem. |
I've rebased this atop master. Does this look good? I'm waiting on review of this before addressing the remaining items in the checklist. Next up after this is switching marshaling over to using exceptions instead of |
d3d301b
to
eb050f2
Compare
See this issue: ghcjs/ghcjs#419
eb050f2
to
6bf4307
Compare
It seems you guys want to break and rewrite Marshalling. I'm going to document FFI and what I know about Marshalling and the relationship between FFI and Marshalling on https://github.com/ghcjs/ghcjs/wiki/GHCJS-User-Guide I already documented a basic project setup with stack on it. Since FFI is not documented, it was difficult for me to figure out how it worked. I had to ask people and consult ghcjs-dom. Coding is not finished until it is documented for users. I hope you won't break FFI anytime soon because I spent days on poking GHCJS to figure out FFI. |
Notes:
EnsureNotMaybe
constraint to make the marshaling ofMaybe
well behavedJSType
andJSONType
toJSTypeOf
andJSONTypeOf
, to avoid collision with the new type family.Things left to do:
GHCJS.Marshal
- it might be better to have the other parts of ghcjs-base import this marshaling stuff. That would allow the stuff here to turn into normal deriving: https://github.com/mgsloan/ghcjs-base/blob/bd055d746b0c38c3d104fca7f89fa137e59d6111/GHCJS/Marshal.hs#L190 .NFData
/Data
as standard practice for newtype wrappers around references (documented here)