Last and Position

Refactoring `position` with a proper error message. Tidied up a /lot/ of
the instances using the tuple isomorphism and a change from 'Maybe' to
'Last'.
This commit is contained in:
Tom Harding 2019-04-08 22:24:21 +01:00
parent 3693467412
commit a84269ab5f
5 changed files with 112 additions and 154 deletions

View File

@ -12,6 +12,7 @@ module Data.Partial.Build
import Control.Applicative (liftA2)
import Control.Lens (Prism', prism')
import Data.Kind (Type)
import Data.Monoid (Last (..))
import Data.Partial.Types (Partial (..), GPartial_)
import GHC.Generics
@ -52,8 +53,8 @@ instance (GHasPartial left, GHasPartial right)
gfromPartial (l :*: r) = liftA2 (:*:) (gfromPartial l) (gfromPartial r)
instance GHasPartial (K1 index inner) where
gtoPartial = K1 . Just . unK1
gfromPartial = fmap K1 . unK1
gtoPartial = K1 . pure . unK1
gfromPartial = fmap K1 . getLast . unK1
instance (Generic structure, GHasPartial (Rep structure))
=> HasPartial structure where

View File

@ -9,6 +9,7 @@ module Data.Partial.Default
import Data.Kind (Type)
import Data.Maybe (fromMaybe)
import Data.Monoid (Last (..))
import Data.Partial.Types (Partial (..), GPartial_)
import GHC.Generics
@ -50,7 +51,7 @@ instance (GDefaults left, GDefaults right)
instance GDefaults (K1 index inner) where
gdefaults (K1 inner) (K1 partial)
= K1 (fromMaybe inner partial)
= K1 (fromMaybe inner (getLast partial))
instance (Generic structure, GDefaults (Rep structure))
=> Defaults structure where

View File

