如何(不安全地)将地图反映为约束?

dsp*_*pyz 5 reflection dictionary haskell typeclass singleton-type

我想这代表了一个会员资格类型类具体化 Data.Map.Map.所以类似于:

class Reifies s (Map Text v) => IsMember (x :: Symbol) s where
  value :: Proxy s -> Proxy x -> v
Run Code Online (Sandbox Code Playgroud)

然后我想实现一个函数,Dict只要符号存在就返回该类的实例:

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
Run Code Online (Sandbox Code Playgroud)

我不介意unsafeCoerce用来实现checkMember,但即便如此,我也无法弄清楚如何做到这一点(填写类型孔).


近似序言:

{-# 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)
Run Code Online (Sandbox Code Playgroud)

Li-*_*Xia 1

为什么它必须是一个类IsMember?一个简单的类型怎么样:

newtype Member x s v = Member v

checkMember :: ... => proxy x -> Maybe (Member x s v)
Run Code Online (Sandbox Code Playgroud)

保持Member抽象可以保留类型值Member x s v属于与之关联的字典的不变式s的不变式。unsafeCoerce您不需要。

从那里开始,可能还有一些方法可以使用反射来提升Member回类型级别,但这听起来有些过度设计。


编辑:从讨论来看,似乎需求是外部的,没有太多可做的。这是一种实现方法checkMember

reflection也像这样实现自己的机制。)

我们可以滥用这样一个事实:GHC 使用单一方法对类进行脱糖处理,并且没有超类,class C a where m :: v直接将其应用于展开的方法m :: v,并将值限制C a => b为函数v -> b

  • 我们需要一个没有超类的版本IsMemberIsMember0 )
  • 我们包装IsMember0 x s v => r一个新类型,这样它就可以被强制为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))
Run Code Online (Sandbox Code Playgroud)