gpt4 book ai didi

haskell - 将大型记录类型的解析器提升为仿函数

转载 作者:行者123 更新时间:2023-12-01 23:27:21 25 4
gpt4 key购买 nike

这是 this question 的后续问题关于在 monadic 上下文中解析 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 的事实让我觉得我应该使用将我的解析器提升到 (->) Int 仿函数的不同方法。有更好的方法吗?还是我只需要写出 liftA7 并使用它?

最佳答案

无需在此处解除嵌套上下文。类型parseJSON :: Parser (Int -> DataPoint)只是要求你写一个 Parser它返回一个函数。

查看您的原始代码,

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

我首先注意到您使用的是嵌套的 fmap<*> 下方称呼。我将重新关联它(它的合法性是 a consequence of the applicative laws )以简化您的代码:

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

在我看来,这整体上更容易阅读。我只是将双参数函数应用于上下文中的两个参数。

现在希望你能看到如何让这个解析器返回一个Int -> DataPoint。 - 只需更改您在参数上映射的函数的返回类型。

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

Haskell 允许您将嵌套的 lambda 扁平化。

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

顺便说一句,这都等同于嘈杂但也许更清晰的 do -符号:

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

快速测试:

main = 
let input = "{\"name\": \"Greenland\", \"reading\": 54}"
f = decode input :: Maybe (Int -> DataPoint)
in print $ fmap ($ 7) f
-- Just (DataPoint {placeName = "Greenland", readings = (7,54)})

关于haskell - 将大型记录类型的解析器提升为仿函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54949898/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com