Liquid Haskell:
изящные типы
Денис Шевченко, #fpconf, 03/12/2016
всесильные типы?

average :: Foldable t => t Int -> Int
average ns = sum ns `div` length ns
                    

checkProtocol :: [IP] -> Bool
checkProtocol = isIPv4 . head
                    

getUserName :: Home -> Text
getUserName homeDir = parts !! 1
  where
    parts = splitOn "/" homeDir
                    
факт |
сильная типизация —
не панацея
цель |
усовершенствовать
наши типы
LiquidHaskell
проект UC San Diego  |  с 2008 года
lightbulb_outline
|  идея
v:Int
v:Int
-9223372036854775808 … 9223372036854775807
{v:Int | v>0}
type Pos = {v:Int | v>0}
type Pos = {v:Int | v>0}
Liquid Base + Refinement
Liquid?
Logically Qualified Data Type
Logically Qualified Data Type
computer
|  приготовимся

$ stack install liquidhaskell
                    

$ apt-get install z3
                    

$ stack exec -- liquid App.hs 
                    
7.*
8.*
school
|  примеры

module Liquid.Spec.Port where

{-@ type NotRootPort =
           {p:Int | 1024 <= p
                    && p <= 65535}
@-}
                    

import Liquid.Spec.Port ()

{-@ localPort :: NotRootPort @-}
localPort :: Int
localPort = 3010
                    
**** RESULT: SAFE ****

import Liquid.Spec.Port ()

{-@ localPort :: NotRootPort @-}
localPort :: Int
localPort = 22
                    
**** RESULT: UNSAFE ****

module Liquid.Spec.Number where

{-@ type NotZero = {n:Int | n != 0}
@-}
                    

{-@ okDiv :: Int -> NotZero -> Int @-}
okDiv :: Int -> Int -> Int
okDiv = div
                    

main :: IO ()
main = (readLn :: IO Int)
  >>= print . okDiv 512
                    
**** RESULT: UNSAFE ****

main :: IO ()
main = (readLn :: IO Int)
  >>= divideIt
 where
  divideIt 0 = putStrLn "Go away!"
  divideIt v = print $ 512 `okDiv` v
                    
**** RESULT: SAFE ****
UNSAFE — скорее всего, плохой…
SAFE — точно хороший!

servicePorts :: [Port]
servicePorts =
  [3000, 3001, 3010, 3011, 3012, 3013
   3013, 3014, 3020, 3030, 3040, 3050]
                    

module Liquid.Spec.List where

import Data.Set

{-@ type ListUnique a =
           {v:[a] | NoDups v}
@-}
                    

{-@ predicate NoDups L =
                Set_emp (dups L)
@-}
                    

{-@ measure dups :: [a] -> (Set a)
    dups ([])   = {v | Set_emp v}
    dups (x:xs) = {v | v =
      if (Set_mem x (listElts xs))
      then (Set_cup (Set_sng x) (dups xs))
      else (dups xs)}
@-}
                    

{-@ servicePorts ::
    ListUnique NotRootPort @-}
servicePorts :: [Port]
servicePorts =
  [3000, 3001, 3010, 3011, 3012, 3013
   3013, 3014, 3020, 3030, 3040, 3050]
                    
**** RESULT: UNSAFE ****
money_off
|  подарки
Сообщения об ошибках

main :: IO ()
main = print . f $ "O RLY?"
                    

[1 of 1] Compiling Main ...

~/src/Main.hs:8:16: Not in scope: ‘f’
                    
ghc

 src/Main.hs:8:16: Error: GHC Error
  
   8 | main = print . f $ "O RLY?"
                      ^
                        
       Not in scope: ‘f’
                    
liquid
Тотальность функций

$ liquid --totality App.hs 
                    

data Currency = USD | RUB | GBP | EUR

display :: Currency -> Text
display USD = "United States Dollar"
display RUB = "Russia Ruble"
display GBP = "United Kingdom Pound"
                    
**** RESULT: UNSAFE ****
Ошибки типов

import Liquid.Spec.Port ()

{-@ localPort :: NotRootPort @-}
localPort :: Int
localPort = 22
                    
looks_one
мы можем сделать
наши проекты ещё надёжнее
looks_two
это несложно
благодарю!
здесь слайды
bit.do/fpconf2016
а здесь я
dshevchenko.biz