polydata 0.1.0.0 → 0.3.0.0
raw patch · 4 files changed
Files
- polydata.cabal +11/−7
- src/Data/Poly.hs +0/−215
- src/Data/Poly/Functor.hs +1/−1
- test/Tests.hs +2/−1
polydata.cabal view
@@ -1,10 +1,14 @@ name: polydata-version: 0.1.0.0+version: 0.3.0.0 synopsis: Wrap together data and it's constraints. description:- This package allows one to pass data, particularly functions, together with a constraint which describes how+ This package, together with its dependency [polydata-core](https://hackage.haskell.org/package/polydata-core),+ allows one to pass data, particularly functions, together with a constraint which describes how polymorphic that data is. This constraint can then be used in a generic way to produce quite polymorphic functions,- for example, a "map" function that works on a pair of two different types, + for example, a "map" function that works on a pair of two different types.+ .+ See [Data.Poly](https://hackage.haskell.org/package/polydata-core/docs/Data-Poly.html)+ for a basic tutorial. license: MIT license-file: LICENSE copyright: Clinton Mead (2017)@@ -21,15 +25,15 @@ location: https://github.com/clintonmead/polydata.git library- exposed-modules: Data.Poly, Data.Poly.Function, Data.Poly.Functor- build-depends: base == 4.9.*, indextype == 0.2.*, constraint-manip == 0.1.*+ exposed-modules: Data.Poly.Function, Data.Poly.Functor+ build-depends: base == 4.9.*, indextype == 0.3.*, constraint-manip == 0.1.*, polydata-core == 0.1.* hs-source-dirs: src default-language: Haskell2010 Test-Suite tests type: exitcode-stdio-1.0 main-is: Tests.hs- other-modules: Data.Poly, Data.Poly.Function, Data.Poly.Functor- build-depends: base == 4.9.*, indextype == 0.2.*, constraint-manip == 0.1.*, hspec == 2.4.*+ other-modules: Data.Poly.Function, Data.Poly.Functor+ build-depends: base == 4.9.*, indextype == 0.3.*, constraint-manip == 0.1.*, polydata-core == 0.1.*, hspec == 2.4.* hs-source-dirs: test, src default-language: Haskell2010
− src/Data/Poly.hs
@@ -1,215 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE UndecidableSuperClasses #-}-{-# LANGUAGE FlexibleInstances #-}--{-|-This package allows one to wrap data in a type: 'Poly', which explicitly carries around that's type's polymorphism.--This idea is motivated by this problem:--How does one write a function @g@ such that-->>> g f (x,y) = (f x, f y)--that works for all @a@ and @b@ where @f a@ and @f b@ are valid.--Lets try some approaches in ghci:-->>> let g f (a,b) = (f a, f b)->>> :t-g :: (t1 -> t) -> (t1, t1) -> (t, t)--No good. As untyped function arguments are by default monomorphic, we've forced the pair to have two elements-the same type.--We could try this:-->>> let g (f :: (forall a b. a -> b)) (a,b) = (f a, f b)->>> :t g-g :: (forall a2 b. a2 -> b) -> (a1, a) -> (t1, t)--but the only function with type @(forall a b. a -> b)@ is @undefined@, so that's pretty useless.--Perhaps we could do this:-->>> let g (f :: (forall a. Num a => a -> a)) (a,b) = (f a, f b)->>> :t g-g :: (Num t1, Num t) =>- (forall a. Num a => a -> a) -> (t1, t) -> (t1, t)--This is nice, then we can do something like:-->>> let h = g (+2) (1::Int, 2.5::Float)->>> h-(3,4.5)->>> :t h-h :: (Int, Float)--However, this only works for Numeric functions now.--So what we're going to do is connect the function's constraints with the function itself,-so we get a definition of @g@ like this:--> g :: (c (a -> a'), c (b -> b')) => Poly c -> (a, b) -> (a' -> b')--And indeed you can see polymorphic map function that works on heterogeneous tuples in 'Data.Poly.Functor'.--The 'Poly' type is quite generic, and indeed "Data.Poly.Function"-has some helper functions for constructing polymorphic functions directly.--}-module Data.Poly (- Poly(Poly, getPoly),- GetPolyConstraint,- IsPoly- )-where--import GHC.Exts (Constraint)-{-|-'Poly' has the following data definition:--> data Poly (c :: * -> Constraint) where-> Poly :: { getPoly :: (forall a. c a => a) } -> Poly c--Haddock has trouble parsing it, presumably because it's confused by @(c :: * -> Constraint)@.--Here's a first example, which is a polymorphic version of 'toInteger':--> polyToInteger = Poly @((IsFunc 1) &&& ((Arg 0) `IxConstrainBy` Integral) &&& ((Result 1) `IxIs` Integer)) toInteger--So lets look from left to right for what constraints we're passing to 'polyToInteger':--> (IsFunc 1)--'Control.IndexT.Function.IsFunc' constrains a type to be a function, in this case of one variable--> ((Arg 0) `IxConstrainBy` Integral)--'Control.ConstraintManip.Arg' @0@ specifies the first argument (this is zero based)-'Control.ConstraintManip.IxConstrainBy' constrains the argument given to the constraint given,-in this case 'Integral'--> ((Result 1) `IxIs` Integer)--So the 'Control.ConstraintManip.Result' (of the one argument function) is 'Integer'.--So then we can do:--> getPoly polyToInteger (10 :: Int) -- (10 :: Integer)--Our second example is probably simpler:--> triple = Poly @((IsHomoFunc 1) &&& ((Arg 0) `IxConstrainBy` Num)) (*3)--'Control.IndexT.Function.IsHomoFunc' is like 'Control.IndexT.Function.IsFunc' but ensures the two arguments are the same.--'Control.ConstraintManip.IxConstrainBy' we've already seen. Note that here:--> (Arg 0) `IxConstrainBy` Num--and--> (Result 1) `IxConstrainBy` Num--have the same effect because the first argument and the result are already constrained to have the same type from-'Control.IndexT.Function.IsHomoFunc'.--Two more examples, with two arguments, are:--> add = Poly @((IsHomoFunc 2) &&& ((Arg 0) `IxConstrainBy` Num)) (+)--and--> eq = Poly @((IsHomoArgFunc 2) &&& ((Arg 0) `IxConstrainBy` Eq) &&& ((Result 2) `IxIs` Bool)) (==)--'Control.IndexT.Function.IsHomoArgFunc', unlike 'Control.IndexT.Function.IsHomoFunc', just specifies that the arguments are-identical, the result may be different.--At this point it's probably worth looking at "Data.Poly.Function", which has a range of convience functions for making the-above definitions easier.--If you've now looked at "Data.Poly.Function", you've seen two ways to define the constraints to pass to 'Poly':--1) Use the convienience functions in "Data.Poly.Function"-2) Combine constraints of one variable with '(Control.ConstraintManip.&&&)' as detailed above.--But sometimes these above two methods aren't flexible enough to generate the polymorphic constraint required.--Consider 'Data.Foldable.foldl''--> foldl' :: Foldable t => (b -> a -> b) -> b -> t a -> b--with something this complicated, its sometimes best to define the constraint directly ourselves.-So here it is:--> type FoldConstraint t = (-> IsFunc 3 t, -- A fold is a function of three args-> IndexT 1 t ~ ResultT 3 t, -- The second (i.e. arg 1) is equal to the result-> IsFunc 2 (IndexT 0 t), -- the first argument (i.e. the fold function) is a function of two args-> (IndexT 0 (IndexT 0 t)) ~ (ResultT 2 (IndexT 0 t)), -- the first argument of the function which is the first argument is the same as it's third-> IndexT 1 t ~ (IndexT 0 (IndexT 0 t)), -- also, the first argument of the function which is the first argument is the same as the second argument of the function-> IsData 1 (IndexT 2 t), -- the third argument is a data type with one variable-> Foldable (GetConstructor1 (IndexT 2 t)), -- the constructor of that third argument is Foldable-> IndexC 1 0 (IndexT 2 t) ~ IndexT 1 (IndexT 0 t) -- the parameter to the constructor of Foldable is the same as the second argument of the fold function-> )--You'll want to look at the package "indextype" to get some details on these functions.--But if you go through the above slowly, you'll see that this constraint completely describes the sort of functions that-have the same signature as 'Data.Foldable.foldl''.--So then we can do this:--> class (FoldConstraint t) => FoldConstraintC t-> instance (FoldConstraint t) => FoldConstraintC t->-> pfoldl' = Poly @FoldConstraintC foldl'-> polyFold (Poly foldFunc) =-> (foldFunc (+) 0 [1,2,3], foldFunc (+) 0 [1.5,2.5,3.5], foldFunc (++) "" ["Hello", ", ", "World"])--And we can then do:-->>> (polyFold pfoldl') :: (Int, Float, String)-(6,7.5,"Hello, World")--Note that this wrapping approach preserves the polymorphism until inside the function.--At this point, you may ask, why not just define a new datatype with a polymorphic parameter each time you want to do this?--Well, firstly, you'd have to define a new datatype each time you want to pass a different type of function polymorphically,-which is a bit of boilerplate, although it's arguably less than this.--But more importantly, having a \"constraint\" on the type, instead of the actual type, allows as to use that constraint to-build more complex constraints.--A good example of that is 'Data.Poly.Functor.hmap'.--For complex functions, there can be a lot to write these constraints, but constraints are composable, so you can split-out common parts.--However, I have a feeling there is a mechanical way to generate these constraints using Template Haskell.-This will be my next addition to the library.--}-data Poly (c :: * -> Constraint) where- Poly :: { getPoly :: (forall a. c a => a) } -> Poly c--{-|-Gets the type of the constraint in a 'Poly'--}-type family GetPolyConstraint a :: * -> Constraint where- GetPolyConstraint (Poly c) = c--type family IsPolyT a :: Constraint where- IsPolyT a = a ~ Poly (GetPolyConstraint a)--{--Constraint that asserts @t@ is a @Poly u@ for some @u@.--}-class (IsPolyT a) => IsPoly a-instance (IsPolyT a) => IsPoly a-
src/Data/Poly/Functor.hs view
@@ -7,7 +7,7 @@ {-# LANGUAGE ConstraintKinds #-} module Data.Poly.Functor (- PolyFunctor, hmap, PolyFunctorConstraint+ PolyFunctor(hmap), PolyFunctorConstraint ) where
test/Tests.hs view
@@ -21,6 +21,7 @@ import Control.ConstraintManip import Data.List (foldl')+import Data.Type.Equality (type (~~)) import Test.Hspec (hspec, it, shouldBe) @@ -37,7 +38,7 @@ IndexT 1 t ~ (IndexT 0 (IndexT 0 t)), -- also, the first argument of the function which is the first argument is the same as the second argument of the function IsData 1 (IndexT 2 t), -- the third argument is a data type with one variable Foldable (GetConstructor1 (IndexT 2 t)), -- the constructor of that third argument is Foldable- IndexC 1 0 (IndexT 2 t) ~ IndexT 1 (IndexT 0 t) -- the parameter to the constructor of Foldable is the same as the second argument of the fold function+ IndexC 1 0 (IndexT 2 t) ~~ IndexT 1 (IndexT 0 t) -- the parameter to the constructor of Foldable is the same as the second argument of the fold function ) class (FoldConstraint t) => FoldConstraintC t