Prism primer
ghci> :t _Right
_Right :: Prism (Either c a) (Either c b) a b
ghci> preview _Right (Right 42)
Just 42
ghci> preview _Right (Left "nope")
Nothing
ghci> review _Right 42
Right 42
ghci> preview (_Left . _Right) (Left (Right 42))
Just 42
Slide 6
Slide 6 text
ASN.1 and DER
BasicConstraints ::= SEQUENCE {
cA BOOLEAN DEFAULT FALSE,
pathLenConstraint INTEGER (0..MAX) OPTIONAL }
Parser
newtype Parser a = Parser
{ runParser :: String -> Maybe (a, String) }
char :: Parser Char
char = Parser $ \s -> case s of
"" -> Nothing
(c : s ) -> Just (c, s )
Slide 10
Slide 10 text
What if. . .
input type is not String?
element type is not Char?
Slide 11
Slide 11 text
Control.Lens.Cons
uncons
:: (Cons s s a a)
=> s -> Maybe (a, s) -- look familiar?
Slide 12
Slide 12 text
Control.Lens.Cons
class Cons s t a b where
_Cons :: Prism s t (a, s) (b, t)
instance Cons ByteString ByteString Word8 Word8
instance Cons Text Text Char Char
instance Cons [a] [b] a b
instance Cons (Vector a) (Vector b) a b
-- and many more!
Slide 13
Slide 13 text
Redefining the parser
type Parser s a = Prism s s (a, s) (a, s)
char :: (Cons s s a a) => Parser s a
char = _Cons
runParser :: Parser s a -> s -> Maybe (a, s)
runParser = preview
Slide 14
Slide 14 text
But if the parser is a Prism. . .
type Parser s a = Prism s s (a, s) (a, s)
char :: (Cons s s a a) => Parser s a
char = _Cons
runParser :: Parser s a -> s -> Maybe (a, s)
runParser = preview
runPrinter :: Parser s a -> (a, s) -> s
runPrinter = review
Slide 15
Slide 15 text
Parser ∧ printer → grammar
type Grammar s a = Prism s s (a, s) (a, s)
element :: (Cons s s a a) => Grammar s a
element = _Cons
runParser :: Grammar s a -> s -> Maybe (a, s)
runParser = preview
runPrinter :: Grammar s a -> (a, s) -> s
runPrinter = review
Slide 16
Slide 16 text
Convenience functions
parse :: Grammar s a -> s -> Maybe a
parse g s = fmap fst (preview g s)
print :: (Monoid s) => Grammar s a -> a -> s
print g a = review g (a, mempty)
Slide 17
Slide 17 text
Grammar - map
(<<$>>) :: Prism a a b b -> Grammar s a -> Grammar s b
p <<$>> g = g . swapped . aside p . swapped
swapped :: Iso (a, b) (c, d) (b, a) (d, c)
aside :: Prism s t a b -> Prism (e, s) (e, t) (e, a) (e, b)
Slide 18
Slide 18 text
Grammar - product
(<<*>>) :: Grammar s a -> Grammar s b -> Grammar s (a, b)
g1 <<*>> g2 = g1 . aside g2 . productIso
where
productIso = iso
(\(a, (b, s)) -> ((a, b), s))
(\((a, b), s) -> (a, (b, s)))
Slide 19
Slide 19 text
Grammar - sum
(<<+>>) :: Grammar s a -> Grammar s b -> Grammar s (Either a b)
g1 <<+>> g2 = prism
(\(x, s) -> either (review g1 . (,s)) (review g2 . (,s)) x)
(\s -> first Left <$> preview g1 s
<|> first Right <$> preview g2 s)
Slide 20
Slide 20 text
Grammar - list
many :: Grammar s a -> Grammar s [a]
many g = isoList <<$>> (g <<*>> many g) <<+>> success ()
isoList :: Iso (Either (a, [a]) ()) [a]
isoList = ...
-- like pure :: Applicative f => a -> f a
success :: a -> Grammar s a
success a = prism snd (Just . (a,))
Slide 21
Slide 21 text
Grammar - basic grammars
satisfy :: (Cons s s a a) => (a -> Bool) -> Grammar s a
satisfy f = prism id (\a -> guard (f a) >> pure a) <<$>> element
symbol :: (Cons s s a a, Eq a) => a -> Grammar s a
symbol a = satisfy (== a)
eof :: (Cons s s a a) => Grammar s ()
eof = prism snd
(\s -> maybe (Just ((), s)) (const Nothing) (uncons s))
Slide 22
Slide 22 text
Grammar - delimiters
literal :: (Cons s s a a, Eq a) => a -> Grammar s ()
match :: Grammar s a -> a -> Grammar s ()
between :: Grammar s () -> Grammar s () -> Grammar s a -> Grammar s a
Slide 23
Slide 23 text
Grammar - combinators
(<<*) :: Grammar s a -> Grammar s () -> Grammar s a
(*>>) :: Grammar s () -> Grammar s a -> Grammar s a
many1 :: Grammar s a -> Grammar s (NonEmpty a)
replicate :: Natural -> Grammar s a -> Grammar s [a]
Slide 24
Slide 24 text
Grammar - bind
bind :: Grammar s a -> (a -> Grammar s b) -> (b -> a) -> Grammar s b
bind p f g = prism
(\(b, s) -> review p (g b, review (f (g b)) (b, s)))
(preview p >=> \(a, s ) -> preview (f a) s )
Slide 25
Slide 25 text
Grammar - bind
ghci> let g = bind integral (\n -> replicate n alpha)) length
ghci> parse (many g) "1a2ab3abc4bad!"
Just ["a","ab","abc"]
ghci> print (many g) ["hello", "world"] :: String
"5hello5world"
Slide 26
Slide 26 text
Deriving Isos for custom types
{-# LANGUAGE TemplateHaskell #-}
import Data.Fresnel.TH (makeIso)
data Foo = A Int Char | B Bool
makeIso Foo
-- results in splice --
_Foo :: Iso (Either (Int, Char) Bool) Foo
_Foo = ...
Slide 27
Slide 27 text
Putting it all together
data PhoneNumber = PhoneNumber
{ areaCode :: String
, localNumber :: String
} deriving (Show)
makeIso PhoneNumber
phoneNumber :: Cons s s Char Char => Grammar s PhoneNumber
phoneNumber = _PhoneNumber <<$>>
between (literal ( ) (literal ) ) (replicate 2 digit)
<<* match (many space) " "
<<*>> replicate 8 (match (many space) "" *>> digit)
Slide 28
Slide 28 text
Putting it all together
ghci> parse phoneNumber ("(07)3456 78 9 0" :: String)
Just (PhoneNumber "07" "34567890")
ghci> print phoneNumber (PhoneNumber "07" "34567890") :: String
"(07) 34567890"
Slide 29
Slide 29 text
ASN.1 grammars
Slide 30
Slide 30 text
Primitive types
boolean :: (Cons s s ASN1 ASN1) => Grammar s Bool
integer :: (Cons s s ASN1 ASN1) => Grammar s Integer
octetString :: (Cons s s ASN1 ASN1) => Grammar s B.ByteString
oid :: (Cons s s ASN1 ASN1) => Grammar s OID
Slide 31
Slide 31 text
OPTIONAL, DEFAULT and SEQUENCE
opt :: Grammar s a -> Grammar s (Maybe a)
def :: (Eq a) => a -> Grammar s a -> Grammar s a
sequence :: (Cons s s ASN1 ASN1) => Grammar s a -> Grammar s a
Slide 32
Slide 32 text
ASN.1 grammar - example
data BasicConstraints = NotCA | CA (Maybe Natural)
_BasicConstraints :: Iso (Bool, Maybe Natural) BasicConstraints
_BasicConstraints = ...
basicConstraints :: (Cons s s ASN1 ASN1) => Grammar s BasicConstraints
basicConstraints = _BasicConstraints <<$>> sequence
( def False boolean
<<*>> opt (natural <<$>> integer) )
How did I get here?
1. Evaluate existing solutions
Slide 37
Slide 37 text
How did I get here?
1. Evaluate existing solutions
2. Write Cons-based parser (to parse [ASN1])
Slide 38
Slide 38 text
How did I get here?
1. Evaluate existing solutions
2. Write Cons-based parser (to parse [ASN1])
3. Hmm. . . _Cons is a prism. . .
Slide 39
Slide 39 text
How did I get here?
1. Evaluate existing solutions
2. Write Cons-based parser (to parse [ASN1])
3. Hmm. . . _Cons is a prism. . .
4. Type tetris
Slide 40
Slide 40 text
How did I get here?
1. Evaluate existing solutions
2. Write Cons-based parser (to parse [ASN1])
3. Hmm. . . _Cons is a prism. . .
4. Type tetris
5. fresnel is born
Slide 41
Slide 41 text
fresnel and fresnel-asn1
Repos
https://github.com/frasertweedale/hs-fresnel
https://github.com/frasertweedale/hs-fresnel-asn1
AGPLv3+
I have actually used it!
Slide 42
Slide 42 text
Limitations and caveats
Error reporting
Performance
Slide 43
Slide 43 text
Advantages
One module; many stream and element types
Don’t repeat yourself
Reuse existing optics
ASN.1 grammar corresponds to spec
Slide 44
Slide 44 text
What’s next?
Hackage
Kerberos, X.509
Implement binary DER decoding?
Slide 45
Slide 45 text
Resources and related topics
Invertible Syntax Descriptions
Paper: www.informatik.uni-marburg.de/~rendel/unparse/
Libraries: boomerang, roundtrip, invertible-syntax
Christian Marie’s YLJ2015 talk
Experiment in combining Applicative and Divisible
https://github.com/charleso/portmanteau
lens