我正在尝试编写 HTML 表单助手,我希望调用站点支持以下两种用例:
{-# LANGUAGE AllowAmbiguousTypes, DataKinds, OverloadedStrings #-}
import Data.Proxy
import Data.Text as Text
import GHC.Generics
import GHC.Records
import GHC.TypeLits
data FormCtx obj = FormCtx { ctxFieldNamePrefix :: !Text, ctxObject :: !obj }
data MyRecord = MyRecord { field1 :: !Text, field2 :: !Text } deriving (Generic)
-- USE-CASE #1
let ctx = FormCtx "MyRecord" obj
in (fieldNameFor @"field1" ctx, fieldValueFor @"field1" ctx)
-- USE-CASE #2 -- with a (Maybe obj)
let ctx = FormCtx "MyRecord" (Just obj)
in (fieldNameFor @"field1" ctx, fieldValueFor @"field1" ctx)
-- Implementation for fieldNameFor which works only
-- with `obj`, and not `Maybe obj`
fieldNameFor :: forall k obj a . (KnownSymbol k, HasField k obj a) => FormCtx obj -> Text
fieldNameFor FormCtx {ctxFieldNamePrefix} = ctxFieldNamePrefix <> "[" <> (Text.pack $ symbolVal (Proxy @k)) <> "]"
-- Implementation for fieldValueFor which works only
-- with `obj`, and not `Maybe obj`
fieldValueFor :: forall k obj a . (KnownSymbol k, HasField k obj a) => FormCtx obj -> a
fieldValueFor FormCtx {ctxObject} = getField @k ctxObject
我尝试定义一个名为
FieldNameFor
的类型类,以便能够为 obj
和 Maybe obj
定义重叠实例,但我无法使其与 KnownSymbol k, HasField k obj a
所需的类型类约束一起使用getField
上班。
这不是很漂亮,但是很有效。只需用它替换现有的
fieldValueFor
即可:
{-# LANGUAGE TypeFamilies #-}
type family CopyMaybe a b where
CopyMaybe (Maybe a) b = Maybe b
CopyMaybe a b = b
class FieldValueFor k obj a where
fieldValueFor :: FormCtx obj -> CopyMaybe obj a
instance {-# OVERLAPPABLE #-} (KnownSymbol k, HasField k obj a, CopyMaybe obj a ~ a) => FieldValueFor k obj a where
fieldValueFor FormCtx {ctxObject} = getField @k ctxObject
instance (KnownSymbol k, HasField k obj a) => FieldValueFor k (Maybe obj) a where
fieldValueFor FormCtx {ctxObject} = getField @k <$> ctxObject
对于
fieldNameFor
来说更简单:只需删除 HasField k obj a
约束(它一直都是多余的),然后您现有的实现就可以工作了。