Last active
August 29, 2015 14:12
-
-
Save plaidfinch/c03206171c240a8a1179 to your computer and use it in GitHub Desktop.
Playing with Data.Constraint for great fun
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE GADTs #-} | |
{-# LANGUAGE PolyKinds #-} | |
{-# LANGUAGE DataKinds #-} | |
{-# LANGUAGE ConstraintKinds #-} | |
{-# LANGUAGE TypeFamilies #-} | |
{-# LANGUAGE TypeOperators #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
import Data.Constraint | |
import Data.Proxy | |
newtype (f ∘ g) a = O { getO :: f (g a) } | |
data Product (f :: k -> *) (xs :: [k]) where | |
(:*:) :: f x -> Product f xs -> Product f (x ': xs) | |
Nil :: Product f '[] | |
uncons :: Product f (x ': xs) -> (f x, Product f xs) | |
uncons (x :*: xs) = (x, xs) | |
type family All (c :: k -> Constraint) (xs :: [k]) :: Constraint where | |
All c '[] = () | |
All c (x ': xs) = (c x, All c xs) | |
-- Map, lifted to the type level | |
type family Map (f :: k -> l) (xs :: [k]) :: [l] where | |
Map f '[] = '[] | |
Map f (x ': xs) = f x ': Map f xs | |
-- Discharge a constraint with a dictionary | |
(~~>) :: Dict c -> (c => z) -> z | |
Dict ~~> z = z | |
-- If for all x in xs, (c x) holds, produce a list of dictionaries as evidence | |
dicts :: All c xs => Proxy c -> Product Proxy xs -> Product (Dict ∘ c) xs | |
dicts _ Nil = Nil | |
dicts p (_ :*: xs) = O Dict :*: dicts p xs | |
-- If for all x in xs, (c (f x)) holds, produce a list of dictionaries as evidence | |
dictsF :: All c (Map f xs) => Proxy c -> Proxy f -> Product Proxy xs -> Product (Dict ∘ c ∘ f) xs | |
dictsF _ _ Nil = Nil | |
dictsF p q (_ :*: xs) = O (O Dict) :*: dictsF p q xs | |
-- Given a list of dictionaries showing that for each x in xs, (c x) holds, | |
-- collapse them into a single dictionary showing that (c x) holds for all x. | |
collectDicts :: Product (Dict ∘ c) xs -> Dict (All c xs) | |
collectDicts Nil = Dict | |
collectDicts (O Dict :*: ds') = collectDicts ds' ~~> Dict | |
-- Given a list of dictionaries showing that for each x in xs, (c (f x)) holds, | |
-- collapse them into a single dictionary showing that (c (f x)) holds for all x. | |
collectDictsF :: Product (Dict ∘ c ∘ f) xs -> Dict (All c (Map f xs)) | |
collectDictsF Nil = Dict | |
collectDictsF (O (O Dict) :*: ds') = collectDictsF ds' ~~> Dict | |
-- Given a function which needs (d x) and (c (f x)) to hold for all x in xs, | |
-- map it over a list of xs if provided explicit dictionary evidence that these | |
-- constraints do in fact hold. | |
mapWithDicts :: forall c d f g xs. (forall x. (d x, c (f x)) => f x -> g x) | |
-> Product (Dict ∘ c ∘ f) xs -> Product (Dict ∘ d) xs | |
-> Product f xs -> Product g xs | |
mapWithDicts t ds1 ds2 xs = | |
case xs of | |
(x :*: xs') -> | |
case uncons ds1 of | |
(O (O Dict), ds1') -> case uncons ds2 of | |
(O Dict, ds2') -> t x :*: mapWithDicts t ds1' ds2' xs' | |
Nil -> Nil | |
-- Map a function requiring certain constraints over a (Product f xs). | |
-- The function requires (c f), as well as (d (f x)) and (e x) for all x in xs. | |
mapWithConstraints :: forall c d e f g xs. (c f, All d (Map f xs), All e xs, KnownProduct xs) | |
=> Proxy c -> Proxy d -> Proxy e | |
-> (forall x. (c f, d (f x), e x) => f x -> g x) | |
-> Product f xs -> Product g xs | |
mapWithConstraints _ _ _ f xs = | |
mapWithDicts f (dictsF (Proxy :: Proxy d) (Proxy :: Proxy f) proxies) (dicts (Proxy :: Proxy e) proxies) xs | |
where proxies = productProxy :: Product Proxy xs | |
-- Generate a Product of Proxies for any type-level list. | |
class KnownProduct (xs :: [k]) where productProxy :: Product Proxy xs | |
instance KnownProduct '[] where productProxy = Nil | |
instance (KnownProduct xs) => KnownProduct (x ': xs) where productProxy = Proxy :*: productProxy |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment