Samantha Frohlich
1
The Monadic Profunctor Paradigm of Bidirectional Programming
2
The Monadic Profunctor Paradigm of Bidirectional Programming
Li-yao Xia, Samantha Frohlich, Dominic Orchard & Meng Wang
Bidirectional Programming
Bidirectional Transformation (BX) =
a piece of code that can be run in two directions and is expected to satisfy some round-tripping properties
Advantages: saves time, saves implementation effort, and avoids sync issues
There
Back again
Round tripping
Bidirectional Programming
Biparsers
parse
"1"
1
"1"
1
Profunctors
class Profunctor p where
lmap :: (d → c) → p c a → p d a
rmap :: (a → b) → p c a → p c b
rmap = post-comp (a → a')
lmap = pre-comp (b → b')
b -> a
contravariant
covariant
Monadic
Profunctors
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
newtype Parser a
= Parser (String -> [(a,String}])
print :: a -> String
newtype Reader a b
= Reader { runReader :: a -> b }
newtype Writer a b
= Writer { runWriter :: (a, b)
Monadic
Profunctors
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
Example
letter :: Biparser Char Char
number :: Biparser Int Int
data LorN = Letter Char | Number Int
lOrN :: Biparser LorN LorN
lOrN = (lmap ?l? . prune) letter >>=
\l → return (Letter l))
<|> (lmap ?n? . prune) number >>=
\n → return (Number n))
Monadic
Profunctors
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
Example
letter :: Biparser Char Char
number :: Biparser Int Int
data LorN = Letter Char | Number Int
lOrN :: Biparser LorN LorN
lOrN = (letter >>=
\l -> return (Letter l))
<|> (number >>=
\n -> return (Number n))
(>>=) :: BP LorN Int -> (Int -> BP LorN LorN) -> BP LorN LorN
Monadic
Profunctors
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
Example
letter :: Biparser Char Char
number :: Biparser Int Int
data LorN = Letter Char | Number Int
lOrN :: Biparser LorN LorN
lOrN = (?l? letter >>=
\l -> return (Letter l))
<|> (?n? number >>=
\n -> return (Number n))
?l? :: Biparser Char Char -> Biparser LorN Char
?n? :: Biparser Int Int -> Biparser LorN Int
Monadic
Profunctors
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
Example
letter :: Biparser Char Char
number :: Biparser Int Int
data LorN = Letter Char | Number Int
lOrN :: Biparser LorN LorN
lOrN = (lmap ?l? letter >>=
\l -> return (Letter l))
<|> (lmap ?n? number >>=
\n -> return (Number n))
lmap :: (d -> c) -> p c a -> p d a
?l? :: LorN -> Char
?l? (Letter c) = c
?n? :: LorN -> Int
?n? (Number n) = n
Monadic
Profunctors
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
Example
letter :: Biparser Char Char
number :: Biparser Int Int
data LorN = Letter Char | Number Int
lOrN :: Biparser LorN LorN
lOrN = (lmap ?l? . prune) letter >>=
\l -> return (Letter l))
<|> (lmap ?n? . prune) number >>=
\n -> return (Number n))
prune :: p b a -> p (Maybe b) a
?l? :: LorN -> Maybe Char
?l? (Letter c) = Just c
?l? _ = Nothing
?n? :: LorN -> Maybe Int
?n? (Number n) = Just n
?n? _ = Nothing
14
Advantages of PBT:
15
Background: PBT + Generators
-- property based testing:
prop xs = reverse (reverse xs) == xs
-- unit testing:
test1 = reverse (reverse []) == []
test2 = reverse (reverse [1]) == [1]
test3 = reverse (reverse [1..10]) == [1..10]
16
6
/ \
4 8
/ \ / \
2 5 7 9
. . .
bstProp :: BST -> Bool
bstProp bst = prop bst
mostlyVacuousProp :: Tree -> Bool
mostlyVacuousProp t = isBST t => prop t
BST
Background: PBT + Generators
17
Background: PBT + Generators
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Gen Tree
bst (lo, hi) | lo > hi = return Leaf
bst (lo, hi) =
frequency
[ ( 1, return Leaf ),
( 5, do
x <- choose (lo, hi)
l <- bst (lo, x - 1)
r <- bst (x + 1, hi)
return (Node l x r) ) ]
Bigenerators
gen
check
6
/ \
4 8
True
6
/ \
4 8
Student Research Competition
Harrison Goldstein
Benjamin C. Pierce
20
Motivation: Internal Shrinking
Shrinking is useful.
We don’t want to manually shrink or write shrinkers.
MacIver and Donaldson [2020] show how to do shrinking with the help of a generator.
21
22
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
NPM Package
Analyzer
23
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
Oops, this breaks the code!
- Some User
24
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
25
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
26
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
"repository": {
"type": "a",
"url": "a"
},
"keywords": [],
"author": "a",
"license": "a",
"devDependencies": {},
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
27
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
"repository": {
"type": "a",
"url": "a"
},
"keywords": [],
"author": "a",
"license": "a",
"devDependencies": {},
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
28
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
"repository": {
"type": "a",
"url": "a"
},
"keywords": [],
"author": "a",
"license": "a",
"devDependencies": {},
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
29
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
(Remember, generators are parsers!)
30
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
Test-Case Reduction via Test-Case Generation
MacIver and Donaldson 2020
31
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
32
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
33
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
Student Research Competition
Harrison Goldstein
Benjamin C. Pierce
35
data Pick a where
Pick :: [(Weight, Choice, Freer Pick a)] -> Pick a
-- return :: a → p b a
-- (>>=) :: p b a → (a → p b b) → p b b
-- lmap :: (d → c) → p c a → p d a
-- prune :: p b a → p (Maybe b) a
type FreeGen a = Freer Pick a
FreeGen.hs
36
-- lmap :: (d → c) → p c a → p d a
-- prune :: p b a → p (Maybe b) a
type FreeGen a = Freer Pick a
FreeGen.hs
data Pick a where
Pick :: [(Weight, Choice, Freer Pick a)] -> Pick a
37
data Pick b a where
Pick :: [(Weight, Choice, Freer (Pick b) a)] -> Pick b a
-- lmap :: (d → c) → p c a → p d a
-- prune :: p b a → p (Maybe b) a
type FreeGen a = Freer Pick a
FreeGen.hs
38
data Pick b a where
Pick :: [(Weight, Choice, Freer (Pick b) a)] -> Pick b a
Lmap :: (c -> d) -> Pick d a -> Pick c a
Prune :: Pick b a -> Pick (Maybe b) a
type FreeGen a = Freer Pick a
FreeGen.hs
39
data R b a where
Pick :: [(Weight, Choice, Freer (R b) a)] -> R b a
Lmap :: (c -> d) -> R d a -> R c a
Prune :: R b a -> R (Maybe b) a
type Reflective b a = Freer (R b) a
Reflective.hs
40
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Gen Tree
bst (lo, hi) | lo > hi = return Leaf
bst (lo, hi) =
frequency
[ ( 1, return Leaf ),
( 5, do
x <- choose (lo, hi)
l <- bst (lo, x - 1)
r <- bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
41
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = return Leaf
bst (lo, hi) =
frequency
[ ( 1, return Leaf ),
( 5, do
x <- choose (lo, hi)
l <- bst (lo, x - 1)
r <- bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
42
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = return Leaf
bst (lo, hi) =
frequency
[ ( 1, return Leaf ),
( 5, do
x <- choose (lo, hi)
l <- bst (lo, x - 1)
r <- bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
43
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = TODO return Leaf
bst (lo, hi) =
frequency
[ ( 1, TODO return Leaf ),
( 5, do
x <- TODO choose (lo, hi)
l <- TODO bst (lo, x - 1)
r <- TODO bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
44
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = TODO return Leaf
bst (lo, hi) =
frequency
[ ( 1, TODO return Leaf ),
( 5, do
x <- TODO choose (lo, hi)
l <- TODO bst (lo, x - 1)
r <- TODO bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
45
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi
= (lmap (\ a' -> if a==a' then Just a else Nothing) . prune)
(return Leaf)
bst (lo, hi) =
frequency
[ ( 1, TODO return Leaf ),
( 5, do
x <- TODO choose (lo, hi)
l <- TODO bst (lo, x - 1)
r <- TODO bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
46
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = exact Leaf
bst (lo, hi) =
frequency
[ ( 1, exact Leaf ),
( 5, do
x <- TODO choose (lo, hi)
l <- TODO bst (lo, x - 1)
r <- TODO bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
47
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = exact Leaf
bst (lo, hi) =
frequency
[ ( 1, exact Leaf ),
( 5, do
x <- TODO choose (lo, hi)
l <- TODO bst (lo, x - 1)
r <- TODO bst (x + 1, hi)
return (Node l x r) ) ]
BST.hs
48
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = exact Leaf
bst (lo, hi) =
frequency
[ ( 1, exact Leaf ),
( 5, do
x <- (lmap (\Node _ n _ -> Just n) . prune) (choose (lo, hi))
l <- (lmap (\Node l' _ _ -> Just l) . prune) (bst (lo, x - 1))
r <- (lmap (\Node _ _ r' -> Just r) . prune) (bst (x + 1, hi))
return (Node l x r) ) ]
BST.hs
49
-- QuickCheck generator for BSTs:
bst :: (Int, Int) -> Reflective Tree Tree
bst (lo, hi) | lo > hi = exact Leaf
bst (lo, hi) =
frequency
[ ( 1, exact Leaf ),
( 5, do
x <- focus (_Node._2) (choose (lo, hi))
l <- focus (_Node._1) (bst (lo, x - 1))
r <- focus (_Node._3) (bst (x + 1, hi))
return (Node l x r) ) ]
BST.hs
50
GenInterp.hs
generate :: Reflective b a -> Gen a
generate = interp where
� interp (Return a) = return a
interp (Bind r f) = interpR r >>= interp . F
� interpR :: R b a -> Gen a
interpR (Pick gs) = QC.frequency [(w, interp g) | (w, _, g) <- rs]
interpR (Lmap _ r) = interpR r
interpR (Prune r) = interpR r
51
> QC.generate generate bst
(Node Leaf 1 Leaf)
> QC.generate generate bst
(Node (Node Leaf 5 Leaf) 6 Leaf)
> check bst ((Node Leaf 1 Leaf))
True
> check bst (Node (Node Leaf 6 Leaf) 5 Leaf)
False
52
53
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
54
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
55
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
010010101010
100101011101
101101110111…
56
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
010010101010
100101011101
101101110111…
57
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
010010101010
100101011101
101101110111…
58
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
"repository": {
"type": "git",
"url": "https://example.com"
},
"keywords": [
"reflective",
"generators"
],
"author": "test",
"license": "mit",
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"express": "^4.15.3",
"reflective": "^0.0.1"
}
}
010010101010
100101011101
101101110111…
59
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
60
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
61
{
"name": "a",
"description": "a",
"scripts": {
"start": "a",
"build": "a",
"test": "a"
},
…
"dependencies": {
"express": "^4.15.3"
}
}
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
010010101010
100101011101
101101110111…
101011011101…
62
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
{ 01 => 12%,
10 => 9%,
… }
{
"name": "reflective-generators",
"description": "What a great project",
"scripts": {
"start": "node ./src/server.js",
"build": "babel ./src -out-dir ./dist",
"test": "mocha ./test"
},
…
}
Inputs from Hell: Learning Input Distributions for Grammar-Based Test Generation
Soremekun et al. 2020
63
> complete (bst (1,10)) (Node Leaf undefined Leaf)
Just (Node Leaf 8 Leaf)
�> parse (bst (1,10)) ["leaf","leaf","leaf"]
[(Leaf,["leaf","leaf"])]
��> unparse (bst (1,10)) (Node Leaf 4 Leaf)
Just ["node","4","leaf","leaf"]
��> QC.generate $ weighted (bst (1,4)) True (\case {"leaf" -> 100; "node" -> 0; _ -> 0})
Node (Node Leaf 1 Leaf) 2 (Node Leaf 3 (Node Leaf 4 Leaf))
> enum (bst (1,10))
[Leaf,Node Leaf 1 Leaf,Node Leaf 2 Leaf,...]
Student Research Competition
Harrison Goldstein
Benjamin C. Pierce
65
~demo~
66
The Monadic Profunctor Paradigm of Bidirectional Programming
Li-yao Xia, Dominic Orchard, and Meng Wang. 2019. Composing bidirectional programs monadically. In European Symposium on Programming. Springer, 147–175. https://doi.org/10.1007/978-3-030-17184-1_6
Harrison Goldstein and Benjamin C. Pierce. 2022. Parsing Randomness. Proceedings of the ACM on Programming Languages 6,OOPSLA2(Oct.2022),128:89–128:113. https://doi.org/10.1145/3563291
Harrison Goldstein, Samantha Frohlich, Meng Wang, and Benjamin C. Pierce. 2023. Reflecting on Random Generation. Proc. ACM Program. Lang. 7, ICFP, Article 200 (August 2023), 34 pages. https://doi.org/10.1145/3607842
Koen Claessen and John Hughes. 2000. QuickCheck: a lightweight tool for random testing of Haskell programs. In Proceedings of the Fifth ACM SIGPLAN International Conference on Functional Programming (ICFP ’00), Montreal, Canada, September 18 21,2000, Martin Odersky and Philip Wadler(Eds.). ACM, Montreal, Canada, 268–279. https://doi.org/10.1145/351240.351266
Oleg Kiselyov and Hiromi Ishii. 2015. Freer monads, more extensible effects. ACM SIGPLAN Notices 50, 12 (2015), 94–105. https://dl.acm.org/doi/10.1145/2804302.2804319 Publisher: ACM New York, NY, USA.
David R. MacIver and Alastair F. Donaldson. 2020. Test-Case Reduction via Test-Case Generation: Insights from the Hypothesis Reducer (Tool Insights Paper). In 34th European Conference on Object-Oriented Programming (ECOOP 2020) (Leibniz International Proceedings in Informatics (LIPIcs), Vol. 166), Robert Hirschfeld and Tobias Pape (Eds.). Schloss Dagstuhl–Leibniz-Zentrum für Informatik, Dagstuhl, Germany, 13:1–13:27. https://doi.org/10.4230/LIPIcs.ECOOP.2020. 13 ISSN: 1868-8969.
Ezekiel Soremekun, Esteban Pavese, Nikolas Havrikov, Lars Grunske, and Andreas Zeller. 2020. Inputs from Hell: Learning Input Distributions for Grammar-Based Test Generation. IEEE Transactions on Software Engineering (2020). https://doi.org/10.1109/TSE.2020.3013716 Publisher: IEEE.
References
Round tripping
The Monadic Profunctor Paradigm of Bidirectional Programming
Li-yao Xia, Samantha Frohlich, Dominic Orchard & Meng Wang
Bidirectional Programming
Biparsers
Bigenerators
Partial Monadic Profunctors (PMPs)
Bidirectional Transformation (BX) = a piece of code that can be run in two directions and is expected to satisfy some round-tripping properties
Advantages: saves time, saves implementation effort, and avoids sync issues
There
Back again
parse
"1"
1
"1"
1
gen
check
1
True
1
data Biparser b a = Biparser
{ parse :: String → (a, String)
, print :: b → (a, String) }
class Profunctor p where
lmap :: (d → c) → p c a → p d a
rmap :: (a → b) → p c a → p c b
rmap = post-comp (a → a')
lmap = pre-comp (b → b')
b -> a
contravariant
covariant
Profunctors
PMP Biparser
Example
letter :: Biparser Char Char
number :: Biparser Int Int
data LorN = Letter Char | Number Int
lOrN :: Biparser LorN LorN
lOrN = (lmap ?l? . prune) letter >>=
\l → return (Letter l))
<|> (lmap ?n? . prune) number >>=
\n → return (Number n))
return :: a → p b a
(>>=) :: p b a → (a → p b b) → p b b
lmap :: (d → c) → p c a → p d a
rmap :: (a → b) → p c a → p c b
prune :: p b a → p (Maybe b) a
Operations