Обновление записи для динамически разрешенного имени поля - PullRequest
0 голосов
/ 29 ноября 2018

У меня есть запись, определенная следующим образом:

data MyData = MyData
    { name :: String
    , addr :: String
     ... a lot of other fields of String type
    }

Далее я хочу создать список пар (String, fieldName), примерно так:

fields =
  [ ("NAME", name)
  , ("ADDRESS", addr)
  , ... and for other fields
  ]

И, наконец, мне нужнофункция, которая может получить пустую запись типа MyData и заполнить ее динамически поле за полем, например:

initByStrings strs = foldl (\ d (x, y) -> d{y=(findIn x strs)}) emptyMyData fields 

Возможно ли такое поведение в Haskell без длинных монотонных конструкций, как показано ниже?

...
lst = map (\ x -> findIn x strs) fields
f lst where
    f (name:addr:...) = MyData name addr ...

Ответы [ 2 ]

0 голосов
/ 29 ноября 2018

Это вариант использования для обобщений.

import GHC.Generics

data MyData = MyData
  { ...
  } deriving (Generic)  -- extension: DerivingGeneric

Класс типа Generic имеет связанный тип Rep и метод tofrom)

to :: MyData -> Rep MyData p {- ignore the p -}

Rep MyData разворачивается в тип, созданный с M1, (:*:) и K1:

Rep MyData =
  M1 D _ (
    M1 C _ (
        ( M1 S _ (K1 _ String) )
      :*:
        ( M1 S _ (K1 _ String) )
    )
  )
-- the things hidden by underscores carry metadata about MyData
-- (type name, constructor name, field names, whether fields are lazy, etc.).

Так что, если вы можете написать функцию, которая работает для многих комбинаций M1,(:*:), K1, затем вы можете получить функцию на MyData композицией с to.

class GFromMap r where
  gFromMap :: Map String String -> Maybe (r p)  -- always ignore the p

-- extension: FlexibleContexts
fromMap :: (Generic a, GFromMap (Rep a)) => Map String String -> Maybe a
fromMap m = to <$> gFromMap m

Нам нужно четыре экземпляра GFromMap.Два для новых типов M1 D и M1 C, несущих информацию о MyData, которые нас не волнуют (имя типа, имена конструкторов).

-- extension: FlexibleInstances
instance GFromMap r => GFromMap (M1 D d r) where
  gFromMap m = M1 <$> gFromMap m

instance GFromMap r => GFromMap (M1 C c r) where
  gFromMap m = M1 <$> gFromMap m

Один для продуктов (:*:)

-- extension: TypeOperators
instance (GFromMap r1, GFromMap r2) => GFromMap (r1 :*: r2) where
  gFromMap m = (:*:) <$> gFromMap m <*> gFromMap m

И еще один для полей, здесь нам нужно извлечь имя поля из метаданных s, связанных с новым типом M1 S, используя класс типа Selector.

-- extension: ScopedTypeVariables, TypeFamilies
-- the type equality (a ~ String) is for better error messages when
-- a record has a field not of type String
instance (a ~ String, Selector s) => GFromMap (M1 S s (K1 i a)) where
  gFromMap m = M1 <$> K1 <$> Map.lookup fdName m
    where fdName = toUpper <$> selName (undefined :: _t s _r _a)  -- we can refer to s thanks to ScopedTypeVariables

Полная суть: https://gist.github.com/Lysxia/f27c078faec11487df2828cdfb81752a

0 голосов
/ 29 ноября 2018

Решение было создано следующим образом:

В списке полей есть функция, которая обновляет соответствующее поле в записи:

fields =
  [ ("NAME", (\d x -> d{name=x}))
  , ("ADDRESS", (\d x -> d{addr=x}))
  , ... and for other fields
  ]

Функция, которая инициализирует MyData запись, выглядит так:

 initByStrings strs = foldl (\ d (x, y) -> y d(findIn x strs)}) emptyMyData fields 

Таким образом, поля записей могут обновляться одно за другим из foldl, используя некоторую внешнюю функцию, которая решает строковое значение из строкового имени поля из списка.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...