packages feed

javelin-frames 0.1.0.1 → 0.1.0.2

raw patch · 3 files changed

+61/−55 lines, 3 filesdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Frame: DisplayOptions :: Int -> (Row t -> [(String, String)]) -> DisplayOptions t
+ Data.Frame: DisplayOptions :: Int -> (Row t -> [(String, String)]) -> DisplayOptions (t :: (Type -> Type) -> Type)
- Data.Frame: [maximumNumberOfRows] :: DisplayOptions t -> Int
+ Data.Frame: [maximumNumberOfRows] :: DisplayOptions (t :: (Type -> Type) -> Type) -> Int
- Data.Frame: [rowDisplayFunction] :: DisplayOptions t -> Row t -> [(String, String)]
+ Data.Frame: [rowDisplayFunction] :: DisplayOptions (t :: (Type -> Type) -> Type) -> Row t -> [(String, String)]
- Data.Frame: class Frameable t
+ Data.Frame: class Frameable (t :: Type -> Type -> Type)
- Data.Frame: class (Frameable t, Eq (Key t), Ord (Key t)) => Indexable t where {
+ Data.Frame: class (Frameable t, Eq Key t, Ord Key t) => Indexable (t :: Type -> Type -> Type) where {
- Data.Frame: data DisplayOptions t
+ Data.Frame: data DisplayOptions (t :: Type -> Type -> Type)
- Data.Frame: data () => These a b
+ Data.Frame: data These a b
- Data.Frame: defaultDisplayOptions :: Frameable t => DisplayOptions t
+ Data.Frame: defaultDisplayOptions :: forall (t :: (Type -> Type) -> Type). Frameable t => DisplayOptions t
- Data.Frame: type Frame (dt :: (Type -> Type) -> Type) = dt Vector
+ Data.Frame: type Frame (dt :: Type -> Type -> Type) = dt Vector
- Data.Frame: type Key t;
+ Data.Frame: type Key (t :: Type -> Type -> Type);
- Data.Frame: type Row (dt :: (Type -> Type) -> Type) = dt Identity
+ Data.Frame: type Row (dt :: Type -> Type -> Type) = dt Identity

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for javelin-frames +## Release 0.1.0.2++* Fixed markup of tutorial+ ## Release 0.1.0.1  * Explicit support for `containers-0.8`
javelin-frames.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               javelin-frames-version:            0.1.0.1+version:            0.1.0.2 synopsis:           Type-safe data frames based on higher-kinded types. -- description: license:            MIT
src/Data/Frame/Tutorial.hs view
@@ -14,17 +14,17 @@      -- * Quick start     -- $quickstart-    +     -- * Defining types     -- $construction      -- * Advanced indexing     -- $advindexing -    -- * Merging dataframes    +    -- * Merging dataframes     -- ** Zipping     -- $zipping-    +     -- ** Merging by key     -- $merging @@ -41,8 +41,8 @@  This is a short user guide on how to get started using @javelin-frames@. -The central data structure at the heart of this package is the dataframe. -A dataframe, represented by @`Frame` t@ for some record-type @t@, is a +The central data structure at the heart of this package is the dataframe.+A dataframe, represented by @`Frame` t@ for some record-type @t@, is a record whose values are arrays representing columns.  -}@@ -63,7 +63,7 @@  == Defining dataframes -All dataframes must be defined as record types with a type parameter @f@, +All dataframes must be defined as record types with a type parameter @f@, where each field involves the `Column` type family, like so:  >>> :{@@ -80,7 +80,7 @@  >>> deriving instance Frameable Student -Note that the derivation is automatically done for you, through the `Generic` +Note that the derivation is automatically done for you, through the `Generic` instance for @Student@.  One caveat of this approach is that instances for other typeclasses (e.g. `Show`, `Eq`)@@ -100,7 +100,7 @@ Let's now build a dataframe. We use `fromRows` to pack individual students into a dataframe:  >>> :{-    students = fromRows +    students = fromRows              [ MkStudent "Albert" 12 'C'              , MkStudent "Beatrice" 13 'B'              , MkStudent "Clara" 12 'A'@@ -110,13 +110,13 @@ Individual students like @MkStudent "Albert" 23 'C'@ are of type @`Row` Student@, but the dataframe @students@ has type @`Frame` Student@. -We can render the dataframe @students@ into a nice string using `display` +We can render the dataframe @students@ into a nice string using `display` (and print that string using using `putStrLn`):  >>> putStrLn (display students) studentName | studentAge | studentMathGrade ----------- | ---------- | -----------------   "Albert" |         12 |              'C' +   "Albert" |         12 |              'C'  "Beatrice" |         13 |              'B'     "Clara" |         12 |              'A' @@ -145,25 +145,25 @@ There's `mapRows` to map each row to a new structure:  >>> :{-    putStrLn -        $ display -            $ mapRows -                (\(MkStudent name age grade) -> MkStudent name (2*age) grade) +    putStrLn+        $ display+            $ mapRows+                (\(MkStudent name age grade) -> MkStudent name (2*age) grade)                 students :} studentName | studentAge | studentMathGrade ----------- | ---------- | -----------------   "Albert" |         24 |              'C' +   "Albert" |         24 |              'C'  "Beatrice" |         26 |              'B'     "Clara" |         24 |              'A'  There's `filterRows` to keep specific rows:  >>> :{-    putStrLn -        $ display -            $ filterRows -                (\(MkStudent _ _ grade) -> grade < 'C') +    putStrLn+        $ display+            $ filterRows+                (\(MkStudent _ _ grade) -> grade < 'C')                 students :} studentName | studentAge | studentMathGrade@@ -175,9 +175,9 @@  >>> import Data.Char (ord) >>> :{-    foldlRows -        (\acc (MkStudent _ age grade) -> acc + age + ord grade) -        (0 :: Int) +    foldlRows+        (\acc (MkStudent _ age grade) -> acc + age + ord grade)+        (0 :: Int)         students :} 235@@ -190,7 +190,7 @@ === Querying by integer index  Querying by integer index is supported for all dataframes. Use-the `ilookup` function to retrive a row:+the `ilookup` function to retrieve a row:  >>> ilookup 0 students Just (MkStudent {studentName = "Albert", studentAge = 12, studentMathGrade = 'C'})@@ -222,8 +222,8 @@         index = studentName :} -Now, we can use the functions `Frame.lookup` and `at` (similar to `ilookup` -and `iat`, respectively) which take key (in our case, student names) +Now, we can use the functions `Frame.lookup` and `at` (similar to `ilookup`+and `iat`, respectively) which take key (in our case, student names) instead of integer indices.  >>> Frame.lookup "Beatrice" students@@ -239,7 +239,7 @@ and more advanced functionality. -} -{- $construction +{- $construction  To start using the machinery of this package, one must define the appropriate type. Types that can be turned into dataframes are non-empty, higher-kinded, record types.@@ -259,14 +259,14 @@         deriving (Generic) :} -Here, we define a higher-kinded record type @Store@ with four fields. +Here, we define a higher-kinded record type @Store@ with four fields. The type parameter @f@ allows the various functions in this package to switch between a column-oriented format and single-rows.  In practice the type @f@ can only be `Identity` (for a single row), or `Vector` (for a dataframe) -For ergonomics, the type synonym @`Row` t@ is provided to represent a +For ergonomics, the type synonym @`Row` t@ is provided to represent a single row. The type synonym @`Frame` t@ is provided to represent a dataframe. @@ -286,7 +286,7 @@ record types.  In order to access dataframe functionality, we need to ask our code-to generate some boilerplate automatically. We do this by deriving an +to generate some boilerplate automatically. We do this by deriving an instance of `Frameable`:  >>> :set -XDeriveAnyClass@@ -296,7 +296,7 @@ a `Generic` instance. This allows @javelin-frames@ to inspect our type @Store@ and write an implementation of `Frameable` automatically. -** Limitations+== Limitations  At this time, `Frameable` can only be derived for higher-kinded record types that do NOT nest. For example, consider the following hierarchy:@@ -322,7 +322,7 @@ deriving instance Frameable Company @ -Are you an expert in generics who wants to help us figure it out? Feel free to +Are you an expert in generics who wants to help us figure it out? Feel free to [raise an issue or open a pull request](https://github.com/LaurentRDC/javelin). -} @@ -334,12 +334,12 @@ However, many types can naturally be indexed by a subset of the columns, which becomes a key This key is similar to primary keys in databases. -We can derive an instance of `Indexable` to allow us to query data from a +We can derive an instance of `Indexable` to allow us to query data from a dataframe not by the integer index of the rows, but by some key instead. -** Simple keys+== Simple keys -The simplest example is that of keys derived from a single column. +The simplest example is that of keys derived from a single column.  We start with a data definition: @@ -367,7 +367,7 @@ As an example, let's build a dataframe of stores:  >>> :{-    stores = fromRows +    stores = fromRows            [ MkStore "Store A" (Addr "8712 1st Avenue") 787123745            , MkStore "Store B" (Addr "90 2st Street")   188712313            , MkStore "Store C" (Addr "109 3rd Street")  910823870@@ -379,7 +379,7 @@ >>> Frame.lookup 787123745 stores Just (MkStore {storeName = "Store A", storeAddress = Addr "8712 1st Avenue", storeId = 787123745}) -** Compound keys+== Compound keys  Sometimes, it is preferable to identify rows through multiple columns. Again in in analogy with databases, the key is a _compound key_.@@ -396,7 +396,7 @@     deriving instance Show (Row Actor) :} -In this case, we can identify actors by their first and last name, +In this case, we can identify actors by their first and last name, which creates a compound key:  >>> :{@@ -409,7 +409,7 @@ We define some data  >>> :{-    actors = fromRows +    actors = fromRows            [ MkActor "George" "Clooney" 63            , MkActor "Brad"   "Pitt"    61            , MkActor "George" "Takei"   87@@ -472,7 +472,7 @@ >>> :{     putStrLn         $ display-            $ zipRowsWith +            $ zipRowsWith                 (\(MkPet name age) (MkPetInfo _ race) -> MkPetSummary name age race)                 pets                 petInfos@@ -486,14 +486,14 @@   Hmm this doesn't look right, if you manually inspect the two source dataframes.-This is because rows are combined in order. You may want to sort rows using +This is because rows are combined in order. You may want to sort rows using `sortRowsBy` or `sortRowsByUnique`, before applying `zipRowsWith`:  >>> import Data.Function (on) >>> :{     putStrLn         $ display-            $ zipRowsWith +            $ zipRowsWith                 (\(MkPet name age) (MkPetInfo _ race) -> MkPetSummary name age race)                 (sortRowsBy (compare `on` petName) pets)                 (sortRowsBy (compare `on` petInfoName) petInfos)@@ -511,9 +511,9 @@  {- $merging -If you want to merge dataframes whose rows have a natural key (i.e. have an instance of `Indexable`), -then you should take a look at `mergeWithStrategy`. -In this function, for each key present in __either__ dataframe, +If you want to merge dataframes whose rows have a natural key (i.e. have an instance of `Indexable`),+then you should take a look at `mergeWithStrategy`.+In this function, for each key present in __either__ dataframe, a merging strategy is applied. This strategy encodes how the merge should proceed in three cases:  * The key is present in the left dataframe, but not the right;@@ -555,12 +555,12 @@                    [ MkContainerDest 1 "Japan"                    , MkContainerDest 2 "Canada"                    , MkContainerDest 3 "USA"-                   -- missing container destination for #4 +                   -- missing container destination for #4                    , MkContainerDest 5 "France"                    ] :} -We will first start by merging the dataframes only when we have complete data +We will first start by merging the dataframes only when we have complete data (i.e. an inner join). We first define the shape of the resulting dataframe:  >>> :{@@ -580,7 +580,8 @@ * v`That`: The key is present in the right dataframe, but not the left; * v`These`: The key is present in both dataframes (not to be confused with the type constructor t`These`). -In the simplest case, we only care about keys present in both dataframe (v`These`)+In the simplest case, we only care about keys present in both dataframe (`These`)+ >>> :{     completeDataStrategy :: Int -> These (Row ContainerOrigin) (Row ContainerDest) -> Maybe (Row ContainerJourney)     completeDataStrategy containerId (These (MkContainerOrigin _ origin) (MkContainerDest _ dest))@@ -589,14 +590,15 @@ :}  Sidenote: @completeDataStrategy@ is equivalent to `matchedStrategy`. We re-defined it for illustrative purposes.+ >>> :{     putStrLn         $ display-            $ mergeWithStrategy +            $ mergeWithStrategy                 completeDataStrategy                 containerOrigins                 containerDests-:}  +:} containerJourneyId | containerJourneyOrig | containerJourneyDest ------------------ | -------------------- | --------------------                  1 |             "Canada" |              "Japan"@@ -618,18 +620,18 @@     completeDataStrategy' :: Int -> These (Row ContainerOrigin) (Row ContainerDest) -> Maybe (Row ContainerJourney)     completeDataStrategy' containerId (These (MkContainerOrigin _ origin) (MkContainerDest _ dest))         | validCountry origin && validCountry dest = Just $ MkContainerJourney containerId origin dest-        | otherwise                                = Nothing +        | otherwise                                = Nothing     completeDataStrategy' _ _ = Nothing -- not enough data :}  >>> :{     putStrLn         $ display-            $ mergeWithStrategy +            $ mergeWithStrategy                 completeDataStrategy'                 containerOrigins                 containerDests-:}  +:} containerJourneyId | containerJourneyOrig | containerJourneyDest ------------------ | -------------------- | --------------------                  1 |             "Canada" |              "Japan"@@ -654,7 +656,7 @@         | validCountry origin && validCountry dest = Just $ MkPartialContainerJourney containerId (Just origin) dest         | validCountry dest                        = Just $ MkPartialContainerJourney containerId Nothing       dest         | otherwise                                = Nothing-    maybeOriginStrategy containerId (That (MkContainerDest _ dest)) +    maybeOriginStrategy containerId (That (MkContainerDest _ dest))                                                    = Just $ MkPartialContainerJourney containerId Nothing       dest     maybeOriginStrategy _           (This _)       = Nothing -- we require a destination :}@@ -662,7 +664,7 @@ >>> :{     putStrLn         $ display-            $ mergeWithStrategy +            $ mergeWithStrategy                 maybeOriginStrategy                 containerOrigins                 containerDests