-
Notifications
You must be signed in to change notification settings - Fork 66
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
Add variadic callback generators #83
Conversation
It adds * `h$makeCallbackVar` in jsbits/callback.js * variadic callback generators in GHCJS/Foreign/Callback/Variadic.hs * Better explanation of `OnBlocked` and `syncCallback'` in README * A detailed explanation of variadic callbacks in README It bumps the version to 0.2.1.0
Thanks for the code and detailed readme, and apologies for taking so long to review it. A downside I see with this approach is that it always has to convert Also you use My last point is about handling the situation where the number of arguments is different from what we expect. JavaScript uses the Anyway, variadic callbacks are long ovedue, so thanks for building a working implementation; we'll definitely merge some variant of this in |
It doesn't convert
With my implementation,
I want to hear your rationale. The benefit of removing the noise of
If
Unless a callback itself is variadic, when a callback is called with wrong numbers of arguments, the callback or the caller should be modified. If a caller could pass instance PFromJSVal a => PFromJSVal (Maybe a) where
pFromJSVal x | isUndefined x || isNull x = Nothing
pFromJSVal x = Just (pFromJSVal x)
{-# INLINE pFromJSVal #-} Since javascript doesn't have compile time error checking, the callback argument mismatch should be reported as a runtime error. RecommendationIn README, I attached |
Ah you're right, you're not completely converting I do agree that the type signature for your variant is strictly more general, it's still possible to use So from that perspective, I think that unless we have a strong reason to keep both parts together, it'd be best to implement the core functionality with purely Having I still think it'd be better to first review the alternative I have in mind. Perhaps we'll find that my idea has some other downsides that I hadn't thought about, or figure out an even better approach. But I don't think it'd be fair to let you wait much longer, so I'll do the code for it this weekend. If I fail to come up with a reasonable working (not necessarily finished) approach then I agree that merging your PR with an experimental status is better than letting this sit for much longer. Is that ok? |
Yes, it's ok. |
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
module Main where
import GHC.TypeLits
import Data.Proxy
import Data.Type.Equality
import Data.Type.Bool
-- uh fake, use the real Any in the implementation
data Any = Any
unsafeCoerce :: a -> Any
unsafeCoerce _ = Any
data Callback = Callback { callbackArity :: Integer
, callbackAction :: Any
}
-- | The end result of a function
type family Result x where
Result (a -> b) = Result b
Result a = a
-- | The arity of a function
type family Arity n :: Nat where
Arity (a -> b) = 1 + Arity b
Arity a = 0
type family AllArgsEq t f :: Bool where
AllArgsEq t (a -> b) = a == t && AllArgsEq t b
AllArgsEq t a = 'True
type IsValidCallback a =
( KnownNat (Arity a)
, 'True ~ (AllArgsEq Int a && Result a == IO ()))
mkCallback :: forall a. IsValidCallback a => a -> IO Callback
mkCallback f = mkCallbackInternal (natVal (Proxy :: Proxy (Arity a)))
(unsafeCoerce f)
mkCallbackInternal :: Integer -> Any -> IO Callback
mkCallbackInternal n x = do
putStrLn ("creating callback with arity " ++ show n)
-- do some foreign call to some RTS function here
pure (Callback n x)
-------------------------------------------------------------------------------
cb1 :: Int -> Int -> IO ()
cb1 = undefined
cb2 :: IO ()
cb2 = undefined
cb3 :: Int -> String -> IO ()
cb3 = undefined
cb4 :: Int -> a -> IO ()
cb4 = undefined
main :: IO ()
main = do
mkCallback cb1
mkCallback cb2
-- mkCallback cb3
-- mkCallback cb4
pure () This was what I had in mind. Only the general case is implemented here, where an Since we know the expected arity at creation time, we can easily add optimized cases for small arities. An actual implementation could avoid this runtime test by making more use of the typelevel arity nat to dispatch to specialized callback makers in JS for some arities (say 0..2). But I'm not sure if that'd be worth the trouble, since I expect callback creation to be relatively rare compared to callback use. |
As long as there is user documentation for On electron, callbacks of 6~11 arguments are not unusual. The current iteration of ghcjs-base cannot be used to make a GHCJS binding for electron. |
I'm closing this due to inactivity. |
My variadic callback generators are efficient because they directly iterate over
arguments
object.This pull request adds
h$makeCallbackVar
in jsbits/callback.jsGHCJS/Foreign/Callback/Variadic.hs
OnBlocked
andsyncCallback'
in READMEAlso, it bumps the version to 0.2.1.0
This relates to #76