@ -8,11 +8,14 @@
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Partial.Field where
module Data.Partial.Field
( HasField' (..)
) where
import Control.Lens (Lens', dimap)
import Data.Partial.Types (Partial (..), Partial_)
import Data.Kind (Type)
import Data.Monoid (Last (..))
import Data.Partial.Types (Partial (..), Partial_)
import GHC.Generics
import GHC.TypeLits (ErrorMessage (..), Symbol, TypeError)
@ -45,8 +48,8 @@ class HasField' (field :: Symbol) (structure :: Type) (focus :: Type)
-------------------------------------------------------------------------------
type family Field (field :: Symbol) (rep :: Type -> Type) :: Maybe Type where
Field s (S1 (_ ('Just s) _ _ _) (_ (Maybe x))) = 'Just x
Field s (M1 _ _ xs) = Field s xs
Field s (S1 (_ ('Just s) _ _ _) (_ (Last x))) = 'Just x
Field s (M1 _ _ xs) = Field s xs
Field s (l :*: r) = Field s l <|> Field s r
Field _ _ = 'Nothing
@ -63,8 +66,8 @@ instance GHasField' field inner focus
=> GHasField' field (M1 index meta inner) focus where
gfield = dimap unM1 (fmap M1) . gfield @field
instance GHasField' field (K1 index (Maybe focus)) focus where
gfield = dimap unK1 (fmap K1)
instance GHasField' field (K1 index (Last focus)) focus where
gfield = dimap (getLast . unK1) (fmap (K1 . Last))
instance GHasFieldProduct (Field field left) field (left :*: right) focus
=> GHasField' field (left :*: right) focus where

View File

@ -8,37 +8,66 @@
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Partial.Position where
module Data.Partial.Position
( HasPosition' (..)
) where
import GHC.Generics
import Control.Lens (Lens')
import Control.Lens (Lens', dimap)
import Data.Kind (Type)
import Data.Monoid (Last (..))
import Data.Partial.Types (Partial (..), Partial_)
import GHC.TypeLits (Nat, type (<=?), type (+), type (-))
import GHC.Generics
import GHC.TypeLits (ErrorMessage (..), Nat, TypeError, type (<=?), type (+), type (-))
-- | For types /without/ named fields, our lens must be positional. This works
-- just the same as 'field', but we type-apply the index (starting at @1@).
-- | Taking another cue from @generic-lens@, we can lens into partial product
-- types whose fields /aren't/ named using a positional index.
--
-- >>> import Control.Lens
-- >>> import Data.Partial.Build
--
-- We address the positions using a type application:
--
-- >>> toPartial ("Hello", True) ^. position @1
-- Just "Hello"
--
-- >>> mempty @(Partial (Int, String, Bool)) ^. position @3
-- Nothing
--
-- >>> toPartial ("Hello", True) ^. position @4
-- ...
-- ... ([Char], Bool) has no position #4!
-- ...
class HasPosition' (index :: Nat) (structure :: Type) (focus :: Type)
| index structure -> focus where
position :: Lens' (Partial structure) (Maybe focus)
-------------------------------------------------------------------------------
type family Position (index :: Nat) (rep :: Type -> Type) :: Maybe Type where
Position 1 (K1 _ (Maybe inner)) = 'Just inner
Position _ (K1 _ _ ) = 'Nothing
Position 1 (K1 _ (Last x)) = 'Just x
Position _ (K1 _ _ ) = 'Nothing
Position count (M1 index meta inner)
= Position count inner
Position n (M1 _ _ x)
= Position n x
Position count (left :*: right)
= Position_ (Count left <=? count) count left right
Position n (l :*: r)
= Position_ (n <=? Count l) n l r
type family Count (rep :: Type -> Type) :: Nat where
Count (left :*: right) = Count left + Count right
Count _ = 1
type family Position_ (isLeft :: Bool) (count :: Nat) (left :: Type -> Type) (right :: Type -> Type) :: Maybe Type where
Position_ 'True count left _ = Position count left
Position_ 'False count _ right = Position count right
type family Position_
(isLeft :: Bool)
(count :: Nat)
(left :: Type -> Type)
(right :: Type -> Type) :: Maybe Type
where
Position_ 'True count left _
= Position count left
Position_ 'False count left right
= Position (count - Count left) right
class GHasPosition' (index :: Nat) (rep :: Type -> Type) (focus :: Type)
| index rep -> focus where
@ -46,8 +75,7 @@ class GHasPosition' (index :: Nat) (rep :: Type -> Type) (focus :: Type)
instance GHasPosition' count inner focus
=> GHasPosition' count (M1 index meta inner) focus where
gposition = go . gposition @count
where go f = fmap M1 . f . unM1
gposition = dimap unM1 (fmap M1) . gposition @count
class GHasPositionProduct
(goal :: Maybe Type)
@ -71,11 +99,29 @@ instance (GHasPosition' (count - Count left) right focus)
gpositionProduct = go . gposition @(count - Count left)
where go f (left :*: right) = fmap (left :*:) (f right)
instance any ~ focus => GHasPosition' 1 (K1 index (Maybe any)) focus where
gposition f = fmap K1 . f . unK1
instance any ~ focus => GHasPosition' 1 (K1 index (Last any)) focus where
gposition = dimap (getLast . unK1) (fmap (K1 . Last))
instance (Generic structure, GHasPosition' index (Partial_ structure) focus)
type HasPosition_ (position :: Nat) (structure :: Type)
= AssertHasPosition position structure (Position position (Partial_ structure))
type family AssertHasPosition
(position :: Nat)
(structure :: Type)
(goal :: Maybe Type) where
AssertHasPosition _ _ ('Just xs) = xs
AssertHasPosition position structure 'Nothing = TypeError
( 'ShowType structure
':<>: 'Text " has no position #"
':<>: 'ShowType position
':<>: 'Text "!"
)
instance
( Generic structure
, GHasPosition' index (Partial_ structure) focus
, HasPosition_ index structure ~ focus
)
=> HasPosition' index structure focus where
position = go . gposition @index
where go f (Partial inner) = fmap Partial (f inner)
position = dimap runPartial (fmap Partial) . gposition @index

View File

@ -1,8 +1,6 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
@ -11,13 +9,13 @@
{-# LANGUAGE UndecidableInstances #-}
module Data.Partial.Types where
import Control.Applicative ((<|>), empty)
import Data.Monoid (Last (..))
import Data.Function (on)
import Data.Kind (Type)
import Data.Proxy (Proxy (..))
import Data.Void (Void)
import GHC.Generics
import GHC.TypeLits (KnownSymbol, symbolVal)
import Test.QuickCheck (Gen)
import Test.QuickCheck.Arbitrary (Arbitrary (..), CoArbitrary (..))
import Test.QuickCheck.Function (Function (..), functionMap)
@ -38,7 +36,7 @@ type family GPartial_ (rep :: Type -> Type) :: Type -> Type where
= GPartial_ left :*: GPartial_ right
GPartial_ (K1 index value)
= K1 index (Maybe value)
= K1 index (Last value)
GPartial_ (left :+: right)
= GPartial_ left :+: GPartial_ right
@ -46,128 +44,27 @@ type family GPartial_ (rep :: Type -> Type) :: Type -> Type where
GPartial_ U1 = U1
GPartial_ V1 = V1
-- | Partial structures have an 'Eq' instance providing that all the
-- parameters' types have an 'Eq' instance as well.
class GEq (rep :: Type -> Type) where
geq :: rep p -> rep q -> Bool
instance (Eq tuple, Generic xs, Tuple xs tuple) => Eq (Partial xs) where
(==) = (==) `on` toTuple
instance GEq inner => GEq (M1 index meta inner) where
geq (M1 x) (M1 y) = geq x y
instance (Ord tuple, Generic xs, Tuple xs tuple) => Ord (Partial xs) where
compare = compare `on` toTuple
instance (GEq left, GEq right) => GEq (left :*: right) where
geq (leftX :*: rightX) (leftY :*: rightY)
= geq leftX leftY && geq rightX rightY
instance (Semigroup tuple, Generic xs, Tuple xs tuple)
=> Semigroup (Partial xs) where
x <> y = fromTuple (toTuple x <> toTuple y)
instance Eq parameter => GEq (K1 index (Maybe parameter)) where
geq (K1 x) (K1 y) = x == y
instance (Monoid tuple, Generic xs, Tuple xs tuple)
=> Monoid (Partial xs) where
mempty = fromTuple mempty
instance (Generic structure, GEq (Partial_ structure))
=> Eq (Partial structure) where
Partial x == Partial y = geq x y
-- | Similarly, partial structures have an ordering if all parameters are also
-- instances of 'Ord'. Note that an "empty" parameter is /less than/ a
-- completed parameter due to the 'Ord' instance of 'Maybe'.
class GEq rep => GOrd (rep :: Type -> Type) where
gcompare :: rep p -> rep q -> Ordering
instance GOrd inner => GOrd (M1 index meta inner) where
gcompare (M1 x) (M1 y) = gcompare x y
instance (GOrd left, GOrd right) => GOrd (left :*: right) where
gcompare (leftX :*: rightX) (leftY :*: rightY)
= gcompare leftX leftY <> gcompare rightX rightY
instance Ord parameter => GOrd (K1 index (Maybe parameter)) where
gcompare (K1 x) (K1 y) = compare x y
instance (Generic structure, GOrd (Partial_ structure))
=> Ord (Partial structure) where
compare (Partial x) (Partial y) = gcompare x y
-- | Partial structures form a semigroup by taking the (right-biased) union of
-- partial data.
class GSemigroup (rep :: Type -> Type) where
gmappend :: rep p -> rep q -> rep r
instance GSemigroup inner => GSemigroup (M1 index meta inner) where
gmappend (M1 x) (M1 y) = M1 (gmappend x y)
instance (GSemigroup left, GSemigroup right)
=> GSemigroup (left :*: right) where
gmappend (leftX :*: rightX) (leftY :*: rightY)
= gmappend leftX leftY :*: gmappend rightX rightY
instance GSemigroup (K1 index (Maybe anything)) where
gmappend (K1 x) (K1 y) = K1 (y <|> x)
instance GSemigroup U1 where
gmappend U1 U1 = U1
instance (Generic structure, GSemigroup (Partial_ structure))
=> Semigroup (Partial structure) where
Partial x <> Partial y
= Partial (gmappend x y)
-- | Partial structures form a monoid if the unit element is a partial
-- structure with no completed fields.
class GSemigroup rep => GMonoid (rep :: Type -> Type) where
gmempty :: rep p
instance (GMonoid inner, GSemigroup (M1 index meta inner))
=> GMonoid (M1 index meta inner) where
gmempty = M1 gmempty
instance (GMonoid left, GMonoid right, GSemigroup (left :*: right))
=> GMonoid (left :*: right) where
gmempty = gmempty :*: gmempty
instance GSemigroup (K1 index (Maybe anything))
=> GMonoid (K1 index (Maybe anything)) where
gmempty = K1 empty
instance GSemigroup U1 => GMonoid U1 where
gmempty = U1
instance (Generic structure, GMonoid (Partial_ structure))
=> Monoid (Partial structure) where
mempty = Partial gmempty
-- | If all the components of a structure have an 'Arbitrary' instance, we can
-- trivially create arbitrary partial structures.
class GArbitrary (rep :: Type -> Type) where
garbitrary :: Gen (rep p)
instance GArbitrary inner => GArbitrary (M1 index meta inner) where
garbitrary = fmap M1 garbitrary
instance (GArbitrary left, GArbitrary right)
=> GArbitrary (left :*: right) where
garbitrary = (:*:) <$> garbitrary <*> garbitrary
instance Arbitrary inner => GArbitrary (K1 index inner) where
garbitrary = fmap K1 arbitrary
instance (Generic structure, GArbitrary (Partial_ structure))
instance (Arbitrary tuple, GToTuple (Partial_ structure) tuple)
=> Arbitrary (Partial structure) where
arbitrary = fmap Partial garbitrary
arbitrary = fmap (Partial . gfromTuple) arbitrary
class GCoArbitrary (rep :: Type -> Type) where
gcoarbitrary :: rep p -> Gen b -> Gen b
instance GCoArbitrary inner => GCoArbitrary (M1 index meta inner) where
gcoarbitrary (M1 x) = gcoarbitrary x
instance (GCoArbitrary left, GCoArbitrary right)
=> GCoArbitrary (left :*: right) where
gcoarbitrary (left :*: right) = gcoarbitrary left . gcoarbitrary right
instance CoArbitrary inner => GCoArbitrary (K1 index (Maybe inner)) where
gcoarbitrary (K1 inner) = coarbitrary inner
instance (Generic structure, GCoArbitrary (Partial_ structure))
instance (CoArbitrary tuple, GToTuple (Partial_ structure) tuple)
=> CoArbitrary (Partial structure) where
coarbitrary (Partial x) = gcoarbitrary x
coarbitrary (Partial x) = coarbitrary (gtoTuple x)
-- | For complete partial structures, the 'Show' instances should match (though
-- there are some edge-cases around, say, rendering of negative numbers).
@ -200,8 +97,8 @@ instance (GShow 'True inner, KnownSymbol field)
instance GShow 'False inner => GShow 'False (S1 meta inner) where
gshow (M1 inner) = gshow @'False inner
instance Show inner => GShow named (K1 R (Maybe inner)) where
gshow (K1 x) = maybe "???" show x
instance Show inner => GShow named (K1 R (Last inner)) where
gshow (K1 x) = maybe "???" show (getLast x)
instance (Generic structure, GShow 'True (Partial_ structure))
=> Show (Partial structure) where
@ -226,6 +123,16 @@ instance Function inner => GToTuple (K1 index inner) inner where
gfromTuple = K1
gtoTuple = unK1
class Tuple (structure :: Type) (tuple :: Type)
| structure -> tuple where
toTuple :: Partial structure -> tuple
fromTuple :: tuple -> Partial structure
instance (Generic structure, GToTuple (Partial_ structure) tuple)
=> Tuple structure tuple where
toTuple = gtoTuple . runPartial
fromTuple = Partial . gfromTuple
instance (Generic structure, Function tuple, Tuple structure tuple)
=> Function (Partial structure) where
function = functionMap (gtoTuple . runPartial) (Partial . gfromTuple)
function = functionMap toTuple fromTuple