Refinement
Outline
- Goals
- Problems
- Runtime Verification
- PyContracts
- Paranoid Scientist
- Refinement Types
- Liquid Haskell
- Ghosts of Departed Proofs
Goal
Goal
- Correctness
- Safety
- Readability
- Interactivity
- Simple
- Easy to use
Problematic Code
Contrived Python Example
def f(u, d, t, m):
return do_something(u, d, t, m, o)
def f(u, d, t, m):
return do_something(u, d, t, m, o)
- What do we know about f ?
- What does f do?
- Where/How can we use f ?
Contrived Python Example
def send_default_email(user, domain, topic, msg):
return send_email(user,
domain,
topic,
msg,
default_options)
- What do we know about f ?
- What does f do?
- Where/How can we use f ?
Improved Python Example
def send_default_email(user: str, domain: str, topic: str, msg: str) -> str:
"""
Sends an email to 'user@domain'
>>> response = send_default_email('joe', 'computer.com', 'ur PR', 'lgtm')
>>> print(response)
>>> 'Email successful'
:param user: ...
:param domain: ...
:param msg: ...
:return: ...
"""
return send_email(user, domain, topic, msg, default_options)
Improved Python Example
tv_members = ['heneli']
responses = ['Success', 'Fail']
@precondition("user in tv_members")
@precondition("domain == tv.com")
@return(resp: str in responses)
def send_tv_email(user: str, domain: str, topic: str, msg: str) -> str:
"""
Sends an email to 'user@domain'
>>> response = send_tv_email('bob', 'tv.com', 'ur PR', 'lgtm')
*** Exception: User not in tv_members
>>> response = send_tv_email('heneli', 'tv.com', 'ur PR', 'lgtm')
'Success'
:param user: ...
:param domain: ...
:param msg: ...
:return: ...
"""
return send_email(user, domain, topic, msg, default_options)
"Refined" Python Example
Problematic Code
Example: Endpoint of a NonEmpty List
- Say you have a list you know is non-empty
- Ex. myList = [1,2,3,4,5]
head' :: [a] -> a
head' = \case
(x:_) -> x
[] -> error "empty list!"
endpts :: [Int] -> (Int, Int)
endpts [] = error "empty list!"
endpts xs = (head' xs, head' $ reverse xs)
endptsEx :: IO (Int, Int)
endptsEx = do
putStrLn "Enter a non-empty list of integers:"
xs <- readLn :: IO [Int]
if xs /= []
then pure (head' xs, head' $ reverse xs)
else endptsEx
Run-time failure on bad inputs
Returning a dummy value
* Everything can now be null or None
* All functions are responsible for checking for dummy value
headMay :: [a] -> Maybe a
headMay xs =
case xs of
(x:_) -> Just x
[] -> Nothing
safeEndpts :: [Int] -> Maybe (Int, Int)
safeEndpts xs =
case headMay xs of
Nothing -> Nothing
Just front ->
case reverse $ headMay xs of
Nothing -> Nothing
Just back -> Just (front, back)
Use an Optional Value
headMay :: [a] -> Maybe a
headMay xs =
case xs of
(x:_) -> Just x
[] -> Nothing
safeEndpts :: [Int] -> Maybe (Int, Int)
safeEndpts xs =
case headMay xs of
Nothing -> Nothing
Just x -> Just
( fromJust $ headMay xs
, fromJust . headMay $ reverse xs
)
Optional + Unsafe
* We already know the list is nonempty since headMay xs succeeded
Contrived Haskell Example
fn :: String -> String -> String -> String -> IO String
fn u d t m = fn2 u d t m o
sendTVEmail :: String -> String -> String -> String -> IO String
sendTVEmail u d t m = sendEmail u d t m o
Improved Example
newtype EmailUser = EmailUser String
newtype EmailDomain = EmailDomain String
newtype EmailTopic = EmailTopic String
newtype EmailMessage = EmailMessage String
newtype EmailResponse = EmailResponse String
sendTVEmail :: EmailUser -> EmailDomain -> EmailTopic
-> EmailMessage -> IO EmailResponse
sendTVEmail usr domain topic msg =
sendEmail usr domain topic msg defaultOptions
Best Practice
nonEmptyUpToNPrintableAscii :: Int -> Gen Text
nonEmptyUpToNPrintableAscii n =
map pack . resize n
. listOf1
. suchThat arbitrary
$ ((&&) <$> isPrint <*> isAscii)
upToNPrintableAscii :: Int -> Gen Text
upToNPrintableAscii n =
map pack . resize n
. listOf
. suchThat arbitrary
$ ((&&) <$> isPrint <*> isAscii)
* Newtypes over base types (ex. Text)
* Ample documentation
* Manual checks where used or unsafe
* Tests that generate arbitrary values with some constraints
Runtime Verification
PyContracts
PyContracts
-
Define a “contract” using the @contract decorator:
from contracts import contract
@contract(x='int,>=0')
def f(x):
pass
>>> f(-2)
ContractNotRespected: Breach for argument 'x' to f().
Condition -2 >= 0 not respected
checking: >=0 for value: Instance of int: -2
checking: int,>=0 for value: Instance of int: -2
-
Define a “contract” using the @contract decorator:
-
Constraints on input
PyContracts
-
Use the “returns” keyword for constraints on the return value.
from contracts import contract
@contract(returns='int,>=0')
def f(x):
return x
3 ways to add Contracts
-
Decorator argument
@contract
def f(x):
"""
Function description.
:type x: int,>0
:rtype: <=1
"""
@contract
def f(x:'int,>=0') -> '>=1':
...
@contract(x='int,>=0', returns='>=1')
def f(x):
...
-
Docstring
-
Python 3 Annotations
Paranoid Scientist
Paranoid Scientist
- Can create you own types
- Verify arbitrary entry and exit conditions
- Uses decorator notation
- Toggle it on and off
- Automated testing
To the picture...
Liquid Haskell
Types + Predicates
{-@ head :: { xs : [a] | 1 <= len xs } -> a @-}
head :: [a] -> a
head (x:_) = x
{-@ abs :: Int -> { n : Int | 0 <= n } @-}
abs :: Int -> Int
abs x = if x < 0 then 0 - x else x
To the tutorial...
Ghosts of Departed Proofs
GDP
-
Ghost Variable
- Assignable variables that appear in program annotations but do not correspond to values
- Don't appear at term level
- They are used to facilitate specification and verification, e.g., by using a ghost variable to count the number of iterations of a loop, and also to express extra-functional behaviours
- Assignable variables that appear in program annotations but do not correspond to values
GDP
-
Ghost of Departed Proofs
- Properties and proofs are represented in code
- Proofs carried by phantom type parameters
- Library-controlled APIs to create proofs
- Combinators for manipulating ghost proofs
GDP
-
Ghost of Departed Proofs
- Properties and proofs are represented in code
- Proofs carried by phantom type parameters
- Library-controlled APIs to create proofs
- the library author should be the only one able to introduce new axioms about the behavior of their API
-
Combinators for manipulating ghost proofs
- Attach ghost proofs as phantom variables to newtypes
- "Name" the ghost proof in library code
GDP
-
Ghost of Departed Proofs
-
Properties and proofs are represented in code
- Curry-Howard correspondence, propositions are represented by types, and the proof of a proposition will be a value of that type.
-
Properties and proofs are represented in code
GDP
-
Ghost of Departed Proofs
- Proofs carried by phantom type parameters
- To ensure that proof-carrying code does not have a run-time cost, proofs will only be used to inhabit types that appear as phantom type variables attached to newtype wrappers.
- Attach ghost proofs as phantom variables to newtypes
- Proofs carried by phantom type parameters
GDP
-
Ghost of Departed Proofs
- Combinators for manipulating ghost proofs
To the code
Refinement Types(-ish)
By Heneli Kailahi
Refinement Types(-ish)
- 297