将大型记录类型的解析器放入函子中

时间:2019-03-01 17:51:36

标签: haskell

这是this question的后续问题,涉及在单子上下文中解析JSON。

考虑一个简单的记录类型及其Data.Aeson.FromJSON实例。对于(Int, Int)字段,我正在从JSON中读取一个值,并将另一个值设置为20。

import Data.Aeson

data DataPoint = DataPoint { placeName :: String
                           , readings :: (Int, Int)
                           } deriving (Show)

instance FromJSON DataPoint where
    parseJSON = withObject "DataPoint" $ \o -> DataPoint
        <$> o .: "name"
        <*> fmap (\v -> (v, 20)) (o .: "reading")

这很好:

> (decodeThrow "{\"name\": \"Greenland\", \"reading\": 54}") :: Maybe DataPoint
Just (DataPoint {placeName = "Greenland", readings = (54,20)})

我想用单独提供的值代替那个假人20。在Daniel Wagner’s answer之后,我可以将其提升为reader monad / environment functor (->) Int,就像

import Control.Applicative (liftA2)

instance FromJSON (Int -> DataPoint) where
    parseJSON = withObject "DataPoint" $ \o -> liftA2 DataPoint
        <$> fmap pure             (o .: "name")
        <*> fmap (\v c -> (v, c)) (o .: "reading")

在GHCi中,

> let f = (decodeThrow "{\"name\": \"Greenland\", \"reading\": 54}") :: Maybe (Int -> DataPoint)
> f <*> Just 7
Just (DataPoint {placeName = "Greenland", readings = (54,7)})

在这种情况下,liftA2的类型为

liftA2 :: Functor f => (a -> b -> c) -> f a -> f b -> f c

专门用于

liftA2 :: (Parser String -> Parser (Int, Int) -> Parser DataPoint)
    -> (Int -> Parser String)
    -> (Int -> Parser (Int, Int))
    -> (Int -> Parser DataPoint)

这是我的问题。我的真实记录有七个字段,因此我必须编写一个liftA7之类的函数,才能对我的真实数据使用相同的方法。这很容易,但是标准库仅提供liftAliftA2liftA3的事实使我认为我应该使用另一种方法将解析器放入{ {1}}函子。有没有更好的方法?还是我只需要写出(->) Int并使用它?

1 个答案:

答案 0 :(得分:4)

这里无需遍历嵌套上下文。类型arrange只是要求您编写一个返回函数的foo <- foo %>% arrange(ID) %>% group_by(Heading) %>% mutate(Count = seq_len(n()))

查看您的原始代码

parseJSON :: Parser (Int -> DataPoint)

首先,我注意到您在Parser调用下面使用了嵌套的parseJSON = withObject "DataPoint" $ \o -> DataPoint <$> o .: "name" <*> fmap (\v -> (v, 20)) (o .: "reading") 。我将重新关联此代码(此合法性为a consequence of the applicative laws)以简化您的代码:

fmap

我认为,这更容易阅读。我只是将两个参数的函数应用于上下文中的两个参数。

现在希望您可以看到如何使此解析器返回<*>-只需更改要映射到参数的函数的返回类型即可。

parseJSON = withObject "DataPoint" $ \o ->
    (\name reading -> DataPoint name (reading, 20))
    <$> o .: "name"
    <*> o .: "reading"

从语法上讲,Haskell允许您展平嵌套的lambda。

Int -> DataPoint

顺便说一句,这都等同于嘈杂但也许更清晰的parseJSON = withObject "DataPoint" $ \o -> (\name reading -> \x -> DataPoint name (reading, x)) <$> o .: "name" <*> o .: "reading" -表示法:

parseJSON = withObject "DataPoint" $ \o ->
    (\name reading x -> DataPoint name (reading, x))
    <$> o .: "name"
    <*> o .: "reading"

快速测试:

do