【问题标题】:Invertible State monad (and parsers)可逆状态单子(和解析器)
【发布时间】:2013-11-16 23:09:46
【问题描述】:

女士们,先生们,美好的一天!

我一直在编写解析器和编解码器。实现解析器和打印机似乎是大量的代码重复。我想知道是否可以反转有状态计算,因为它本质上是同构的。

可以反转纯函数组合(Control.Lens.Iso 通过在同构上定义组合运算符来实现)。可以看出,

Iso bc cb . Iso ab ba = Iso (bc . ab) (ba . cb) -- from Lenses talk
invert (f . g) = (invert g) . (invert f)        -- pseudo-code

换句话说,要反转函数组合,应该以相反的顺序组合反转函数。因此,如果定义了所有原始同构对,则可以将它们组合成更复杂的对,而无需重复代码。下面是一个纯双向计算的例子(使用 Control.Lens,explanatory video 可以帮助您了解 Lenses、Folds 和 Traversals 的大致概念):

import Control.Lens

tick :: Num a => Iso' a a
tick = iso (+1) (subtract 1)   -- define an isomorphic pair

double :: Num a => Iso' a a
double = iso (+2) (subtract 2) -- and another one

threeTick :: Num a => Iso' a a
-- These are composed via simple function composition!
threeTick = double . tick

main :: IO ()
main = do
        print $ (4 :: Int)^.tick           -- => 5
        print $ (4 :: Int)^.from tick      -- => 3

        print $ (4 :: Int)^.threeTick      -- => 7, Composable
        print $ (4 :: Int)^.from threeTick -- => 1, YEAH

如您所见,我不需要提供threeTick 的倒置版本;自动逆向合成获得!

现在,让我们考虑一个简单的解析器。

data FOO = FOO Int Int deriving Show

parseFoo :: Parser FOO
parseFoo = FOO <$> decimal <* char ' '
               <*> decimal

parseFoo' :: Parser FOO
parseFoo' = do
    first <- decimal
    void $ char ' '
    second <- decimal
    return $ FOO first second


printFoo :: FOO -> BS.ByteString
printFoo (FOO a b) = BS.pack(show a) <>
                     BS.pack(" ")    <>
                     BS.pack(show b)


main :: IO ()
main = do
        print $ parseOnly parseFoo "10 11"  -- => Right (FOO 10 11)
        print $ parseOnly parseFoo' "10 11" -- => Right (FOO 10 11)

        print . printFoo $ FOO 10 11        -- => "10 11"
        print . parseOnly parseFoo . printFoo $ FOO 10 11 -- id

您可以看到parseFoo 的两个版本都具有相当的声明性(感谢解析器组合器)。注意parseFooprintFoo 之间的相似性。我可以在原始解析器(decimalchar)上定义同构,然后自动派生打印机(printFoo :: FOO -&gt; String)吗?理想情况下,解析器组合器也可以工作。

我尝试重新定义一元 &gt;&gt;= 运算符以提供反转语义,但我没有这样做。我觉得可以定义反转的 Kleisli 组合运算符(单子函数组合),类似于组合反转,但是可以将它与普通的 monad 一起使用吗?

f :: a -> m b,     inverse f :: b -> m a
g :: b -> m c,     inverse g :: c -> m b
inverse (f >=> g) = (inverse f) <=< (inverse g)

为什么inverse f 的类型是b -&gt; m a 而不是m b -&gt; a?答案是:一元副作用是箭头的属性,而不是数据类型b 的属性。状态单子二元化在伟大的专家对专家video 中进一步讨论。

如果解决方案确实存在,您能否提供一个printFoo 派生的工作示例?顺便说一句,这里有一个有趣的paper,可以帮助我们找到解决方案。

【问题讨论】:

  • 我可能错了,但我认为对这类事情的研究导致了 Control.Lens 诞生的早期镜头概念。特别是,有很多关于双向计算(有时是“BX”)的论文,包括 Pierce 的一篇,我一直引用它来定义透镜定律。
  • @J.Abrahamson 我相信已经具备足够强大的双向计算理论背景。但是,它有实际用途吗?有人与解析器结婚lenses(MonadState 实例,更普遍)?我认为这是可能的,但超出了我目前的能力。
  • 您可能想查看 XML 生态系统中的 PU 解析器(所有主要的 XML 生态系统都有它们)。它们提供了一种同时描述打印和解析的机制,并且很容易通过编写一些由Isos 和Prisms 构建的操作来增强它们。然而,我个人仍然觉得它们使用起来很糟糕,因为它们没有 Alternative 解析器那样的结构。
  • s/先生们/女士们先生们/

标签: parsing haskell state


【解决方案1】:

您可能有兴趣深入了解lens 包中的Prism 概念。

Prism 可以用作“智能构造函数”来无条件地构建一些东西(例如一个漂亮的打印字符串),并匹配它(例如@987654326 @)。

您必须忽略这些定律,或者将这些定律视为仅支持一个商,因为您从漂亮打印中得到的字符串很可能不是 完全您解析的字符串。

【讨论】:

  • 谢谢。我正在尝试了解棱镜的概念(that 帖子特别有用)。但无论如何,你最了解lens 生态系统,所以你能否提供一个printFoo 派生的“hello world”示例?如我所见,从hackage,每个Iso 都是Prism,但未能使用前者。如果没有,您能否估计一下协调使用 Prisms 和现有解析器的可能性(或者我应该重写 Attoparsec 来做到这一点)。
猜你喜欢
  • 2010-10-14
  • 2019-01-28
  • 2019-12-13
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 2015-12-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多