record-encode (empty) → 0.1.0.0
raw patch · 8 files changed
+418/−0 lines, 8 filesdep +QuickCheckdep +basedep +generics-sopsetup-changed
Dependencies added: QuickCheck, base, generics-sop, hspec, record-encode, vector
Files
- LICENSE +30/−0
- README.md +32/−0
- Setup.hs +2/−0
- record-encode.cabal +44/−0
- src/Data/Record/Encode.hs +96/−0
- src/Data/Record/Encode/Generics.hs +134/−0
- test/LibSpec.hs +79/−0
- test/Spec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Marco Zocca (c) 2018++All rights reserved.++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 Marco Zocca nor the names of other+ 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+OWNER 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.
+ README.md view
@@ -0,0 +1,32 @@+# record-encode++[](https://travis-ci.org/ocramz/record-encode)++This library provides generic machinery to encode values of some algebraic type as points in a vector space.++Analyzing datasets that have one or more categorical variables (== values having a sum type) typically requires a series of boilerplate transformations, and the `encodeOneHot` function provided here does precisely that.++# Usage example++```+ {-# language DeriveGeneric -#}++ import qualified GHC.Generics as G+ import qualified Generics.SOP as SOP+ + import Data.Record.Encode++ data X = A | B | C deriving (G.Generic)+ instance SOP.Generic X+```++```+ > encodeOneHot B+ [0,1,0]+```++++# Acknowledgements++Gagandeep Bhatia (@gagandeepb) for his GSoC '18 work on `Frames-beam`, Mark Karpov (@mrkkrp) for his Template Haskell tutorial, Anthony Cowley (@acowley) for `vinyl` and `Frames`, @mniip on Freenode #haskell for helping me better understand what can be done with generic programming.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ record-encode.cabal view
@@ -0,0 +1,44 @@+name: record-encode+version: 0.1.0.0+synopsis: Generic encoding of records+description: Generic encoding of records. Provides a single function and two typeclasses to encode values of sum types as one-hot vectors.+homepage: https://github.com/ocramz/record-encode+license: BSD3+license-file: LICENSE+author: Marco Zocca+maintainer: ocramz fripost org+copyright: 2018 Marco Zocca+category: Data+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10+tested-with: GHC == 8.4.3++library+ default-language: Haskell2010+ ghc-options: -Wall + hs-source-dirs: src+ exposed-modules: Data.Record.Encode+ other-modules: Data.Record.Encode.Generics+ build-depends: base >= 4.7 && < 5+ , generics-sop+ , vector+++test-suite spec+ default-language: Haskell2010+ ghc-options: -Wall+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ other-modules: LibSpec+ build-depends: base+ , record-encode+ , generics-sop+ , hspec+ , QuickCheck+ , vector++source-repository head+ type: git+ location: https://github.com/ocramz/record-encode
+ src/Data/Record/Encode.hs view
@@ -0,0 +1,96 @@+{-# language FlexibleContexts #-}+{-# language ScopedTypeVariables #-}+{-# language DeriveGeneric #-}+{-# language ConstraintKinds #-}++{-|+This library provides generic machinery (via GHC.Generics and `generics-sop`) to encode values of some algebraic type as points in a vector space.++Processing datasets that have one or more categorical variables (which in other words are values of a sum type) typically requires a series of boilerplate transformations, and the 'encodeOneHot' function provided here does precisely that.+++== Internals++This library makes use of generic programming to analyze both values and types (see the internal Data.Record.Encode.Generics module).++Initially, it was relying on Template Haskell to analyze /types/, using the the instance generation machinery explained here: <https://markkarpov.com/tutorial/th.html#example-1-instance-generation>+++-}+module Data.Record.Encode (+ -- * One-hot encoding+ encodeOneHot+ , G+ -- -- ** Typeclasses+ -- , GVariants(..)+ ) where++import qualified GHC.Generics as G+import Generics.SOP hiding (Proxy)+import Data.Proxy++import qualified Data.Vector as V+import qualified Data.Vector.Mutable as VM++import Data.Record.Encode.Generics++++data X a = A | B a | C | D | E | F deriving G.Generic+instance Generic (X a)++-- | Constraints necessary to 'encodeOneHot' a value.+--+-- NB: 'GVariants' is an internal typeclass, and this constraint is automatically satisfied if the type is an instance of 'G.Generic'+type G a = (GVariants (G.Rep a), G.Generic a, Generic a)++-- | Computes the one-hot encoding of a value of a sum type.+--+-- The type of the input value must be an instance of 'Generic' (from GHC.Generics) /and/ of 'Generic' (from the `generics-sop` library).+--+-- >>> :set -XDeriveGeneric+--+-- >>> import qualified GHC.Generics as G+-- >>> import qualified Generics.SOP as SOP+-- >>> import Data.Record.Encode+--+-- >>> data X = A | B | C deriving (G.Generic)+-- >>> instance SOP.Generic X+--+-- >>> encodeOneHot B+-- [0,1,0]+encodeOneHot :: forall a . G a => a -> V.Vector Int+encodeOneHot x = oneHot len i where+ len = fromIntegral $ gnconstructors (Proxy :: Proxy a)+ i = gindex $ from x++-- | Create a one-hot vector+oneHot :: Num a =>+ Int -- ^ Vector length+ -> Int -- ^ Index of "1" entry+ -> V.Vector a+oneHot n i = V.create $ do+ vm <- VM.replicate n 0+ VM.write vm i 1+ return vm++-- class Encode i d where+-- -- type ETy d :: *+-- encode :: d -> V.Vector i+-- -- type EIx d :: * +-- -- encode :: d -> V.Vector (ETy d)+-- -- encodeSparse :: d -> V.Vector (EIx d, EIx d, ETy d)++-- -- | Some pointwise decision (e.g. maximum a posteriori) from a mixture of labels to a single value+-- class Decode i d where+-- decode :: V.Vector i -> d+ + +++{- |++from A+ :: (C1 _ U1 :+: (C1 _ U1 +: C1 _ U1)) :+: (C1 _ U1 :+: (C1 _ U1 :+: C1 _ U1))++-}
+ src/Data/Record/Encode/Generics.hs view
@@ -0,0 +1,134 @@+{-# language TypeOperators #-}+{-# language DeriveGeneric #-}+-- {-# language GADTs, DataKinds, PolyKinds, FlexibleInstances, FlexibleContexts #-}+{-# language KindSignatures, FlexibleInstances, FlexibleContexts, ScopedTypeVariables #-}+module Data.Record.Encode.Generics (+ gindex+ , GVariants(..)+ , gnconstructors+ ) where++import qualified GHC.Generics as G+import Data.Proxy++import Generics.SOP hiding (Proxy)+import Generics.SOP.NS+++data X a = A | B a | C | D | E | F deriving G.Generic+instance Generic (X a)++++-- | Compute the structural index of a value of a sum type via its Generic representation+-- e.g.:+-- +-- >>> data S = Sa | Sb | Sc deriving (Eq, Show, G.Generic)+-- >>> instance Generic S+-- +-- >>> gindex $ from Sb+-- 1+gindex :: SOP f xs -> Int+gindex = index_SOP+++-- | Counts the number of outermost constructors ("variants" of a type)+class GVariants (f :: * -> *) where+ vars :: proxy f -> Int++instance GVariants (G.M1 G.C m f) where { vars _ = 1 }++instance GVariants G.V1 where { vars _ = 0 }++instance GVariants f => GVariants (G.M1 G.D m f) where { vars _ = vars (Proxy :: Proxy f) }++instance (GVariants f, GVariants g) => GVariants (f G.:+: g) where { vars _ = vars (Proxy :: Proxy f) + vars (Proxy :: Proxy g) }++-- | Counts the number of outermost constructors+gnconstructors :: forall a . (G.Generic a, GVariants (G.Rep a)) => Proxy a -> Int+gnconstructors _ = vars (Proxy :: Proxy (G.Rep a))+++-- [17:21] <mniip> @let class Variants f where { variants :: proxy f -> Int }; instance Variants (M1 C m f) where { variants _ = 1 }; instance Variants V1 where { variants _ = 0 }; instance Variants f => Variants (M1 D m f) where { variants _ = variants (Proxy :: Proxy f) }; instance (Variants f, Variants g) => Variants (f :+: g) where { variants _ = variants (Proxy :: Proxy f) + variants (Proxy :: Proxy g) }+-- [17:21] <lambdabot> Defined.+-- [17:21] <mniip> @let variants' :: forall a. (Generic a, Variants (Rep a)) => Proxy a -> Int; variants' _ = variants (Proxy :: Proxy (Rep a))+-- [17:21] <lambdabot> Defined.+-- [17:21] <mniip> > variants' (Proxy :: Proxy Bool)+-- [17:21] <lambdabot> 2+-- [17:22] <mniip> > variants' (Proxy :: Proxy (Maybe (Either Bool Bool)))+-- [17:22] <lambdabot> 2+-- [17:22] <mniip> as you can see this only focuses on the outer type constructor+-- [17:22] <mniip> > variants (Proxy :: Proxy Proxy)++++++++++++++++-- -- | ++-- class GEncode i o where+-- gencode :: i x -> Maybe (o x)++-- -- λ> :t fmap to . gencode . from+-- -- +-- -- fmap to . gencode . from+-- -- :: (GEncode (Rep a) (Rep b), Generic b, Generic a) =>+-- -- a -> Maybe b+++-- -- instance GEncode (V1 p) where+-- -- gencode _ = error "Cannot encode V1"++-- -- instance GEncode (U1 p) where+-- -- gencode U1 = error "Cannot encode U1"+++++++-- data OneHot = OH !Int !Int deriving (Eq, Show) ++-- class Encode d where+-- type ETy d :: *+-- type ETy d = OneHot -- (Int, Int)+-- -- encode :: d -> V.Vector (ETy d)+-- encode :: d x -> ETy d++-- -- instance Encode+++ ++++-- instance Encode (V1 p) where+-- encode _ = error "Cannot encode V1"++-- instance Encode (U1 p) where+-- encode U1 = error "Cannot encode U1"++-- instance Encode (K1 i c p) where++-- instance Encode (M1 i c f p) where++-- instance Encode ((f :*: g) p) where++-- instance Encode ((f :+: g) p) where + +++++-- class VG.Vector v x => GEncode' i v x where+-- gencode' :: i x -> Maybe (v Int)
+ test/LibSpec.hs view
@@ -0,0 +1,79 @@+{-# language DeriveGeneric #-}+module LibSpec where++import Test.Hspec+-- import Test.Hspec.QuickCheck++import qualified GHC.Generics as G+import Generics.SOP ++import qualified Data.Vector as V++import Data.Record.Encode+++data X = Xa | Xb | Xc deriving (Eq, Show, G.Generic)+instance Generic X+++++main :: IO ()+main = hspec spec++spec :: Spec+spec =+ describe "Data.Record.Encode" $ do+ it "creates a one-hot encoded vector from a sum type" $ do+ V.length (encodeOneHot Xb) `shouldBe` 3+ + + -- prop "ourAdd is commutative" $ \x y ->+ -- ourAdd x y `shouldBe` ourAdd y x+++++-- data P0 = P0 Bool Char deriving (Eq, Show, G.Generic)+-- -- instance Generic P0++-- deriveCountable ''Bool+-- deriveCountable ''Char+-- -- deriveCountable ''Integer+-- deriveCountable ''P0+++-- -- λ> hcmap (Proxy :: Proxy Show) (mapIK (const ())) $ from $ P0 42 'z'+-- -- SOP (Z (K () :* K () :* Nil))+++-- data Fx = Ax | Bx | Cx deriving (Eq, Show, Enum, G.Generic)+-- -- instance Generic Fx++-- data Fy a = Ay a | By | Cy deriving (Eq, Show, G.Generic)+-- -- instance Generic (Fy a)++-- data Gx = Ax' | Bx' | Cx' deriving (Eq, Show, Enum, G.Generic)+-- -- instance Generic Gx++-- -- | a Product-Of-Sums+-- data P1 a = P1 Fx (Fy a) deriving (Eq, Show, G.Generic)+-- -- instance Generic (P1 a)++-- p10 :: P1 Integer+-- p10 = P1 Ax (Ay 42)++-- -- gp10 :: SOP I '[ '[Fx, Fy Integer] ]+-- -- gp10 = from p10++-- -- λ> from $ P1 Ax (Ay 42)+-- -- SOP (Z (I Ax :* I (Ay 42) :* Nil))+++-- data P2 = P2 Fx Fx deriving (Eq, Show, G.Generic)+-- -- instance Generic P2++++-- -- data Y = Y (X, X) deriving (Eq, Show, G.Generic)+-- -- deriveCountable ''Y
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}