如何处理 Haskell 中的深堆栈仿函数?
How do I get a handle on deep stacks of functors in Haskell?
时不时地,我发现自己映射到一大堆函子上,例如一些可选值集合的解析器:
-- parse a rectangular block of characters to a map of
-- coordinate to the character, or Nothing for whitespace
parseRectangle :: Parser (Map (Int, Int) (Maybe Char))
data Class = Letter | Digit | Other
classify :: Char -> Class
parseClassifiedRectangle :: Parser (Map (Int, Int) (Maybe Class))
parseClassifiedRectangle = fmap (fmap (fmap classify)) parseRectangle
嵌套 fmap
有哪些好的方法?通常它不像这里那么清楚,我最终会添加 fmap
s 直到代码类型检查。简单的代码最终变成一堆乱七八糟的fmap
样板,我真正想表达的是"lift this function to the appropriate depth and apply it to the contained type".
一些想法,none 目前我觉得特别满意:
- 定义
fmap2 :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)
和朋友
- 定义具体的助手,比如
mapMaybeMap :: (a -> b) -> Map k (Maybe a) -> Map k (Maybe b)
- 为仿函数堆栈引入
newtype
包装器,并制作 Functor
的实例,例如 newtype MaybeMapParser a = Parser (Map (Int, Int) (Maybe a))
其他人 运行 在大型代码库中遇到过这个问题吗?这甚至是一个问题吗?你是怎么处理的?
让我打破这个人们似乎羞于回答的有趣问题的僵局。这个问题可能更多地归结为风格问题而不是任何问题,因此缺乏答案。
我的方法如下:
parseClassifiedRectangle :: Parser (Map (Int, Int) (Maybe Class))
parseClassifiedRectangle = doClassify <$> parseRectangle
where
doClassify = Map.map (fmap classify)
我尝试将 <$>
用于顶层函子,而将 fmap
用于内部函子;尽管这在实践中并不总是很有效。
我使用了本地命名绑定。但是,即使将 doClassify
保留为 f
,它有时也有助于阐明所发生情况的高级视图:"on the parsed value we are doing a thing, see below for what thing does." 我不知道进行绑定的效率问题是什么。
我还使用了 fmap
的特定实例作为 Map 实例。这有助于我在堆栈中定位并为最终 fmap
提供路标。
希望对您有所帮助。
时不时地,我发现自己映射到一大堆函子上,例如一些可选值集合的解析器:
-- parse a rectangular block of characters to a map of
-- coordinate to the character, or Nothing for whitespace
parseRectangle :: Parser (Map (Int, Int) (Maybe Char))
data Class = Letter | Digit | Other
classify :: Char -> Class
parseClassifiedRectangle :: Parser (Map (Int, Int) (Maybe Class))
parseClassifiedRectangle = fmap (fmap (fmap classify)) parseRectangle
嵌套 fmap
有哪些好的方法?通常它不像这里那么清楚,我最终会添加 fmap
s 直到代码类型检查。简单的代码最终变成一堆乱七八糟的fmap
样板,我真正想表达的是"lift this function to the appropriate depth and apply it to the contained type".
一些想法,none 目前我觉得特别满意:
- 定义
fmap2 :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)
和朋友 - 定义具体的助手,比如
mapMaybeMap :: (a -> b) -> Map k (Maybe a) -> Map k (Maybe b)
- 为仿函数堆栈引入
newtype
包装器,并制作Functor
的实例,例如newtype MaybeMapParser a = Parser (Map (Int, Int) (Maybe a))
其他人 运行 在大型代码库中遇到过这个问题吗?这甚至是一个问题吗?你是怎么处理的?
让我打破这个人们似乎羞于回答的有趣问题的僵局。这个问题可能更多地归结为风格问题而不是任何问题,因此缺乏答案。
我的方法如下:
parseClassifiedRectangle :: Parser (Map (Int, Int) (Maybe Class))
parseClassifiedRectangle = doClassify <$> parseRectangle
where
doClassify = Map.map (fmap classify)
我尝试将 <$>
用于顶层函子,而将 fmap
用于内部函子;尽管这在实践中并不总是很有效。
我使用了本地命名绑定。但是,即使将 doClassify
保留为 f
,它有时也有助于阐明所发生情况的高级视图:"on the parsed value we are doing a thing, see below for what thing does." 我不知道进行绑定的效率问题是什么。
我还使用了 fmap
的特定实例作为 Map 实例。这有助于我在堆栈中定位并为最终 fmap
提供路标。
希望对您有所帮助。