packages feed

nthese (empty) → 0.1.0.0

raw patch · 4 files changed

+473/−0 lines, 4 filesdep +basedep +semialigndep +sop-core

Dependencies added: base, semialign, sop-core, these

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for nthese++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (c) 2025, Manuel Bärenz+++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of the copyright holder nor the names of its+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ nthese.cabal view
@@ -0,0 +1,41 @@+cabal-version: 3.0+name: nthese+version: 0.1.0.0+synopsis: A heterogeneous, n-ary generalisation of These+description:+  `NThese` is an n-ary generalisation of the `These` datatype,+  written in `sop-core` style.++license: BSD-3-Clause+license-file: LICENSE+author: Manuel Bärenz+maintainer: programming@manuelbaerenz.de+build-type: Simple+extra-doc-files: CHANGELOG.md+tested-with:+  ghc ==9.6+  ghc ==9.8+  ghc ==9.10+  ghc ==9.12++common warnings+  ghc-options: -Wall++library+  import: warnings+  exposed-modules: Data.NThese+  build-depends:+    base >=4.18 && <4.22,+    semialign ^>=1.3,+    sop-core ^>=0.5,+    these ^>=1.2,++  hs-source-dirs: src+  default-language:+    GHC2021++  default-extensions:+    DataKinds+    LambdaCase+    ScopedTypeVariables+    TypeFamilies
+ src/Data/NThese.hs view
@@ -0,0 +1,398 @@+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE UndecidableSuperClasses #-}++module Data.NThese (module Data.NThese) where++-- base+import Data.Function ((&))+import Data.Functor ((<&>))+import Data.Kind (Constraint, Type)+import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NonEmpty+import Unsafe.Coerce (unsafeCoerce)++-- sop-core+import Data.SOP (All, AllN, AllZip, AllZipN, CollapseTo, HAp (..), HCollapse (..), HExpand (..), HSequence (..), HTrans, HTraverse_ (..), K (..), NP (..), NS (..), Prod, SList (..), SListI, hmap, sList, unK, type (-.->) (apFn), type (:.:) (..))+import Data.SOP.Classes (HPure (..), HTrans (..), Same)+import Data.SOP.Constraint (SListIN, Tail)++-- these+import Data.Functor.These (These1 (..))+import Data.These (These (..))+import Data.These qualified as These++-- semialign+import Data.Align (Align (..), Semialign (..), Unalign (..))++{- | An n-ary generalisation of the 'These' datatype.++@'NThese' f [a1, a2, a3, ...]@ contains at least one of @f a1@, @f a2@, and so on,+and potentially all of them.++@'NThese' 'Identity' [a, b]@ is isomorphic to @'These' a b@.++= Relations to other types++* @'NP' f as@ is the n-ary product. It contains exactly @n@ elements. There is an injection from 'NP' into 'NThese' with all positions filled, but in general 'NThese' needn't fill every position.+* @'NP' ('Maybe' :.: f) as@ can contain 0 to @n@ elements, whereas 'NThese' always contains at least one element.+* @'NS' f as@ is the n-ary sum. It contains exacty 1 element. There is an injection from 'NS' into 'NThese', but in general 'NThese' can fill more positions.+* @'NonEmpty' a@ is the nonempty homogeneous list. It is similar to @'NThese' ('K' a) as@, but 'NonEmpty' can contain 1 to infinitely many elements,+  while 'NThese' has at most @n@ elements and contains positional information about the present values.+-}+data NThese :: (k -> Type) -> [k] -> Type where+  -- | There is a value right here, and no values in the tail. Generalises 'This'.+  ThisHere ::+    -- | The value right here+    f a ->+    NThese f (a : as)+  -- | There is no value right here, but there are some values guaranteed to be in the tail. Generalises 'That'.+  ThatThere ::+    -- | The tail, guaranteed to contain values+    NThese f as ->+    NThese f (a : as)+  -- | The first present value is at the head, further values are in the tail. Generalises 'These'.+  TheseHere ::+    -- | The first present value+    f a ->+    -- | The tail, guaranteed to contain further values+    NThese f as ->+    NThese f (a : as)++-- * Accessing the head of 'NThese'++{- | Get the first element when it's guaranteed to be present++When there is only one type variable, 'TheseHere' is the only possible constructor,+so the type is isomorphic to @f a@.+-}+unThisHere :: NThese f '[a] -> f a+unThisHere (ThisHere fa) = fa+unThisHere (TheseHere _ impossible) = case impossible of {}+unThisHere (ThatThere impossible) = case impossible of {}++-- | Extract the first element, if present.+safeHead :: NThese f (a : as) -> Maybe (f a)+safeHead (ThisHere fa) = Just fa+safeHead (TheseHere fa _) = Just fa+safeHead (ThatThere _) = Nothing++-- | Prepend an element.+cons :: f a -> NThese f as -> NThese f (a : as)+cons = TheseHere++-- | Prepend a possibly absent element.+consMaybe :: Maybe (f a) -> NThese f as -> NThese f (a : as)+consMaybe = \case+  Nothing -> ThatThere+  Just fa -> cons fa++-- * Relationship to 'These'++-- | Extension of 'That' to more type parameters.+mkThat :: (SListI as) => f a2 -> NThese f (a1 : a2 : as)+mkThat fa2 = ThatThere $ ThisHere fa2++-- | Extension of 'These' to more type parameters.+mkThese :: (SListI as) => f a1 -> f a2 -> NThese f (a1 : a2 : as)+mkThese fa1 fa2 = TheseHere fa1 $ ThisHere fa2++-- | Witness that 'NThese' is a generalisation of 'These' with @n >= 2@ type variables.+fromThese :: (SListI as) => These (f a1) (f a2) -> NThese f (a1 : a2 : as)+fromThese = These.these ThisHere mkThat mkThese++-- | 'NThese' is recursively isomorphic to 'These'.+toThese :: NThese f (a : as) -> These (f a) (NThese f as)+toThese = \case+  ThisHere fa -> This fa+  ThatThere fas -> That fas+  TheseHere fa fas -> These fa fas++-- | Inverse of 'toThese'.+absorbThese :: (SListI as) => These (f a) (NThese f as) -> NThese f (a : as)+absorbThese = \case+  This fa -> ThisHere fa+  That fas -> ThatThere fas+  These fa fas -> TheseHere fa fas++-- * Interaction with other n-ary heterogeneous datatypes++-- ** N-ary sums++{- | Injection of n-ary sums.++An n-ary sum contains exactly one element, 'NThese' contains at least one element.+-}+fromNS :: (SListI as) => NS f as -> NThese f as+fromNS = \case+  Z fa -> ThisHere fa+  S ns -> ThatThere $ fromNS ns++-- | Project onto the first present element, discarding the rest.+headNS :: NThese f as -> NS f as+headNS = \case+  ThisHere fa -> Z fa+  ThatThere fas -> S $ headNS fas+  TheseHere fa _ -> Z fa++{- | Extract all elements into a 'NonEmpty' list.++The information that we have at most one element of each type is lost.+-}+toNSs :: (SListI as) => NThese f as -> NonEmpty (NS f as)+toNSs = \case+  ThisHere fa -> Z fa :| []+  ThatThere nt -> toNSs nt <&> S+  TheseHere fa fas -> Z fa `NonEmpty.cons` fmap S (toNSs fas)++-- ** N-ary products++{- | Injection of n-ary products.++An n-ary products contains exactly, n elements, 'NThese' contains at most one element.+-}+fromNP :: (SListI as) => NP f (a : as) -> NThese f (a : as)+fromNP = \case+  fa :* Nil -> ThisHere fa+  fa :* fas@(_ :* _) -> TheseHere fa $ fromNP fas++{- | Injection of possibly absent n-ary products.++@'NP' ('Maybe' :.: f) as@ contains 0 to n elements, 'NThese' contains at least one element.+In case that there is no element, 'Nothing' is returned.+-}+fromNPMaybe :: NP (Maybe :.: f) as -> Maybe (NThese f as)+fromNPMaybe = \case+  Nil -> Nothing+  Comp (Just fa) :* fas -> Just $ maybe (ThisHere fa) (TheseHere fa) $ fromNPMaybe fas+  Comp Nothing :* fas -> fromNPMaybe fas <&> ThatThere++{- | Projection onto possibly absent n-ary products.++The information that there is at least one element is lost.+-}+toNP :: (SListI as) => NThese f as -> NP (Maybe :.: f) as+toNP = \case+  ThisHere fa -> Comp (Just fa) :* hpure (Comp Nothing)+  ThatThere fas -> Comp Nothing :* toNP fas+  TheseHere fa fas -> Comp (Just fa) :* toNP fas++{- | Zip two 'NThese' together.++Each position may contain:++* No value, this is covered by the 'NThese' structure+* One or two values, this is represented in each 'These1' structure+-}+zipNThese :: (SListI as) => NThese f as -> NThese g as -> NThese (These1 f g) as+zipNThese = \case+  ThisHere fa -> \case+    ThisHere ga -> ThisHere $ These1 fa ga+    ThatThere gas -> TheseHere (This1 fa) $ hmap That1 gas+    TheseHere ga gas -> TheseHere (These1 fa ga) $ hmap That1 gas+  ThatThere fas -> \case+    ThisHere ga -> TheseHere (That1 ga) $ hmap This1 fas+    ThatThere gas -> ThatThere $ zipNThese fas gas+    TheseHere ga gas -> TheseHere (That1 ga) $ zipNThese fas gas+  TheseHere fa fas -> \case+    ThisHere ga -> TheseHere (These1 fa ga) $ hmap This1 fas+    ThatThere gas -> TheseHere (This1 fa) $ zipNThese fas gas+    TheseHere ga gas -> TheseHere (These1 fa ga) $ zipNThese fas gas++-- * Generalisation of type classes related to 'These'++{- | Generalise 'align' to 'NThese'.++'align' has this type signature:++@+'align' :: Semialign f => f a -> f b -> f ('These' a b)+@++This generalises zipping 2 @f@-structures, and requiring that at each @f@-position we have either an @a@ or a @b@.++'alignN' generalises this in two directions:++1. Incidental: We use nested @f (g a)@-structures. (But @g@ can always be set to identity, and is thus irrelevant.)+1. Crucial: We have @n@ values instead of 2. At each @f@-position we can have 1 to n values.+-}+alignN :: forall a as f g. (SListI as, Semialign f) => NP (f :.: g) (a : as) -> f (NThese g (a : as))+alignN = \case+  Comp fga :* fgas -> case alignNP fgas of+    Nothing -> ThisHere <$> fga+    Just fgas' ->+      align fga fgas' <&> \case+        This fga' -> ThisHere fga'+        That fgas'' -> ThatThere fgas''+        These fga' fgas'' -> TheseHere fga' fgas''+  where+    alignNP :: (SListI as, Semialign f) => NP (f :.: g) as -> Maybe (f (NThese g as))+    alignNP = \case+      Nil -> Nothing+      fgas -> case sList :: SList as of+        SCons -> Just $ alignN fgas++{- | Generalise 'nil' to 'NThese'.++Creates an 'NThese' filled with 'nil's.+-}+nilN :: forall a as f g. (SListI as, Align f) => NThese (f :.: g) (a : as)+nilN = hpure $ Comp nil++{- | Generalise 'unalign' to 'NThese'.++'unalign' has this type signature:++@+unalign :: Unalign f => f (These a b) -> (f a, f b)+@++Similar to 'alignN' we generalise this from 2 values to @n@ values.+-}+unalignN :: forall f g as. (Unalign f, SListI as) => f (NThese g as) -> NP (f :.: g) as+unalignN fgas = case sList :: SList as of+  SNil -> Nil+  SCons ->+    fgas+      & unalignWith toThese+      & \(fga, fgas') -> Comp fga :* unalignN fgas'++type instance Same NThese = NThese++type instance Prod NThese = NP++-- | Helper class to constrain the type level list on 'NThese' always to be nonempty+class (SListI as) => SListINThese as++type instance SListIN NThese = SListINThese++instance (SListI as, as ~ a : as') => SListINThese as++-- | Will incur an extra constraint on the type level list not to be empty+instance HPure NThese where+  hpure :: forall k (xs :: [k]) (f :: k -> Type). (SListIN NThese xs) => (forall (a :: k). f a) -> NThese f xs+  hpure fa = case sList :: SList xs of+    SNil -> error "Impossible pattern"+    SCons -> case sList :: SList (Tail xs) of+      SNil -> ThisHere fa+      SCons -> TheseHere fa $ hpure fa++  hcpure ::+    forall k (c :: k -> Constraint) (xs :: [k]) (proxy :: (k -> Constraint) -> Type) (f :: k -> Type).+    (AllN NThese c xs) =>+    proxy c ->+    (forall (a :: k). (c a) => f a) ->+    NThese f xs+  hcpure proxy fa = case sList :: SList xs of+    SNil -> error "Impossible pattern"+    SCons -> case sList :: SList (Tail xs) of+      SNil -> ThisHere fa+      SCons -> TheseHere fa $ hcpure proxy fa++instance HAp NThese where+  hap = \case+    Nil -> \case {}+    fna :* fnas -> \case+      ThisHere fa -> ThisHere $ apFn fna fa+      ThatThere gas -> ThatThere $ hap fnas gas+      TheseHere fa fas -> TheseHere (apFn fna fa) $ hap fnas fas++type instance CollapseTo NThese a = NonEmpty a++instance HCollapse NThese where+  hcollapse = collapse_NThese+    where+      collapse_NThese :: (SListI as) => NThese (K a) as -> NonEmpty a+      collapse_NThese = \case+        ThisHere fa -> NonEmpty.singleton $ unK fa+        ThatThere fas -> collapse_NThese fas+        TheseHere (K fa) fas -> fa `NonEmpty.cons` collapse_NThese fas++type instance AllN NThese c = All c++instance HTraverse_ NThese where+  htraverse_ ::+    forall k (xs :: [k]) (g :: Type -> Type) (f :: k -> Type).+    (SListIN NThese xs, Applicative g) =>+    (forall (a :: k). f a -> g ()) ->+    NThese f xs ->+    g ()+  htraverse_ f = htraverse_0+    where+      htraverse_0 :: (Applicative g) => NThese f as -> g ()+      htraverse_0 = \case+        ThisHere fa -> f fa+        ThatThere fas -> htraverse_0 fas+        TheseHere fa fas -> f fa *> htraverse_0 fas++  hctraverse_ ::+    forall k (c :: k -> Constraint) (xs :: [k]) (g :: Type -> Type) (proxy :: (k -> Constraint) -> Type) (f :: k -> Type).+    (AllN NThese c xs, Applicative g) =>+    proxy c ->+    (forall (a :: k). (c a) => f a -> g ()) ->+    NThese f xs ->+    g ()+  hctraverse_ _ f = hctraverse_0+    where+      hctraverse_0 :: (AllN NThese c as, Applicative g) => NThese f as -> g ()+      hctraverse_0 = \case+        ThisHere fa -> f fa+        ThatThere fas -> hctraverse_0 fas+        TheseHere fa fas -> f fa *> hctraverse_0 fas++instance HSequence NThese where+  hsequence' = hsequence'0+    where+      hsequence'0 :: (Applicative f) => NThese (f :.: g) as -> f (NThese g as)+      hsequence'0 = \case+        ThisHere (Comp fga) -> ThisHere <$> fga+        ThatThere fgas -> ThatThere <$> hsequence'0 fgas+        TheseHere (Comp fga) fgas -> TheseHere <$> fga <*> hsequence'0 fgas++  hctraverse' ::+    forall k (c :: k -> Constraint) (xs :: [k]) (g :: Type -> Type) (proxy :: (k -> Constraint) -> Type) (f :: k -> Type) (f' :: k -> Type).+    (AllN NThese c xs, Applicative g) =>+    proxy c ->+    (forall (a :: k). (c a) => f a -> g (f' a)) ->+    NThese f xs ->+    g (NThese f' xs)+  hctraverse' _ f = hctraverse'0+    where+      hctraverse'0 :: (AllN NThese c as) => NThese f as -> g (NThese f' as)+      hctraverse'0 = \case+        ThisHere fa -> ThisHere <$> f fa+        ThatThere fas -> ThatThere <$> hctraverse'0 fas+        TheseHere fa fas -> TheseHere <$> f fa <*> hctraverse'0 fas++  htraverse' ::+    forall k (xs :: [k]) (g :: Type -> Type) (f :: k -> Type) (f' :: k -> Type).+    (SListIN NThese xs, Applicative g) =>+    (forall (a :: k). f a -> g (f' a)) ->+    NThese f xs ->+    g (NThese f' xs)+  htraverse' f = htraverse'0+    where+      htraverse'0 :: NThese f as -> g (NThese f' as)+      htraverse'0 = \case+        ThisHere fa -> ThisHere <$> f fa+        ThatThere fas -> ThatThere <$> htraverse'0 fas+        TheseHere fa fas -> TheseHere <$> f fa <*> htraverse'0 fas++instance HExpand NThese where+  hexpand fa0 = \case+    ThisHere fa -> fa :* hpure fa0+    ThatThere fas -> fa0 :* hexpand fa0 fas+    TheseHere fa fas -> fa :* hexpand fa0 fas+  hcexpand proxy fa0 = \case+    ThisHere fa -> fa :* hcpure proxy fa0+    ThatThere fas -> fa0 :* hcexpand proxy fa0 fas+    TheseHere fa fas -> fa :* hcexpand proxy fa0 fas++type instance AllZipN NThese c = AllZip c++instance HTrans NThese NThese where+  htrans proxy f = \case+    ThisHere fa -> ThisHere $ f fa+    ThatThere fas -> ThatThere $ htrans proxy f fas+    TheseHere fa fas -> TheseHere (f fa) $ htrans proxy f fas++  hcoerce = unsafeCoerce