Haskell: преобразование из структуры Maybes в Hashmap

#haskell #haskell-lens

#haskell #haskell-объектив

Вопрос:

У меня часто возникает шаблон. У меня есть структура, подобная этой:

 data Foo = Foo
  { foo  :: Maybe Text
  , bar  :: Maybe Text
  , buz  :: Maybe Text
  }
  

Я хочу преобразовать его в карту с метками-значениями, не отбрасывая никаких значений:

 myMap :: [(Text, Text)]
  

например: myMap = [("foo", "val1"), ("bar", "val2")]

Какой самый элегантный способ сделать это? Я пробовал использовать такие шаблоны, как if isJust ... then fromJust ... else Nothing , а затем использовать catMaybes , но это не очень элегантно, IMO.

На практике моя структура более неоднородна:

 data Foo = Foo
  { foo  :: Maybe Text
  , bar  :: Maybe Bool
  , buz  :: Maybe Int
  }
  

И я хочу преобразовать его в:

 myMap :: [(Text, Value)]
  

Есть ли для этого способ Lens?

Комментарии:

1. Сигнатура типа, такая как myMap :: [(Text, Value)] , по-видимому, подразумевает, что все поля имеют один и тот же тип. Списки должны быть однородными.

2. @jpmarinier извините, я забыл упомянуть, что это значение от Aeson: hackage.haskell.org/package/aeson-1.5.4.1/docs /…

3. @cdupont: вы можете позволить Haskell автоматически создавать экземпляр для ToJson , а затем использовать его для генерации пар ключ-значение.

Ответ №1:

Мы можем позволить Haskell автоматически создавать экземпляр ToJson :

 {-# LANGUAGE DeriveGeneric #-}

import Data.Text(Text)
import Data.Aeson(ToJSON)

import GHC.Generics(Generic)

data Foo = Foo
  { foo  :: Maybe Text
  , bar  :: Maybe Bool
  , buz  :: Maybe Int
  } deriving Generic

instance ToJSON Foo  

Затем вы можете отфильтровать элементы, значение которых равно Null :

 import Data.Aeson(Value(Null, Object), toJSON)
import Data.HashMap.Strict(toList)

kvs :: Foo -> [(Text, Value)]
kvs foo
    | Object obs <- toJSON foo = filter ((Null /=) . snd) (toList obs)
    | otherwise = []  

Например:

 Prelude> kvs (Foo Nothing (Just True) (Just 14))
[("buz",Number 14.0),("bar",Bool True)]
  

Комментарии:

1. Большое спасибо, я думаю, что это лучшее решение.

Ответ №2:

Не объектив, но, по крайней мере, это просто с пониманием списка:

 quux :: Foo -> [(String, Text)]
quux v = [ (a,b) | (a, Just b) <- zip ["foo", "bar", "buz"] 
                                 $ map ($ v) [foo, bar, buz]]
  

или ваш второй запрос,

 quux2 :: Foo -> [(String, Value)]
quux2 v = [ (a,b) | (a, Just b) <- zip ["foo", "bar", "buz"] 
                    $ map ($ v) 
                       [(TextValue <$>) . foo, 
                          (BoolValue <$>) . bar, (IntValue <$>) . buz]]
  

предположим,

 TextValue :: Text -> Value
BoolValue :: Bool -> Value
IntValue :: Int -> Value
  

Да, это быстро становится хрупким и громоздким. Но первый вариант достаточно прост.

Комментарии:

1. Большое спасибо, мне нравится понимание списка. Хотя я всегда забываю их использовать…