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 constraintson 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

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.

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

GDP

  • Ghost of Departed Proofs
    • Combinators for manipulating ghost proofs

To the code