packages feed

row-types 0.2.0.0 → 0.2.1.0

raw patch · 10 files changed

+491/−399 lines, 10 filesdep +constraintsdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: constraints

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Row: restrict :: forall r r'. (Forall r Unconstrained1, Subset r r') => Rec r' -> Rec r
+ Data.Row.Internal: [As] :: forall c f a t. (a ~ f t, c t) => As c f a
+ Data.Row.Internal: as :: IsA c f a => As c f a
+ Data.Row.Internal: class IsA c f a
+ Data.Row.Internal: data As c f a
+ Data.Row.Internal: instance forall k1 k2 (c :: k2 -> GHC.Types.Constraint) (a :: k2) (f :: k2 -> k1). c a => Data.Row.Internal.IsA c f (f a)
+ Data.Row.Internal: mapForall :: forall f c ρ. Forall ρ c :- Forall (Map f ρ) (IsA c f)
+ Data.Row.Internal: uniqueMap :: forall f ρ. AllUniqueLabels ρ :- AllUniqueLabels (Map f ρ)
+ Data.Row.Records: fromLabelsMapA :: forall c f g ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ) => (forall l a. (KnownSymbol l, c a) => Label l -> f (g a)) -> f (Rec (Map g ρ))
+ Data.Row.Variants: restrict :: forall r r'. (WellBehaved r, Subset r r') => Var r' -> Maybe (Var r)
+ Data.Row.Variants: split :: forall s r. (WellBehaved s, Subset s r) => Var r -> Either (Var s) (Var (r .\\ s))
- Data.Row.Records: update :: KnownSymbol l => Label l -> a -> Rec r -> Rec r
+ Data.Row.Records: update :: (KnownSymbol l, (r .! l) ≈ a) => Label l -> a -> Rec r -> Rec r
- Data.Row.Variants: update :: KnownSymbol l => Label l -> a -> Var r -> Var r
+ Data.Row.Variants: update :: (KnownSymbol l, (r .! l) ≈ a) => Label l -> a -> Var r -> Var r

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@ +## 0.2.1.0 [2018-03-20]+- Bug Fix: The type of 'update' for both Record and Variant now enforce the newly inserted type is correct.+- New: Add 'restrict' and 'split' for Variants.  +- - Removed 'restrict' from Data.Row export list.+- New: Added support for universally quantified rows: 'mapForall' and 'uniqueMap'.+- Added very simple test suite.+ ## 0.2.0.0 [2018-02-12] - Initial Release
Data/Row.hs view
@@ -34,7 +34,6 @@   , type (.==), (.==), pattern (:==), unSingleton   -- ** Restriction   , type (.-), (.-)-  , restrict   -- ** Query   , type (.!), (.!)   -- ** Disjoint union
Data/Row/Internal.hs view
@@ -30,9 +30,15 @@   , toKey   , type (≈)   , WellBehaved, AllUniqueLabels, Zip, Map, Subset, Disjoint++  , mapForall+  , uniqueMap+  , IsA(..)+  , As(..)   ) where +import Data.Constraint import Data.Functor.Const import Data.Proxy import Data.String (IsString (fromString))@@ -40,7 +46,8 @@ import qualified Data.Text as Text import Data.Type.Equality (type (==)) -import GHC.Exts -- needed for constraints kinds+import qualified Unsafe.Coerce as UNSAFE+ import GHC.OverloadedLabels import GHC.TypeLits import qualified GHC.TypeLits as TL@@ -193,6 +200,39 @@                -- ^ The fold             -> f r  -- ^ The input structure             -> g r++-- * Says that there exists a `t` such that `a ~ f t` and `c t`.+data As c f a where+  As :: forall c f a t. (a ~ f t, c t) => As c f a++class IsA c f a where+  as :: As c f a++instance c a => IsA c f (f a) where+  as = As++newtype MapForall c f (r :: Row *) = MapForall { unMapForall :: Dict (Forall (Map f r) (IsA c f)) }++-- | This allows us to derive a `Forall (Map f r) ..` from a `Forall r ..`.+mapForall :: forall f c ρ. Forall ρ c :- Forall (Map f ρ) (IsA c f)+mapForall = Sub $ unMapForall $ metamorph @ρ @c @(Const ()) @(MapForall c f) @(Const ()) Proxy empty uncons cons $ Const ()+  where empty :: Const () Empty -> MapForall c f Empty+        empty _ = MapForall Dict++        uncons :: forall l t r. (KnownSymbol l, c t)+               => Label l -> Const () ('R (l :-> t ': r)) -> (Const () t, Const () ('R r))+        uncons _ _ = (Const (), Const ())++        cons :: forall ℓ τ ρ. (KnownSymbol ℓ, c τ, FoldStep ℓ τ ρ)+             => Label ℓ -> Const () τ -> MapForall c f ('R ρ)+             -> MapForall c f ('R (ℓ :-> τ ': ρ))+        cons _ _ (MapForall Dict) =+           case UNSAFE.unsafeCoerce @(Dict Unconstrained) @(Dict (FoldStep ℓ (f τ) (MapR f ρ))) Dict of+             Dict -> MapForall Dict++-- | Map preserves uniqueness of labels.+uniqueMap :: forall f ρ. AllUniqueLabels ρ :- AllUniqueLabels (Map f ρ)+uniqueMap = Sub $ UNSAFE.unsafeCoerce @(Dict Unconstrained) Dict  instance Forall (R '[]) c where   {-# INLINE metamorph #-}
Data/Row/Records.hs view
@@ -25,7 +25,7 @@   , empty   , type (.==), (.==), pattern (:==), unSingleton   , default', defaultA-  , fromLabels, fromLabelsA+  , fromLabels, fromLabelsA, fromLabelsMapA   -- ** Extension   , extend, Extend, Lacks, type (.\)   -- ** Restriction@@ -62,6 +62,7 @@  import Control.DeepSeq (NFData(..), deepseq) +import qualified Data.Constraint as Constraint import Data.Functor.Compose import Data.Functor.Const import Data.Functor.Identity@@ -145,7 +146,7 @@ extend (toKey -> l) a (OR m) = OR $ M.insert l (HideType a) m  -- | Update the value associated with the label.-update :: KnownSymbol l => Label l -> a -> Rec r -> Rec r+update :: (KnownSymbol l, r .! l ≈ a) => Label l -> a -> Rec r -> Rec r update (toKey -> l) a (OR m) = OR $ M.adjust f l m where f = const (HideType a)  -- | Focus on the value associated with the label.@@ -376,3 +377,11 @@                => Label ℓ -> Const () τ -> Compose f Rec ('R ρ) -> Compose f Rec ('R (ℓ :-> τ ': ρ))         doCons l _ (Compose r) = Compose $ unsafeInjectFront l <$> mk l <*> r +-- | Initialize a record that is produced by a `Map`.+fromLabelsMapA :: forall c f g ρ. (Applicative f, Forall ρ c, AllUniqueLabels ρ)+               => (forall l a. (KnownSymbol l, c a) => Label l -> f (g a)) -> f (Rec (Map g ρ))+fromLabelsMapA f = fromLabelsA @(IsA c g) @f @(Map g ρ) inner+                Constraint.\\ mapForall @g @c @ρ+                Constraint.\\ uniqueMap @g @ρ+   where inner :: forall l a. (KnownSymbol l, IsA c g a) => Label l -> f a+         inner l = case as @c @g @a of As -> f l
Data/Row/Variants.hs view
@@ -22,6 +22,7 @@   , update, focus, Modify, rename, Rename   -- * Destruction   , impossible, trial, trial', multiTrial, view+  , restrict, split   -- ** Types for destruction   , type (.!), type (.-), type (.\\), type (.==)   -- * Row operations@@ -126,7 +127,7 @@  -- | If the variant exists at the given label, update it to the given value. -- Otherwise, do nothing.-update :: KnownSymbol l => Label l -> a -> Var r -> Var r+update :: (KnownSymbol l, r .! l ≈ a) => Label l -> a -> Var r -> Var r update (toKey -> l') a (OneOf l x) = OneOf l $ if l == l' then HideType a else x  -- | If the variant exists at the given label, focus on the value associated with it.@@ -160,6 +161,15 @@ -- myShow (view y -> Just s) = "String of "++s @ view :: KnownSymbol l => Label l -> Var r -> Maybe (r .! l) view = flip trial'++-- | Split a variant into two sub-variants.+split :: forall s r. (WellBehaved s, Subset s r) => Var r -> Either (Var s) (Var (r .\\ s))+split (OneOf l a) | l `elem` labels @s @Unconstrained1 = Left  $ OneOf l a+                  | otherwise                          = Right $ OneOf l a++-- | Arbitrary variant restriction.  Turn a variant into a subset of itself.+restrict :: forall r r'. (WellBehaved r, Subset r r') => Var r' -> Maybe (Var r)+restrict = either Just (pure Nothing) . split   {--------------------------------------------------------------------
− Examples.lhs
@@ -1,390 +0,0 @@-> {-# LANGUAGE OverloadedLabels #-}-> module Examples where->-> import Data.Row-> import qualified Data.Row.Records as Rec-> import qualified Data.Row.Variants as Var--In this example file, we will explore how to create and use records and variants.-----------------------------------------------------------------------------------  LABELS-----------------------------------------------------------------------------------To begin, we will briefly discuss creating labels -- their use will follow.--The most basic way to create a label is through construction with a type signature:-- x = Label :: Label "x"--With the above definition, x is a label for the field x.  Using type applications,-this can be shortened to:-- x = Label @"x"--And with OverloadedLabels, one can just write:-- #x--We will use the OverloadedLabels notation in these examples.-----------------------------------------------------------------------------------  LENS-----------------------------------------------------------------------------------Records and variants play nicely with the lens library if we additionally import-Data.Row.Lens from the row-types-lens "orphan instance" library.  Each overloaded-label is also a Lens for a record and a Traversal for variants.  Thus, .! can be-replaced with ^. and trial can be made infix with ^?.  Additionally, update-can be made infix:--update #x v r === r & #x .~ v--And because of the power of lens, it's easy to make modifications rather than-just update:--update #x (f $ r .! #x) r === r & #x %~ f--Lens is not included with row-types by default, but using it can make row-types-much friendlier.-----------------------------------------------------------------------------------  RECORDS-----------------------------------------------------------------------------------With some labels defined, let's begin with records.  To start, let's create a-record representing the Cartesian coordinates of the origin.  To do this,-we use the .== operator to initialize values in a record, and we separate each-initialized value with the .+ operator.Notice that the value level code uses the-same operators as the type level code.--> origin :: Rec ("x" .== Double .+ "y" .== Double )-> origin = #x .== 0 .+ #y .== 0--Note that, although we wrote the type explicitly, GHC has no problem inferring-it exactly.--If we show this at the repl, we see:-λ> origin-{ x=0.0, y=0.0 }--Of course, as an extensible record, the order that we build it shouldn't matter,-and indeed, it doesn't.  Consider the following variation:--> origin' :: Rec ("y" .== Double .+ "x" .== Double)-> origin' = #y .== 0 .+ #x .== 0--If we show this at the repl, we see:--λ> origin2-{ x=0.0, y=0.0 }--Indeed, the two values are indistinguishable:--λ> origin == origin'-True--Now, let's expand upon our record.  Why stop at two dimensions when we can make-a record in three dimensions.--> origin3D = #z .== 0.0 .+ origin--Once again, the type is inferred for us, and the record is exactly as expected.--In fact, we can do this generally.  The following function takes a name and a-record and adds the "name" field to that record with the given name.--> named :: r .\ "name" => a -> Rec r -> Rec ("name" .== a .+ r)-> named s r = #name .== s .+ r--Note that we require that the record we are naming must not have a "name" field-already.  Overlapping labels within a single record/variant is strictly forbidden.--Let's say we want to get the values out of the record.  Simple selection is achieved-with the .! operator, like so:--λ> origin .! #x-0.0--and we can use this to write whatever we want.  Here is a function for calculating-Euclidean distance from the origin to a point:--> distance :: (Floating t, r .! "y" ≈ t, r .! "x" ≈ t) => Rec r -> t-> distance p = sqrt $ p .! #x * p .! #x + p .! #y * p .! #y--Once again, the type of distance is entirely inferrable, but we write it here for-convenience.  This works exactly as expected:--λ> distance origin-0.0-λ> distance origin3D-0.0-λ> distance (named "2D" origin)-0.0--Of course, that wasn't very interesting when our only points are at the origin-already.  We could make new records representing new points, but instead, let's-write a function to move the points we have:--> move :: (Num (r .! "x"), Num (r .! "y"))->      => Rec r -> r .! "x" -> r .! "y" -> Rec r-> move p dx dy = Rec.update #x (p .! #x + dx) $->                Rec.update #y (p .! #y + dy) p--Here, we're using the Rec.update operator to update the value at the label x by-adding dx to it, and then we do the same for y.-We can see it work in practice:--λ> move origin 3 4-{ x=3.0, y=4.0 }-λ> distance (move origin 3 4)-5.0-λ> distance (move (named "2D" origin3D) 5 12)-13.0--Note that if we were using row-types-lens and the lens library, we could write-move as:--move p dx dy = p & #x +~ dx & #y +~ dy--So far, we created an origin point in 2d and then one in 3d, but what if we are-adventurous mathematicians who want to have points in a space with some arbitrary-number of dimensions.  We could write out each of the 0s necessary, but there's-an easier way to initialize a record:--> origin4 :: Rec ("x" .== Double .+ "y" .== Double .+ "z" .== Double .+ "w" .== Double)-> origin4 = Rec.default' @Num 0--Finally, we have come to a case where GHC cannot infer the type signature, and how-could it!  The type is providing crucial information about the shape of the record.-Regardless, with the type provided, it works exactly as expected:--λ> origin4-{ w=0.0, x=0.0, y=0.0, z=0.0 }--While we have added names or further fields, we can also choose to forget-information in a record.  To remove a particular label, one can use the .--operator, like so:--> unName :: HasType "name" a r => Rec r -> Rec (r .- "name")-> unName r = r .- #name--For larger changes, it is easier to use the restrict function.  The following-function will take a record that contains both an x and y coordinate and remove-the rest of the fields from it.--> get2D :: (r ≈ "x" .== Double .+ "y" .== Double, Disjoint r rest)->       => Rec (r .+ rest)->       -> Rec r-> get2D r = restrict r--GHC is a little finicky about the type operators and constraints -- indeed, this-type signature will fail to type check if the parentheses around-  "x" .== Double .+ "y" .== Double-in the argument are missing.  Of course, a type signature is not necessary when-using type applications, and the function can instead be written as:--> get2D' r = restrict @("x" .== Double .+ "y" .== Double) r--with no trouble.  Yet another altnerative is to match directly on the values desired-using the :== and :+ record patterns:--> get2D'' :: (r ≈ "x" .== Double .+ "y" .== Double, Disjoint r rest)->         => Rec (r .+ rest)->         -> Rec r-> get2D'' ((Label :: Label "x") :== n1 :+ (Label :: Label "y") :== n2 :+ _)->           = #x .== n1 .+ #y .== n2--(Note that overloaded labels cannot be used in the patterns, so the notation is-unfortunately bloated by types.  Also, the type operators are left associated,-so the "_" must go on the right, and the type signature is unforunately necessary.)--All three of the get2D functions behave the same.-----------------------------------------------------------------------------------  VARIANTS----------------------------------------------------------------------------------Let's move on from records to variants.  In many ways, variants are quite similar,-as might be expected given that variants are dual to records.  The types look-almost the same, and some of the operators are shared as well.  However,-construction and destruction are obviously different.--Creating a variant can be done with IsJust:--> v,v' :: Var ("y" .== String .+ "x" .== Integer)-> v  = IsJust #x 1-> v' = IsJust #y "Foo"--Here, the type is necessary to specify what concrete type the variant is (when-using AllowAmbiguousTypes, the type is not always needed, but it would be needed-to e.g. show the variant).  In the simple case of a variant of just one type,-the simpler singleton function can be used:--> v2 = Var.singleton #x 1--Now, the type can be easily derived by GHC.  We can show variants as easily as-records:--λ> v-{x=1}-λ> v'-{y="Foo"}-λ> v2-{x=1}--Once created, a variant can be expanded by using type applications and the-diversify function.--> v3 = diversify @("y" .== String) v2-> v4 = diversify @("y" .== String .+ "z" .== Double) v2--λ> :t v4-v4 :: Var ('R '["x" ':-> Integer, "y" ':-> String, "z" ':-> Double])-λ> v == v3-True--Doing the above equality test does raise the question of how equality works on-variants.  For instance, v2 and v3 both look the same when you show them, and they-both have the same value inside, but can we test them for equality?  Indeed, we can't,-precisely because their types are different: it is a type error to even try to-check whether they're equal:--λ> v2 == v3-error:-    • Couldn't match type ‘'["y" ':-> [Char]]’ with ‘'[]’-      Expected type: Var ('R '["x" ':-> Integer])-        Actual type: Var ('R '["x" ':-> Integer] .+ ("y" .== String))-    • In the second argument of ‘(==)’, namely ‘v3’-      In the expression: v2 == v3-      In an equation for ‘it’: it = v2 == v3--This may look a little scary, but it's actually a pretty useful message.  Essentially,-it's expecting a variant that can only be an Integer at label "x", but it found one-that could also be a String at label "y".  So, comparing v2 and v3 is not allowed,-but since v3 now has the same labels as v1, that comparison is fine:--λ> v == v3-True-λ> v == IsJust #x 3-False-λ> v == v'-False-λ> v == IsJust #y "fail"-False--(Also note here that using IsJust without a type signature is fine because the correct-type can be easily inferred due to v's type.)--What can you do with a variant?  The only way to really use one is to get the value-out, and to do that, you must trial it:--λ> trial v #x-Left 1-λ> trial v #y-Right {x=1}-λ> trial v' #x-Right {y="Foo"}-λ> trial v' #y-Left "Foo"--If trialing at a label l succeeds, then it provides a Left value of the value at l.-If not, it provides a Right value of the variant with this label removed---since the-trial failed, we now can be sure that the value is not from l.--For ease of use in view patterns, Variants also exposes the view function.-(If using lens, this can be replaced with preview.)  With it, we can write a-function like this:--> myShow :: (r .! "y" ≈ String, Show (r .! "x")) => Var r -> String-> myShow (Var.view #x -> Just n) = "Int of "++show n-> myShow (Var.view #y -> Just s) = "String of "++s-> myShow _ = "Unknown"--λ> myShow v-"Int of 1"-λ> myShow v'-"String of Foo"-λ> myShow (just #z 3 :: Var ("y" .== String .+ "x" .== Integer .+ "z" .== Double))-"Unknown"--This can also be achieved with the IsJust pattern synonym in much the same way:--> myShow' :: (WellBehaved r, r .! "y" ≈ String, Show (r .! "x")) => Var r -> String-> myShow' (IsJust (Label :: Label "x") n) = "Int of "++show n-> myShow' (IsJust (Label :: Label "y") s) = "String of "++s-> myShow' _ = "Unknown"--In either case, the type signature is once again totally derivable.--There are two minor annoyances with this.  First, it's fairly common to want to define-a function like myShow to be exhaustive in the variant's cases, but to do this,-you must manually provide a type signature:--> myShowRestricted :: Var ("y" .== String .+ "x" .== Integer) -> String-> myShowRestricted (Var.view #x -> Just n) = "Int of "++show n-> myShowRestricted (Var.view #y -> Just s) = "String of "++s-> myShowRestricted _ = error "Unreachable"--The second blemish can be seen in this restricted version of myShow.  Even though-we know from the type that we've covered all the posibilities of the variant, GHC-will generate a "non-exhaustive pattern match" warning without the final line.-(This is true for the pattern synonym version too.)--One way to avoid this problem is to use switch.  The switch operator takes a variant-and a record such that for each label that the variant has, the record has a function-at that label that consumes the value the variant has and produces a value in a-common type.  Essentially, switch "applies" the variant to the record to produce-an output value.--> --myShowRestricted' :: Var ("y" .== String .+ "x" .== Integer) -> String-> myShowRestricted' v = switch v $->      #x .== (\n -> "Int of "++show n)->   .+ #y .== (\s -> "String of "++s)--This version of myShow needs neither a type signature (it is inferred exactly) nor-a default "unreachable" case.  However, we no longer have the benefit of Haskell's-standard pattern matching.----A more powerful version of trial is multiTrial, which tests for multiple labels-at once.  With this, you can wholesale change the type of the variant to any (valid)-variant type you would like.  Of course, there needs to be a recourse if the variant-you provide is not expressible in the type you want, so multiTrial returns an Either-of the type you want or a Variant of the leftovers.  Consider the examples:--λ> :t multiTrial @("x" .== Double .+ "y" .== String) v-multiTrial @("x" .== Double .+ "y" .== String) v-  :: Either-       (Var ('R '["x" ':-> Double, "y" ':-> String]))-       (Var ('R '["x" ':-> Integer]))-λ> multiTrial @("x" .== Double .+ "y" .== String) v-Right {x=1}--λ> :t multiTrial @("x" .== Double .+ "y" .== String) v'-multiTrial @("x" .== Double .+ "y" .== String) v'-  :: Either-       (Var ('R '["x" ':-> Double, "y" ':-> String]))-       (Var ('R '["x" ':-> Integer]))-λ> multiTrial @("x" .== Double .+ "y" .== String) v'-Left {y="Foo"}--Thus, multiTrial can be used not only to arbitrarily split apart a variant, but-also to change unused label associations (in this case, we changed the variant-from one where "x" is an Integer to one where it's a Double).---Here are two functions you can define over variants.  The type constraints are a little-ugly (the type equalities are necessary but annoying).--> also :: Disjoint xs ys->      => (Var xs -> a)->      -> (Var ys -> a)->      -> Var (xs .+ ys) -> a-> also f1 f2 e = case multiTrial e of->   Left  e' -> f1 e'->   Right e' -> f2 e'--> joinVarLists :: forall x y. (WellBehaved (x .+ y), x .+ y ≈ y .+ x)->              => [Var x] -> [Var y] -> [Var (x .+ y)]-> joinVarLists xs ys = map (diversify @y) xs ++ map (diversify @x) ys
README.md view
@@ -3,7 +3,7 @@  Row-types is a library of open records and variants for Haskell using closed type families and type literals (among other things...).-See [Examples.lhs](https://raw.githubusercontent.com/target/row-types/master/Examples.lhs)+See [examples/Examples.lhs](https://raw.githubusercontent.com/target/row-types/master/examples/Examples.lhs) for an overview of how this library can be used.  This work is a branch from CTRex [1,2] with other inspiration from data-diverse [3].
+ examples/Examples.lhs view
@@ -0,0 +1,390 @@+> {-# LANGUAGE OverloadedLabels #-}+> module Examples where+>+> import Data.Row+> import qualified Data.Row.Records as Rec+> import qualified Data.Row.Variants as Var++In this example file, we will explore how to create and use records and variants.++--------------------------------------------------------------------------------+  LABELS+--------------------------------------------------------------------------------++To begin, we will briefly discuss creating labels -- their use will follow.++The most basic way to create a label is through construction with a type signature:++ x = Label :: Label "x"++With the above definition, x is a label for the field x.  Using type applications,+this can be shortened to:++ x = Label @"x"++And with OverloadedLabels, one can just write:++ #x++We will use the OverloadedLabels notation in these examples.++--------------------------------------------------------------------------------+  LENS+--------------------------------------------------------------------------------++Records and variants play nicely with the lens library if we additionally import+Data.Row.Lens from the row-types-lens "orphan instance" library.  Each overloaded+label is also a Lens for a record and a Traversal for variants.  Thus, .! can be+replaced with ^. and trial can be made infix with ^?.  Additionally, update+can be made infix:++update #x v r === r & #x .~ v++And because of the power of lens, it's easy to make modifications rather than+just update:++update #x (f $ r .! #x) r === r & #x %~ f++Lens is not included with row-types by default, but using it can make row-types+much friendlier.++--------------------------------------------------------------------------------+  RECORDS+--------------------------------------------------------------------------------++With some labels defined, let's begin with records.  To start, let's create a+record representing the Cartesian coordinates of the origin.  To do this,+we use the .== operator to initialize values in a record, and we separate each+initialized value with the .+ operator.Notice that the value level code uses the+same operators as the type level code.++> origin :: Rec ("x" .== Double .+ "y" .== Double )+> origin = #x .== 0 .+ #y .== 0++Note that, although we wrote the type explicitly, GHC has no problem inferring+it exactly.++If we show this at the repl, we see:+λ> origin+{ x=0.0, y=0.0 }++Of course, as an extensible record, the order that we build it shouldn't matter,+and indeed, it doesn't.  Consider the following variation:++> origin' :: Rec ("y" .== Double .+ "x" .== Double)+> origin' = #y .== 0 .+ #x .== 0++If we show this at the repl, we see:++λ> origin2+{ x=0.0, y=0.0 }++Indeed, the two values are indistinguishable:++λ> origin == origin'+True++Now, let's expand upon our record.  Why stop at two dimensions when we can make+a record in three dimensions.++> origin3D = #z .== 0.0 .+ origin++Once again, the type is inferred for us, and the record is exactly as expected.++In fact, we can do this generally.  The following function takes a name and a+record and adds the "name" field to that record with the given name.++> named :: r .\ "name" => a -> Rec r -> Rec ("name" .== a .+ r)+> named s r = #name .== s .+ r++Note that we require that the record we are naming must not have a "name" field+already.  Overlapping labels within a single record/variant is strictly forbidden.++Let's say we want to get the values out of the record.  Simple selection is achieved+with the .! operator, like so:++λ> origin .! #x+0.0++and we can use this to write whatever we want.  Here is a function for calculating+Euclidean distance from the origin to a point:++> distance :: (Floating t, r .! "y" ≈ t, r .! "x" ≈ t) => Rec r -> t+> distance p = sqrt $ p .! #x * p .! #x + p .! #y * p .! #y++Once again, the type of distance is entirely inferrable, but we write it here for+convenience.  This works exactly as expected:++λ> distance origin+0.0+λ> distance origin3D+0.0+λ> distance (named "2D" origin)+0.0++Of course, that wasn't very interesting when our only points are at the origin+already.  We could make new records representing new points, but instead, let's+write a function to move the points we have:++> move :: (Num (r .! "x"), Num (r .! "y"))+>      => Rec r -> r .! "x" -> r .! "y" -> Rec r+> move p dx dy = Rec.update #x (p .! #x + dx) $+>                Rec.update #y (p .! #y + dy) p++Here, we're using the Rec.update operator to update the value at the label x by+adding dx to it, and then we do the same for y.+We can see it work in practice:++λ> move origin 3 4+{ x=3.0, y=4.0 }+λ> distance (move origin 3 4)+5.0+λ> distance (move (named "2D" origin3D) 5 12)+13.0++Note that if we were using row-types-lens and the lens library, we could write+move as:++move p dx dy = p & #x +~ dx & #y +~ dy++So far, we created an origin point in 2d and then one in 3d, but what if we are+adventurous mathematicians who want to have points in a space with some arbitrary+number of dimensions.  We could write out each of the 0s necessary, but there's+an easier way to initialize a record:++> origin4 :: Rec ("x" .== Double .+ "y" .== Double .+ "z" .== Double .+ "w" .== Double)+> origin4 = Rec.default' @Num 0++Finally, we have come to a case where GHC cannot infer the type signature, and how+could it!  The type is providing crucial information about the shape of the record.+Regardless, with the type provided, it works exactly as expected:++λ> origin4+{ w=0.0, x=0.0, y=0.0, z=0.0 }++While we have added names or further fields, we can also choose to forget+information in a record.  To remove a particular label, one can use the .-+operator, like so:++> unName :: HasType "name" a r => Rec r -> Rec (r .- "name")+> unName r = r .- #name++For larger changes, it is easier to use the restrict function.  The following+function will take a record that contains both an x and y coordinate and remove+the rest of the fields from it.++> get2D :: (r ≈ "x" .== Double .+ "y" .== Double, Disjoint r rest)+>       => Rec (r .+ rest)+>       -> Rec r+> get2D r = Rec.restrict r++GHC is a little finicky about the type operators and constraints -- indeed, this+type signature will fail to type check if the parentheses around+  "x" .== Double .+ "y" .== Double+in the argument are missing.  Of course, a type signature is not necessary when+using type applications, and the function can instead be written as:++> get2D' r = Rec.restrict @("x" .== Double .+ "y" .== Double) r++with no trouble.  Yet another altnerative is to match directly on the values desired+using the :== and :+ record patterns:++> get2D'' :: (r ≈ "x" .== Double .+ "y" .== Double, Disjoint r rest)+>         => Rec (r .+ rest)+>         -> Rec r+> get2D'' ((Label :: Label "x") :== n1 :+ (Label :: Label "y") :== n2 :+ _)+>           = #x .== n1 .+ #y .== n2++(Note that overloaded labels cannot be used in the patterns, so the notation is+unfortunately bloated by types.  Also, the type operators are left associated,+so the "_" must go on the right, and the type signature is unforunately necessary.)++All three of the get2D functions behave the same.++--------------------------------------------------------------------------------+  VARIANTS+--------------------------------------------------------------------------------+Let's move on from records to variants.  In many ways, variants are quite similar,+as might be expected given that variants are dual to records.  The types look+almost the same, and some of the operators are shared as well.  However,+construction and destruction are obviously different.++Creating a variant can be done with IsJust:++> v,v' :: Var ("y" .== String .+ "x" .== Integer)+> v  = IsJust #x 1+> v' = IsJust #y "Foo"++Here, the type is necessary to specify what concrete type the variant is (when+using AllowAmbiguousTypes, the type is not always needed, but it would be needed+to e.g. show the variant).  In the simple case of a variant of just one type,+the simpler singleton function can be used:++> v2 = Var.singleton #x 1++Now, the type can be easily derived by GHC.  We can show variants as easily as+records:++λ> v+{x=1}+λ> v'+{y="Foo"}+λ> v2+{x=1}++Once created, a variant can be expanded by using type applications and the+diversify function.++> v3 = diversify @("y" .== String) v2+> v4 = diversify @("y" .== String .+ "z" .== Double) v2++λ> :t v4+v4 :: Var ('R '["x" ':-> Integer, "y" ':-> String, "z" ':-> Double])+λ> v == v3+True++Doing the above equality test does raise the question of how equality works on+variants.  For instance, v2 and v3 both look the same when you show them, and they+both have the same value inside, but can we test them for equality?  Indeed, we can't,+precisely because their types are different: it is a type error to even try to+check whether they're equal:++λ> v2 == v3+error:+    • Couldn't match type ‘'["y" ':-> [Char]]’ with ‘'[]’+      Expected type: Var ('R '["x" ':-> Integer])+        Actual type: Var ('R '["x" ':-> Integer] .+ ("y" .== String))+    • In the second argument of ‘(==)’, namely ‘v3’+      In the expression: v2 == v3+      In an equation for ‘it’: it = v2 == v3++This may look a little scary, but it's actually a pretty useful message.  Essentially,+it's expecting a variant that can only be an Integer at label "x", but it found one+that could also be a String at label "y".  So, comparing v2 and v3 is not allowed,+but since v3 now has the same labels as v1, that comparison is fine:++λ> v == v3+True+λ> v == IsJust #x 3+False+λ> v == v'+False+λ> v == IsJust #y "fail"+False++(Also note here that using IsJust without a type signature is fine because the correct+type can be easily inferred due to v's type.)++What can you do with a variant?  The only way to really use one is to get the value+out, and to do that, you must trial it:++λ> trial v #x+Left 1+λ> trial v #y+Right {x=1}+λ> trial v' #x+Right {y="Foo"}+λ> trial v' #y+Left "Foo"++If trialing at a label l succeeds, then it provides a Left value of the value at l.+If not, it provides a Right value of the variant with this label removed---since the+trial failed, we now can be sure that the value is not from l.++For ease of use in view patterns, Variants also exposes the view function.+(If using lens, this can be replaced with preview.)  With it, we can write a+function like this:++> myShow :: (r .! "y" ≈ String, Show (r .! "x")) => Var r -> String+> myShow (Var.view #x -> Just n) = "Int of "++show n+> myShow (Var.view #y -> Just s) = "String of "++s+> myShow _ = "Unknown"++λ> myShow v+"Int of 1"+λ> myShow v'+"String of Foo"+λ> myShow (just #z 3 :: Var ("y" .== String .+ "x" .== Integer .+ "z" .== Double))+"Unknown"++This can also be achieved with the IsJust pattern synonym in much the same way:++> myShow' :: (WellBehaved r, r .! "y" ≈ String, Show (r .! "x")) => Var r -> String+> myShow' (IsJust (Label :: Label "x") n) = "Int of "++show n+> myShow' (IsJust (Label :: Label "y") s) = "String of "++s+> myShow' _ = "Unknown"++In either case, the type signature is once again totally derivable.++There are two minor annoyances with this.  First, it's fairly common to want to define+a function like myShow to be exhaustive in the variant's cases, but to do this,+you must manually provide a type signature:++> myShowRestricted :: Var ("y" .== String .+ "x" .== Integer) -> String+> myShowRestricted (Var.view #x -> Just n) = "Int of "++show n+> myShowRestricted (Var.view #y -> Just s) = "String of "++s+> myShowRestricted _ = error "Unreachable"++The second blemish can be seen in this restricted version of myShow.  Even though+we know from the type that we've covered all the posibilities of the variant, GHC+will generate a "non-exhaustive pattern match" warning without the final line.+(This is true for the pattern synonym version too.)++One way to avoid this problem is to use switch.  The switch operator takes a variant+and a record such that for each label that the variant has, the record has a function+at that label that consumes the value the variant has and produces a value in a+common type.  Essentially, switch "applies" the variant to the record to produce+an output value.++> --myShowRestricted' :: Var ("y" .== String .+ "x" .== Integer) -> String+> myShowRestricted' v = switch v $+>      #x .== (\n -> "Int of "++show n)+>   .+ #y .== (\s -> "String of "++s)++This version of myShow needs neither a type signature (it is inferred exactly) nor+a default "unreachable" case.  However, we no longer have the benefit of Haskell's+standard pattern matching.++++A more powerful version of trial is multiTrial, which tests for multiple labels+at once.  With this, you can wholesale change the type of the variant to any (valid)+variant type you would like.  Of course, there needs to be a recourse if the variant+you provide is not expressible in the type you want, so multiTrial returns an Either+of the type you want or a Variant of the leftovers.  Consider the examples:++λ> :t multiTrial @("x" .== Double .+ "y" .== String) v+multiTrial @("x" .== Double .+ "y" .== String) v+  :: Either+       (Var ('R '["x" ':-> Double, "y" ':-> String]))+       (Var ('R '["x" ':-> Integer]))+λ> multiTrial @("x" .== Double .+ "y" .== String) v+Right {x=1}++λ> :t multiTrial @("x" .== Double .+ "y" .== String) v'+multiTrial @("x" .== Double .+ "y" .== String) v'+  :: Either+       (Var ('R '["x" ':-> Double, "y" ':-> String]))+       (Var ('R '["x" ':-> Integer]))+λ> multiTrial @("x" .== Double .+ "y" .== String) v'+Left {y="Foo"}++Thus, multiTrial can be used not only to arbitrarily split apart a variant, but+also to change unused label associations (in this case, we changed the variant+from one where "x" is an Integer to one where it's a Double).+++Here are two functions you can define over variants.  The type constraints are a little+ugly (the type equalities are necessary but annoying).++> also :: Disjoint xs ys+>      => (Var xs -> a)+>      -> (Var ys -> a)+>      -> Var (xs .+ ys) -> a+> also f1 f2 e = case multiTrial e of+>   Left  e' -> f1 e'+>   Right e' -> f2 e'++> joinVarLists :: forall x y. (WellBehaved (x .+ y), x .+ y ≈ y .+ x)+>              => [Var x] -> [Var y] -> [Var (x .+ y)]+> joinVarLists xs ys = map (diversify @y) xs ++ map (diversify @x) ys
row-types.cabal view
@@ -1,5 +1,5 @@ Name:                row-types-Version:             0.2.0.0+Version:             0.2.1.0 License:             MIT License-file:        LICENSE Author:              Daniel Winograd-Cort, Matthew Farkas-Dyck@@ -18,7 +18,7 @@   label.  extra-source-files:-  Examples.lhs+  examples/Examples.lhs   README.md   CHANGELOG.md   LICENSE@@ -26,6 +26,7 @@  Library   Build-Depends: base >= 2 && < 5,+                 constraints,                  deepseq >= 1.4,                  hashable >= 1.2,                  unordered-containers >= 0.2,@@ -36,7 +37,6 @@                  , Data.Row.Variants                  , Data.Row.Switch   ghc-options: -W-  other-modules:   Extensions: AllowAmbiguousTypes,               ConstraintKinds,               DataKinds,@@ -78,6 +78,27 @@               TypeApplications,               TypeFamilies,               TypeOperators++test-suite test+  type: exitcode-stdio-1.0+  main-is: Main.hs+  hs-source-dirs: tests, examples+  ghc-options: -W+  other-modules: Examples+  build-depends: base >= 2 && < 6+               , row-types+  Extensions: AllowAmbiguousTypes,+              DataKinds,+              FlexibleContexts,+              OverloadedLabels,+              PatternSynonyms,+              RankNTypes,+              ScopedTypeVariables,+              TypeApplications,+              TypeFamilies,+              TypeOperators,+              ViewPatterns+  source-repository head     type:     git
+ tests/Main.hs view
@@ -0,0 +1,6 @@++module Main where++import Examples ()++main = putStrLn "Test passes if Examples.lhs type-checks."