I want a typeclass that represents membership in a reified Data.Map.Map. So something like:
class Reifies s (Map Text v) => IsMember (x :: Symbol) s where
value :: Proxy s -> Proxy x -> v
And then I would like to implement a function which returns a Dict instance of this class whenever a symbol is present:
checkMember :: forall s x v. (KnownSymbol x, Reifies s (Map Text v))
=> proxy x -> Maybe (Dict (IsMember x s))
checkMember sx =
let m = reflect (Proxy @s)
in (_ :: v -> Dict (IsMember x s)) <$> Map.lookup (symbolVal sx) m
I don't mind using unsafeCoerce to implement checkMember, but even so I'm having trouble figuring out how to do this (fill in the type hole).
Approximate preamble:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.Constraint(Dict(..))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Proxy (Proxy(..))
import Data.Reflection (Reifies, reflect)
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
Why does it have to be a class IsMember? How about a simple type:
newtype Member x s v = Member v
checkMember :: ... => proxy x -> Maybe (Member x s v)
Keeping Member abstract allows to preserve the invariant that a value of type Member x s v belongs to the dictionary associated with s. No unsafeCoerce on your part are needed.
From there, there might also be some way to use reflection to lift Member back to the type level but that sounds overengineered.
EDIT: from the discussion, it seems the requirement is external and there isn't much to do about it. Here is a way to implement checkMember.
(reflection implements its own machinery like this too.)
We can abuse the fact that GHC desugars classes with a single method and no superclasses class C a where m :: v directly to the unwrapped method m :: v, and constrained values C a => b to functions v -> b.
IsMember without superclasses (IsMember0)IsMember0 x s v => r in a newtype so it can be coerced to IsMember0 x s v -> r (UnsafeMember){-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.Constraint(Dict(..))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Proxy (Proxy(..))
import Data.Reflection (Reifies, reflect, reify)
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import Unsafe.Coerce (unsafeCoerce)
type Text = String
class IsMember0 (x :: Symbol) s v | s -> v where
value0 :: Proxy s -> Proxy x -> v
class (Reifies s (Map Text v), IsMember0 x s v) => IsMember (x :: Symbol) s v | s -> v
instance (Reifies s (Map Text v), IsMember0 x s v) => IsMember (x :: Symbol) s v
value :: IsMember x s v => Proxy s -> Proxy x -> v
value = value0
newtype UnsafeMember x s v = UnsafeMember (IsMember0 x s v => Dict (IsMember x s v))
unsafeMember :: forall x s v. Reifies s (Map Text v) => v -> Dict (IsMember x s v)
unsafeMember v = unsafeCoerce (UnsafeMember @x @s @v Dict) (\ _ _ -> v)
checkMember :: forall s x v proxys proxyx. (KnownSymbol x, Reifies s (Map Text v))
=> proxys s -> proxyx x -> Maybe (Dict (IsMember x s v))
checkMember _ sx =
let m = reflect (Proxy @s)
in unsafeMember <$> Map.lookup (symbolVal sx) m
-- Executable example
main :: IO ()
main = do
let d = Map.fromList [("foo", 33 :: Int)]
foo = Proxy :: Proxy "foo"
reify d (\p ->
case checkMember p foo of
Nothing -> fail "Not found"
Just Dict -> print (value0 p foo))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With