disco-0.1.0.0: src/Disco/Types/Rules.hs
-----------------------------------------------------------------------------
-- |
-- Module : Disco.Types.Rules
-- Copyright : disco team and contributors
-- Maintainer : byorgey@gmail.com
--
-- "Disco.Types.Rules" defines some generic rules about arity,
-- subtyping, and sorts for disco base types.
--
-----------------------------------------------------------------------------
-- SPDX-License-Identifier: BSD-3-Clause
module Disco.Types.Rules
( -- * Arity
Variance(..), arity
-- * Qualifiers
, Qualifier(..), bopQual
-- * Sorts
, Sort, topSort
-- * Subtyping rules
, Dir(..), other
, isSubA, isSubB, isDirB
, supertypes, subtypes, dirtypes
-- * Qualifier and sort rules
, hasQual, hasSort
, qualRules, sortRules
, pickSortBaseTy
)
where
import Control.Monad ((>=>))
import Data.List (foldl')
import Data.Map (Map)
import qualified Data.Map as M
import qualified Data.Set as S
import Disco.Types
import Disco.Types.Qualifiers
------------------------------------------------------------
-- Arity
------------------------------------------------------------
-- | A particular type argument can be either co- or contravariant
-- with respect to subtyping.
data Variance = Co | Contra
deriving (Show, Read, Eq, Ord)
-- | The arity of a type constructor is a list of variances,
-- expressing both how many type arguments the constructor takes,
-- and the variance of each argument. This is used to decompose
-- subtyping constraints.
--
-- For example, @arity CArr = [Contra, Co]@ since function arrow is
-- contravariant in its first argument and covariant in its second.
-- That is, @S1 -> T1 <: S2 -> T2@ (@<:@ means "is a subtype of") if
-- and only if @S2 <: S1@ and @T1 <: T2@.
arity :: Con -> [Variance]
arity CArr = [Contra, Co]
arity CProd = [Co, Co]
arity CSum = [Co, Co]
arity (CContainer _) = [Co]
arity CMap = [Contra, Co]
arity CGraph = [Co]
arity (CUser _) = error "Impossible! arity CUser"
-- CUsers should always be replaced by their definitions before arity
-- is called.
------------------------------------------------------------
-- Subtyping rules
------------------------------------------------------------
-- | A "direction" for the subtyping relation (either subtype or
-- supertype).
data Dir = SubTy | SuperTy
deriving (Eq, Ord, Read, Show)
-- | Swap directions.
other :: Dir -> Dir
other SubTy = SuperTy
other SuperTy = SubTy
--------------------------------------------------
-- Subtype checks
-- | Check whether one atomic type is a subtype of the other. Returns
-- @True@ if either they are equal, or if they are base types and
-- 'isSubB' returns true.
isSubA :: Atom -> Atom -> Bool
isSubA a1 a2 | a1 == a2 = True
isSubA (ABase t1) (ABase t2) = isSubB t1 t2
isSubA _ _ = False
-- | Check whether one base type is a subtype of another.
isSubB :: BaseTy -> BaseTy -> Bool
isSubB b1 b2 | b1 == b2 = True
isSubB N Z = True
isSubB N F = True
isSubB N Q = True
isSubB Z Q = True
isSubB F Q = True
isSubB B P = True
isSubB _ _ = False
-- | Check whether one base type is a sub- or supertype of another.
isDirB :: Dir -> BaseTy -> BaseTy -> Bool
isDirB SubTy b1 b2 = isSubB b1 b2
isDirB SuperTy b1 b2 = isSubB b2 b1
-- | List all the supertypes of a given base type.
supertypes :: BaseTy -> [BaseTy]
supertypes N = [N, Z, F, Q]
supertypes Z = [Z, Q]
supertypes F = [F, Q]
supertypes B = [B, P]
supertypes ty = [ty]
-- | List all the subtypes of a given base type.
subtypes :: BaseTy -> [BaseTy]
subtypes Q = [Q, F, Z, N]
subtypes F = [F, N]
subtypes Z = [Z, N]
subtypes P = [P, B]
subtypes ty = [ty]
-- | List all the sub- or supertypes of a given base type.
dirtypes :: Dir -> BaseTy -> [BaseTy]
dirtypes SubTy = subtypes
dirtypes SuperTy = supertypes
------------------------------------------------------------
-- Qualifier and sort rules
------------------------------------------------------------
-- | Check whether a given base type satisfies a qualifier.
hasQual :: BaseTy -> Qualifier -> Bool
hasQual P QCmp = False -- can't compare Props
hasQual _ QCmp = True
hasQual P QBasic = False
hasQual _ QBasic = True
hasQual P QSimple = False
hasQual _ QSimple = True
-- hasQual (Fin _) q | q `elem` [QNum, QSub, QEnum] = True
-- hasQual (Fin n) QDiv = isPrime n
hasQual b QNum = b `elem` [N, Z, F, Q]
hasQual b QSub = b `elem` [Z, Q]
hasQual b QDiv = b `elem` [F, Q]
hasQual b QEnum = b `elem` [N, Z, F, Q, C]
hasQual b QBool = b `elem` [B, P]
-- | Check whether a base type has a certain sort, which simply
-- amounts to whether it satisfies every qualifier in the sort.
hasSort :: BaseTy -> Sort -> Bool
hasSort = all . hasQual
-- | 'qualRulesMap' encodes some of the rules by which applications of
-- type constructors can satisfy various qualifiers.
--
-- Each constructor maps to a set of rules. Each rule is a mapping
-- from a qualifier to the list of qualifiers needed on the type
-- constructor's arguments for the bigger type to satisfy the
-- qualifier.
--
-- Note in Disco we can get away with any given qualifier requiring
-- /at most one/ qualifier on each type argument. Then we can
-- derive the 'sortRules' by combining 'qualRules'. In general,
-- however, you could imagine some particular qualifier requiring a
-- set of qualifiers (i.e. a general sort) on a type argument. In
-- that case one would just have to encode 'sortRules' directly.
qualRulesMap :: Map Con (Map Qualifier [Maybe Qualifier])
qualRulesMap = M.fromList
[ CProd ==> M.fromList
[ QCmp ==> [Just QCmp, Just QCmp],
QSimple ==> [Just QSimple, Just QSimple]
]
, CSum ==> M.fromList
[ QCmp ==> [Just QCmp, Just QCmp],
QSimple ==> [Just QSimple, Just QSimple]
]
, CList ==> M.fromList
[ QCmp ==> [Just QCmp],
QSimple ==> [Just QSimple]
]
, CBag ==> M.fromList
[ QCmp ==> [Just QCmp],
QSimple ==> [Just QSimple]
]
, CSet ==> M.fromList
[ QCmp ==> [Just QCmp],
QSimple ==> [Just QSimple]
]
, CGraph ==> M.fromList
[ QCmp ==> [Just QCmp],
QNum ==> [Nothing]
]
, CMap ==> M.fromList
[ QCmp ==> [Just QCmp, Just QCmp]
]
]
where
(==>) :: a -> b -> (a,b)
(==>) = (,)
-- We could (theoretically) make graphs and maps also be simple values if we require the map's values are also simple.
-- Eventually we can easily imagine adding an opt-in mode where
-- numeric operations can be used on pairs and functions, then the
-- qualRules would become dependent on what language extension/mode
-- was chosen. For example we could have rules like
--
-- [ CArr ==> M.fromList
-- [ QNum ==> [Nothing, Just QNum] -- (a -> b) can be +, * iff b can
-- , QSub ==> [Nothing, Just QSub] -- ditto for subtraction
-- , QDiv ==> [Nothing, Just QDiv] -- and division
-- ]
-- , CProd ==> M.fromList
-- [ QNum ==> [Just QNum, Just QNum] -- (a,b) can be +, * iff a and b can
-- , QSub ==> [Just QSub, Just QSub] -- etc.
-- , QDiv ==> [Just QDiv, Just QDiv]
-- ]
-- ]
-- | Given a constructor T and a qualifier we want to hold of a type T
-- t1 t2 ..., return a list of qualifiers that need to hold of t1,
-- t2, ...
qualRules :: Con -> Qualifier -> Maybe [Maybe Qualifier]
-- T t1 t2 ... is basic (contains no Prop) iff t1, t2 ... all are.
qualRules c QBasic = Just (map (const (Just QBasic)) (arity c))
-- Otherwise, just look up in the qualRulesMap.
qualRules c q = (M.lookup c >=> M.lookup q) qualRulesMap
-- | @sortRules T s = [s1, ..., sn]@ means that sort @s@ holds of
-- type @(T t1 ... tn)@ if and only if @s1 t1 /\ ... /\ sn tn@.
-- For now this is just derived directly from 'qualRules'.
--
-- This is the @arity@ function described in section 4.1 of Traytel et
-- al.
sortRules :: Con -> Sort -> Maybe [Sort]
sortRules c s = do
-- If any of the quals q in sort s are not in the map corresponding
-- to tycon c, there's no way to make c an instance of q, so fail
-- (the mapM will succeed only if all lookups succeed)
needQuals <- mapM (qualRules c) (S.toList s)
-- Otherwise we are left with a list (corresponding to all the quals
-- in sort s) of lists (each one corresponds to the type args of c).
-- We zip them together to produce a list of sorts.
return $ foldl' (zipWith (\srt -> maybe srt (`S.insert` srt))) (repeat topSort) needQuals
-- | Pick a base type (generally the "simplest") that satisfies a given sort.
pickSortBaseTy :: Sort -> BaseTy
pickSortBaseTy s
| QDiv `S.member` s && QSub `S.member` s = Q
| QDiv `S.member` s = F
| QSub `S.member` s = Z
| QNum `S.member` s = N
| QCmp `S.member` s = N
| QEnum `S.member` s = N
| QBool `S.member` s = B
| QSimple `S.member` s = N
| otherwise = Unit