Я пытаюсь написать помощники 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.
@DanielWagner да, ты прав. Технически getField должен работать с MyRecord и Maybe MyRecord, тогда как fieldNameFor должен работать с obj и Maybe obj (что каким-то образом будет использовать getField внутри)
Что должен fieldValueFor @"field1" (FormCtx "MyRecord" Nothing) вернуть?
@JosephSible-ReinstateMonica, случай № 1: fieldValueFor @"field1" (FormCtx "MyRecord" Nothing) = Nothing ; случай №2: fieldValueFor @"field1" (FormCtx "MyRecord" (Just myRec)) = Just $ getField @"field1" myRec
Итак, вы хотите, чтобы он всегда возвращал Maybe a, а не a, независимо от того, был ли obj завернут в Maybe?
@JosephSible-ReinstateMonica Я отредактировал свой предыдущий комментарий, чтобы прояснить намерения. Посмотрите, имеет ли это больше смысла сейчас?
Так должен ли fieldValueFor @"field1" (FormCtx "MyRecord" myRec) вернуться getField @"field1" myRec (как сейчас) или Just $ getField @"field1" myRec?
Кроме того, было бы приемлемо, если бы это работало с Identity obj и Maybe obj вместо obj и Maybe obj?
@JosephSible-ReinstateMonica, если это единственный возможный способ реализовать что-то подобное... хотя мне интересно, какую боль это принесет на каждом сайте вызова, который должен использовать ctxObject. Можно ли будет сделать ctx ^. object . someLens вместо ctx ^. object . identity . someLens?





Это не очень красиво, но это работает. Просто замените существующий 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 (оно все время было лишним), и тогда ваша существующая реализация будет работать.
Почему
objиMaybe obj, а неMyRecordиMaybe MyRecord?