美好的一天,女士们,先生们!
我经常编写解析器和编解码器。实现解析器和打印机似乎是大量的代码重复。我想知道是否可以反转有状态计算,因为它本质上是同构的。
可以反转纯函数组合(Control.Lens。也可以通过在同构上定义组合运算符来实现)。可以观察到,
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可以帮助您获得镜头,折叠和遍历的一般概念):
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
的两个版本都是相当声明的(感谢解析器组合器)。请注意parseFoo
和printFoo
之间的相似性。我可以在原始解析器(decimal
和char
)上定义同构,然后自动派生打印机(printFoo :: FOO -> String
)吗?理想情况下,解析器组合器也可以正常工作。
我尝试重新定义一个monadic >>=
运算符以提供反向语义,但我没有这样做。我觉得可以用组合反演来定义反向Kleisli合成算子(monadic function composition),但是可以将它与普通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 -> m a
而非m b -> a
?答案是:monadic副作用是箭头的属性,而不是数据类型b
的属性。状态monad二元化在伟大的专家专家video中进一步讨论。
如果解决方案存在,请提供printFoo
推导的实际示例吗?顺便说一句,这是一个有趣的paper,可以帮助我们找到解决方案。
答案 0 :(得分:4)
您可能有兴趣深入了解lens
Prism
包的概念{/ 1}}。
Prism
可以用作“智能构造函数”来无条件地构建内容(例如一个漂亮的打印字符串),并在其上匹配(例如 parse
)。
你必须忽略这些法律,或者将法律视为只保留一个商,因为你从漂亮的打印中获得的字符串很可能不是完全你解析的字符串。