Good day, ladies and gentlemen!
I constantly write parsers and codecs. The implementation of both parsers and printers seems like massive code duplication. I wonder if state computation can be inverted since it is isomorphic in nature.
You can invert the composition of a pure function (Control.Lens.Iso did this by defining a composition operator over isomorphisms). As you can see
Iso bc cb . Iso ab ba = Iso (bc . ab) (ba . cb) -- from Lenses talk invert (f . g) = (invert g) . (invert f) -- pseudo-code
In other words, to invert the composition of a function, you need to compose the inverted functions in the reverse order. Thus, given that all primitive isomorphic pairs are defined, we can compose them to obtain more complex pairs without duplicating code. The following is an example of a pure bi-directional calculation (using Control.Lens, an
explanatory video can help you get an overview of lenses, Folds and Traversals):
import Control.Lens tick :: Num a => Iso' aa tick = iso (+1) (subtract 1) -- define an isomorphic pair double :: Num a => Iso' aa double = iso (+2) (subtract 2) -- and another one threeTick :: Num a => Iso' aa -- 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
As you can see, I did not need to put an inverted version of threeTick ; it is obtained by reverse composition automatically!
Now consider a simple parser.
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 ab) = BS.pack(show a) <> BS.pack(" ") <> BS.pack(show b) main :: IO () main = do print $ parseOnly parseFoo "10 11"
You can see that both versions of parseFoo quite declarative (thanks to combinatorial analyzers). Note the similarities between parseFoo and printFoo . Can I detect isomorphisms over primitive parsers ( decimal and char ) and then just print the printer ( printFoo :: FOO -> String ) automatically? Ideally, parser combinators will also work.
I tried to override the monadic operator >>= to provide inverted semantics, but I did not. I feel that it is possible to define an inverted Claysley composition operator (composition of a monadic function) similar to composition inversion, but can it be used with ordinary monads?
f :: a -> mb, inverse f :: b -> ma g :: b -> mc, inverse g :: c -> mb inverse (f >=> g) = (inverse f) <=< (inverse g)
Why is inverse f type b -> ma and not mb -> a ? Answer: monadic side effect is an arrow attribute, not data type b . The dualization of the state monad is further discussed in a large video expert.
If there is a solution could you provide a working example of printFoo output? By the way, here is an interesting one that can help us find a solution.