n-ary-functor (empty) → 0.1.0.0
raw patch · 6 files changed
+325/−0 lines, 6 filesdep +basedep +doctestdep +doctest-discoversetup-changed
Dependencies added: base, doctest, doctest-discover
Files
- CHANGELOG.md +3/−0
- README.md +14/−0
- Setup.hs +2/−0
- n-ary-functor.cabal +27/−0
- src/NAryFunctor.hs +278/−0
- test/doctests.hs +1/−0
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+## New in 0.1.0.0++* Initial release of the `NFunctor` typeclass
+ README.md view
@@ -0,0 +1,14 @@+# N-ary Functors++[`Functor`](https://hackage.haskell.org/package/base-4.10.1.0/docs/Prelude.html#t:Functor) and [`Bifunctor`](https://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Bifunctor.html#t:Bifunctor) are both in `base`, but what about `Trifunctor`? `Quadrifunctor`? There must be a better solution than creating an infinite tower of typeclasses. Here's the API I managed to implement:++ > nmap <#> (+1) <#> (+2) $ (0, 0)+ (1,2)++ > nmap <#> (+1) <#> (+2) <#> (+3) $ (0, 0, 0)+ (1,2,3)++ > nmap <#> (+1) <#> (+2) <#> (+3) <#> (+4) $ (0, 0, 0, 0)+ (1,2,3,4)++For more details, see the [documentation](https://hackage.haskell.org/package/n-ary-functor-0.1.0.0/docs/NAryFunctor.html) and the [blog post](http://gelisam.blogspot.ca/2017/12/n-ary-functors.html).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ n-ary-functor.cabal view
@@ -0,0 +1,27 @@+name: n-ary-functor+version: 0.1.0.0+synopsis: An n-ary version of Functor+description: A single typeclass for Functor, Bifunctor, Trifunctor, etc.+homepage: https://github.com/gelisam/n-ary-functor+license: PublicDomain+author: Samuel Gélineau+maintainer: gelisam+github@gmail.com+category: Data+build-type: Simple+cabal-version: >=1.10+extra-source-files: README.md+ , CHANGELOG.md++library+ exposed-modules: NAryFunctor+ build-depends: base >=4.9 && <4.11+ hs-source-dirs: src+ default-language: Haskell2010++test-suite doctests+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ ghc-options: -threaded+ main-is: doctests.hs+ build-depends: base >4 && <5, doctest, doctest-discover+ HS-Source-Dirs: test
+ src/NAryFunctor.hs view
@@ -0,0 +1,278 @@+{-# LANGUAGE RankNTypes, TypeFamilies, TypeFamilyDependencies, TypeInType #-}+module NAryFunctor+ ( NFunctor(..)++ -- * Internals+ , NMap1(..), NMap+ ) where++import Data.Bifunctor+import Data.Functor.Identity+import Data.Kind (Type)+++-- |+-- A generalization of 'Functor', 'Bifunctor', 'Trifunctor', etc.+--+-- Example usage:+--+-- >>> nmap <#> (+1) $ Identity (0::Int)+-- Identity 1+--+-- >>> nmap <#> (+1) <#> (+2) $ (0::Int, 0::Int)+-- (1,2)+--+-- >>> nmap <#> (+1) <#> (+2) <#> (+3) $ (0::Int, 0::Int, 0::Int)+-- (1,2,3)+--+-- Laws:+--+-- > nmap <#> id <#> ... <#> id = id+-- > (nmap <#> f1 <#> ... <#> fN) . (nmap <#> g1 <#> ... <#> gN) = nmap <#> (f1 . g1) <#> ... <#> (fN . gN)+--+-- Example instance:+--+-- > instance NFunctor (,,) where+-- > nmap = NMap1 $ \f1+-- > -> NMap1 $ \f2+-- > -> NMap1 $ \f3+-- > -> \(x1,x2,x3)+-- > -> (f1 x1, f2 x2, f3 x3)+class NFunctor (f :: k) where+ nmap :: NMap k f f+++-- |+-- Types like 'Either' which have both a 'Functor' and a 'Bifunctor' instance+-- can have more than one 'NFunctor' instance. Those instances all define the+-- same method, 'nmap', but they return a value of a different type, which is+-- how the correct 'NFunctor' instance is picked:+--+-- > nmap :: NMap1 Type (Either a) (Either a) -- Functor+-- > nmap :: NMap1 (Type -> Type) Either Either -- Bifunctor+--+-- This 'NMap1' is unwrapped by using '<#>' to pass in the next input function.+-- In the case of @NMap1 (Type -> Type)@, the result after passing this input+-- function is another 'NMap1', which needs to be unwrapped using a second+-- '<#>'. The end result is that the 'Functor' behaviour is obtained by using a+-- single '<#>', and the 'Bifunctor' behaviour is obtained by using two.+--+-- >>> nmap <#> (+1) $ Right (0::Int)+-- Right 1+-- >>> nmap <#> (+1) <#> (+2) $ Left (0::Int)+-- Left 1+newtype NMap1 k (f :: Type -> k) (f' :: Type -> k) = NMap1+ { (<#>) :: forall a b. (a -> b) -> NMap k (f a) (f' b)+ }++type family NMap k = (r :: k -> k -> Type) | r -> k where+ NMap Type = (->)+ NMap (Type -> k) = NMap1 k+++-- | For kind @* -> *@ ('Functor'), 'nmap' must be @NMap1 fmap@.+--+-- >>> nmap <#> (+1) $ Right (0::Int)+-- Right 1+instance NFunctor (Either a) where+ nmap = NMap1 fmap++-- | For kind @* -> * -> *@ ('Bifunctor'), 'nmap' must be @NMap1 $ \f1 -> NMap1 $ \f2 -> bimap f1 f2@.+--+-- >>> nmap <#> (+1) <#> (+2) $ Left (0::Int)+-- Left 1+instance NFunctor Either where+ nmap = NMap1 $ \f1 -> NMap1 $ \f2 -> bimap f1 f2+++-- |+-- For kind @*@, 'nmap' must be the identity function. If 'Bifunctor' and+-- 'Functor' correspond to binary and unary functors, this corresponds to a+-- "nullary" functor.+--+-- >>> nmap ()+-- ()+instance NFunctor () where+ nmap = id++instance NFunctor Identity where+ nmap = NMap1 $ \f1+ -> \(Identity x1)+ -> Identity (f1 x1)++instance NFunctor (,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> \(x1,x2)+ -> (f1 x1, f2 x2)++instance NFunctor (,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> \(x1,x2,x3)+ -> (f1 x1, f2 x2, f3 x3)++instance NFunctor (,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> \(x1,x2,x3,x4)+ -> (f1 x1, f2 x2, f3 x3, f4 x4)++instance NFunctor (,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> \(x1,x2,x3,x4,x5)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5)++instance NFunctor (,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> \(x1,x2,x3,x4,x5,x6)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6)++instance NFunctor (,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> \(x1,x2,x3,x4,x5,x6,x7)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7)++instance NFunctor (,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> \(x1,x2,x3,x4,x5,x6,x7,x8)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8)++instance NFunctor (,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9)++instance NFunctor (,,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> NMap1 $ \f10+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10)++instance NFunctor (,,,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> NMap1 $ \f10+ -> NMap1 $ \f11+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11)++instance NFunctor (,,,,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> NMap1 $ \f10+ -> NMap1 $ \f11+ -> NMap1 $ \f12+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12)++instance NFunctor (,,,,,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> NMap1 $ \f10+ -> NMap1 $ \f11+ -> NMap1 $ \f12+ -> NMap1 $ \f13+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12, f13 x13)++instance NFunctor (,,,,,,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> NMap1 $ \f10+ -> NMap1 $ \f11+ -> NMap1 $ \f12+ -> NMap1 $ \f13+ -> NMap1 $ \f14+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12, f13 x13, f14 x14)++instance NFunctor (,,,,,,,,,,,,,,) where+ nmap = NMap1 $ \f1+ -> NMap1 $ \f2+ -> NMap1 $ \f3+ -> NMap1 $ \f4+ -> NMap1 $ \f5+ -> NMap1 $ \f6+ -> NMap1 $ \f7+ -> NMap1 $ \f8+ -> NMap1 $ \f9+ -> NMap1 $ \f10+ -> NMap1 $ \f11+ -> NMap1 $ \f12+ -> NMap1 $ \f13+ -> NMap1 $ \f14+ -> NMap1 $ \f15+ -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15)+ -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12, f13 x13, f14 x14, f15 x15)++-- 16-tuples don't even have a Show instance, so we don't bother with an NFunctor instance either
+ test/doctests.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF doctest-discover #-}