From 23aa0661b5329828c1c1af7979582cf826077dae Mon Sep 17 00:00:00 2001 From: "Thomas R. Honeyman" Date: Thu, 18 Oct 2018 12:12:12 -0700 Subject: [PATCH 1/9] Update fundeps and add tests to prove functions with multiple compatible constraints work properly. --- src/Heterogeneous/Folding.purs | 2 +- test/Record.purs | 55 +++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/Heterogeneous/Folding.purs b/src/Heterogeneous/Folding.purs index 4852d0f..07dba09 100644 --- a/src/Heterogeneous/Folding.purs +++ b/src/Heterogeneous/Folding.purs @@ -31,7 +31,7 @@ class FoldingWithIndex f i x y z | f x y -> z, f -> i where instance functionFoldingWithIndex :: FoldingWithIndex (i -> x -> y -> x) i x y x where foldingWithIndex f = f -class HFoldl f x a b | a -> f x b where +class HFoldl f x a b | f a -> x b where hfoldl :: f -> x -> a -> b class HFoldlWithIndex f x a b | a -> f x b where diff --git a/test/Record.purs b/test/Record.purs index 615543a..f233052 100644 --- a/test/Record.purs +++ b/test/Record.purs @@ -2,10 +2,11 @@ module Test.Record where import Prelude +import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Data.Symbol (class IsSymbol, SProxy, reflectSymbol) import Data.Tuple (Tuple(..)) -import Heterogeneous.Folding (class FoldingWithIndex, class HFoldlWithIndex, hfoldlWithIndex) +import Heterogeneous.Folding (class Folding, class FoldingWithIndex, class HFoldl, class HFoldlWithIndex, hfoldl, hfoldlWithIndex) import Heterogeneous.Mapping (class HMapWithIndex, class Mapping, class MappingWithIndex, hmap, hmapWithIndex, mapping) import Prim.Row as Row import Record as Record @@ -194,3 +195,55 @@ test = , b: Nothing , c: 42 } + +----- +-- Verify that multiple folds can be used in constraints. + +data CountLeft = CountLeft + +instance countLeft :: Folding CountLeft Int (Either a b) Int where + folding CountLeft acc (Left _) = acc + 1 + folding CountLeft acc _ = acc + +countLefts :: forall r. HFoldl CountLeft Int { | r } Int => { | r } -> Int +countLefts = hfoldl CountLeft 0 + +data CountRight = CountRight + +instance countRight :: Folding CountRight Int (Either a b) Int where + folding CountRight acc (Right _) = acc + 1 + folding CountRight acc _ = acc + +countRights :: forall r. HFoldl CountRight Int { | r } Int => { | r } -> Int +countRights = hfoldl CountRight 0 + +countBoth :: forall r. + HFoldl CountLeft Int r Int => + HFoldl CountRight Int r Int => + { | r } -> + Int +countBoth r = countRights r + countLefts r + +----- +-- Verify that multiple folds can be used in constraints. + +data ShowValues = ShowValues + +instance showValues :: + (Show a, IsSymbol sym) => + FoldingWithIndex ShowValues (SProxy sym) String a String + where + foldingWithIndex _ prop str a = pre <> show a + where + pre | str == "" = "" + | otherwise = str <> ", " + +showTwice :: forall r. + HFoldlWithIndex ShowProps String { | r } String => + HFoldlWithIndex ShowValues String { | r } String => + { | r } -> + String +showTwice r = do + let a = "{ " <> hfoldlWithIndex ShowProps "" r <> " }" + b = "[ " <> hfoldlWithIndex ShowValues "" r <> " ]" + a <> b From 155ea0d9ba53ac853df8f1e4557a1b81991f7d3a Mon Sep 17 00:00:00 2001 From: "Thomas R. Honeyman" Date: Thu, 18 Oct 2018 13:09:10 -0700 Subject: [PATCH 2/9] Folding fundeps working (thanks to @monoidmusician for assistance). --- src/Heterogeneous/Folding.purs | 2 +- test/Record.purs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Heterogeneous/Folding.purs b/src/Heterogeneous/Folding.purs index 07dba09..cf3b26c 100644 --- a/src/Heterogeneous/Folding.purs +++ b/src/Heterogeneous/Folding.purs @@ -34,7 +34,7 @@ instance functionFoldingWithIndex :: FoldingWithIndex (i -> x -> y -> x) i x y x class HFoldl f x a b | f a -> x b where hfoldl :: f -> x -> a -> b -class HFoldlWithIndex f x a b | a -> f x b where +class HFoldlWithIndex f x a b | f a -> x b where hfoldlWithIndex :: f -> x -> a -> b newtype ConstFolding f = ConstFolding f diff --git a/test/Record.purs b/test/Record.purs index f233052..39b0e34 100644 --- a/test/Record.purs +++ b/test/Record.purs @@ -136,7 +136,7 @@ traverseRecord :: forall f k rin rout. f { | rout } traverseRecord k = map (flip Builder.build {}) <<< - hfoldlWithIndex (TraverseProp k) (pure identity) + hfoldlWithIndex (TraverseProp k :: TraverseProp f k) (pure identity) test1 :: _ test1 = @@ -186,7 +186,7 @@ sequencePropsOf :: forall f rin rout. { | rin } -> f { | rout } sequencePropsOf = - map (flip Builder.build {}) <<< hfoldlWithIndex SequencePropOf (pure identity) + map (flip Builder.build {}) <<< hfoldlWithIndex (SequencePropOf :: SequencePropOf f) (pure identity) test :: Maybe _ test = @@ -218,8 +218,8 @@ countRights :: forall r. HFoldl CountRight Int { | r } Int => { | r } -> Int countRights = hfoldl CountRight 0 countBoth :: forall r. - HFoldl CountLeft Int r Int => - HFoldl CountRight Int r Int => + HFoldl CountLeft Int { | r } Int => + HFoldl CountRight Int { | r } Int => { | r } -> Int countBoth r = countRights r + countLefts r From 20a965c680b8595fbb3c5db469db2f03d47fe824 Mon Sep 17 00:00:00 2001 From: "Thomas R. Honeyman" Date: Thu, 18 Oct 2018 13:11:43 -0700 Subject: [PATCH 3/9] Add change to Mapping also --- src/Heterogeneous/Mapping.purs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Heterogeneous/Mapping.purs b/src/Heterogeneous/Mapping.purs index f7c6c9b..0524392 100644 --- a/src/Heterogeneous/Mapping.purs +++ b/src/Heterogeneous/Mapping.purs @@ -35,10 +35,10 @@ instance constMapping :: where mappingWithIndex (ConstMapping f) _ = mapping f -class HMap f a b | a -> f b where +class HMap f a b | f a -> b where hmap :: f -> a -> b -class HMapWithIndex f a b | a -> f b where +class HMapWithIndex f a b | f a -> b where hmapWithIndex :: f -> a -> b instance hmapApp :: From b61734d68116b75531e87e80bb662ed1c9352502 Mon Sep 17 00:00:00 2001 From: "Thomas R. Honeyman" Date: Thu, 18 Oct 2018 14:00:39 -0700 Subject: [PATCH 4/9] Add mapping tests --- test/Record.purs | 67 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/test/Record.purs b/test/Record.purs index 39b0e34..247811e 100644 --- a/test/Record.purs +++ b/test/Record.purs @@ -2,13 +2,15 @@ module Test.Record where import Prelude +import Data.Bifunctor (lmap) import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Data.Symbol (class IsSymbol, SProxy, reflectSymbol) import Data.Tuple (Tuple(..)) import Heterogeneous.Folding (class Folding, class FoldingWithIndex, class HFoldl, class HFoldlWithIndex, hfoldl, hfoldlWithIndex) -import Heterogeneous.Mapping (class HMapWithIndex, class Mapping, class MappingWithIndex, hmap, hmapWithIndex, mapping) +import Heterogeneous.Mapping (class HMapWithIndex, class MapRecordWithIndex, class Mapping, class MappingWithIndex, hmap, hmapWithIndex, mapping) import Prim.Row as Row +import Prim.RowList (class RowToList) import Record as Record import Record.Builder (Builder) import Record.Builder as Builder @@ -196,6 +198,69 @@ test = , c: 42 } +----- +-- Verify that multiple maps can be used in constraints + +newtype ReplaceLeft r = ReplaceLeft { | r } + +instance replaceLeftH :: + (IsSymbol sym, Row.Cons sym a x vals) => + MappingWithIndex (ReplaceLeft vals) (SProxy sym) (Either a b) (Either a b) where + mappingWithIndex (ReplaceLeft vals) prop = lmap (const $ Record.get prop vals) + +replaceLeft :: forall rvals rin rout. + HMapWithIndex (ReplaceLeft rvals) { | rin } { | rout } => + { | rvals } -> + { | rin } -> + { | rout } +replaceLeft = + hmapWithIndex <<< ReplaceLeft + +testReplaceLeft :: _ +testReplaceLeft = + { a: "goodbye" + , b: 100 + } + `replaceLeft` + { a: Left "hello" + , b: Right 1 + } + +newtype ReplaceRight r = ReplaceRight { | r } + +instance replaceRightH :: + (IsSymbol sym, Row.Cons sym b x vals) => + MappingWithIndex (ReplaceRight vals) (SProxy sym) (Either a b) (Either a b) where + mappingWithIndex (ReplaceRight vals) prop = map (const $ Record.get prop vals) + +replaceRight :: forall rvals rin rout. + HMapWithIndex (ReplaceRight rvals) { | rin } { | rout } => + { | rvals } -> + { | rin } -> + { | rout } +replaceRight = + hmapWithIndex <<< ReplaceRight + +testReplaceRight :: _ +testReplaceRight = + { a: "goodbye" + , b: 100 + } + `replaceRight` + { a: Left "hello" + , b: Right 1 + } + +testReplaceBoth :: forall rvals rin rout rs. + RowToList rout rs => + HMapWithIndex (ReplaceLeft rvals) { | rin } { | rout } => + HMapWithIndex (ReplaceRight rvals) { | rin } { | rout } => + MapRecordWithIndex rs (ReplaceRight rvals) rout rout => + { | rvals } -> + { | rin } -> + { | rout } +testReplaceBoth vals = replaceRight vals <<< replaceLeft vals + ----- -- Verify that multiple folds can be used in constraints. From 0ba771a2f41c1196f495fb91905d1153f1056289 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Sun, 21 Oct 2018 09:31:33 -0700 Subject: [PATCH 5/9] Cleanup functional dependencies and tests --- src/Heterogeneous/Folding.purs | 12 ++++++------ src/Heterogeneous/Mapping.purs | 8 ++++---- test/HList.purs | 2 +- test/Record.purs | 28 ++++++++++++++-------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Heterogeneous/Folding.purs b/src/Heterogeneous/Folding.purs index cf3b26c..7062c1e 100644 --- a/src/Heterogeneous/Folding.purs +++ b/src/Heterogeneous/Folding.purs @@ -25,16 +25,16 @@ class Folding f x y z | f x y -> z where instance functionFolding :: Folding (x -> y -> x) x y x where folding f = f -class FoldingWithIndex f i x y z | f x y -> z, f -> i where +class FoldingWithIndex f i x y z | f i x y -> z where foldingWithIndex :: f -> i -> x -> y -> z instance functionFoldingWithIndex :: FoldingWithIndex (i -> x -> y -> x) i x y x where foldingWithIndex f = f -class HFoldl f x a b | f a -> x b where +class HFoldl f x a b | f x a -> b where hfoldl :: f -> x -> a -> b -class HFoldlWithIndex f x a b | f a -> x b where +class HFoldlWithIndex f x a b | f x a -> b where hfoldlWithIndex :: f -> x -> a -> b newtype ConstFolding f = ConstFolding f @@ -106,7 +106,7 @@ instance hfoldlRecordWithIndex :: hfoldlWithIndex f x = foldlRecordRowList f x (RLProxy :: RLProxy rl) -class FoldlRecord f x (rl :: RowList) (r :: # Type) b | rl -> f x r b where +class FoldlRecord f x (rl :: RowList) (r :: # Type) b | f x rl -> b, rl -> r where foldlRecordRowList :: f -> x -> RLProxy rl -> { | r } -> b instance foldlRecordCons :: @@ -166,7 +166,7 @@ instance hfoldlVariantWithIndex :: hfoldlWithIndex = foldlVariantRowList (RLProxy :: RLProxy rl) -class FoldlVariant f x (rl :: RowList) (r :: # Type) b | rl -> f x r b where +class FoldlVariant f x (rl :: RowList) (r :: # Type) b | f x rl -> b, rl -> r where foldlVariantRowList :: RLProxy rl -> f -> x -> Variant r -> b instance foldlVariantCons :: @@ -204,7 +204,7 @@ instance hfoldlVariantFWithIndex :: hfoldlWithIndex = foldlVariantFRowList (RLProxy :: RLProxy rl) -class FoldlVariantF f x (rl :: RowList) (r :: # Type) z y | rl -> f x r z y where +class FoldlVariantF f x (rl :: RowList) (r :: # Type) z y | f x rl z -> r y where foldlVariantFRowList :: RLProxy rl -> f -> x -> VariantF r z -> y instance foldlVariantFCons :: diff --git a/src/Heterogeneous/Mapping.purs b/src/Heterogeneous/Mapping.purs index 0524392..947f877 100644 --- a/src/Heterogeneous/Mapping.purs +++ b/src/Heterogeneous/Mapping.purs @@ -21,7 +21,7 @@ import Type.Row (RLProxy(..)) class Mapping f a b | f a -> b where mapping :: f -> a -> b -class MappingWithIndex f i a b | f a -> b, f -> i where +class MappingWithIndex f i a b | f i a -> b where mappingWithIndex :: f -> i -> a -> b instance mappingFunction :: Mapping (a -> b) a b where @@ -80,7 +80,7 @@ instance hmapWithIndexRecord :: Builder.build <<< mapRecordWithIndexBuilder (RLProxy :: RLProxy rl) -class MapRecordWithIndex (xs :: RowList) f (as :: # Type) (bs :: # Type) | xs -> f as bs where +class MapRecordWithIndex (xs :: RowList) f (as :: # Type) (bs :: # Type) | xs f -> bs, xs -> as where mapRecordWithIndexBuilder :: RLProxy xs -> f -> Builder { | as } { | bs } instance mapRecordWithIndexCons :: @@ -138,7 +138,7 @@ instance hmapWithIndexVariant :: hmapWithIndex = mapVariantWithIndex (RLProxy :: RLProxy rl) -class MapVariantWithIndex (xs :: RowList) f (as :: # Type) (bs :: # Type) | xs -> f as bs where +class MapVariantWithIndex (xs :: RowList) f (as :: # Type) (bs :: # Type) | xs f -> bs, xs -> as where mapVariantWithIndex :: RLProxy xs -> f -> Variant as -> Variant bs instance mapVariantWithIndexCons :: @@ -177,7 +177,7 @@ instance hmapWithIndexVariantF :: hmapWithIndex = mapVariantFWithIndex (RLProxy :: RLProxy rl) -class MapVariantFWithIndex (xs :: RowList) f (as :: # Type) (bs :: # Type) x y | xs -> f as bs x y where +class MapVariantFWithIndex (xs :: RowList) f (as :: # Type) (bs :: # Type) x y | xs f x -> as bs y where mapVariantFWithIndex :: RLProxy xs -> f -> VariantF as x -> VariantF bs y instance mapVariantFWithIndexCons :: diff --git a/test/HList.purs b/test/HList.purs index 4ebe366..ad7f57c 100644 --- a/test/HList.purs +++ b/test/HList.purs @@ -89,7 +89,7 @@ showWithIndex :: forall hlist. hlist -> Array (Tuple Int String) showWithIndex = - hfoldlWithIndex ShowWithIndex [] + hfoldlWithIndex ShowWithIndex ([] :: Array (Tuple Int String)) testShow :: _ testShow = diff --git a/test/Record.purs b/test/Record.purs index 247811e..d30f604 100644 --- a/test/Record.purs +++ b/test/Record.purs @@ -8,9 +8,8 @@ import Data.Maybe (Maybe(..)) import Data.Symbol (class IsSymbol, SProxy, reflectSymbol) import Data.Tuple (Tuple(..)) import Heterogeneous.Folding (class Folding, class FoldingWithIndex, class HFoldl, class HFoldlWithIndex, hfoldl, hfoldlWithIndex) -import Heterogeneous.Mapping (class HMapWithIndex, class MapRecordWithIndex, class Mapping, class MappingWithIndex, hmap, hmapWithIndex, mapping) +import Heterogeneous.Mapping (class HMapWithIndex, class Mapping, class MappingWithIndex, hmap, hmapWithIndex, mapping) import Prim.Row as Row -import Prim.RowList (class RowToList) import Record as Record import Record.Builder (Builder) import Record.Builder as Builder @@ -132,13 +131,13 @@ instance traverseProp :: traverseRecord :: forall f k rin rout. Applicative f => - HFoldlWithIndex (TraverseProp f k) (f (Builder {} {})) { | rin } (f (Builder {} { | rout})) => + HFoldlWithIndex (TraverseProp f k) (f (Builder {} {})) { | rin } (f (Builder {} { | rout })) => k -> { | rin } -> f { | rout } traverseRecord k = - map (flip Builder.build {}) <<< - hfoldlWithIndex (TraverseProp k :: TraverseProp f k) (pure identity) + map (flip Builder.build {}) + <<< hfoldlWithIndex (TraverseProp k :: TraverseProp f k) (pure identity :: f (Builder {} {})) test1 :: _ test1 = @@ -188,7 +187,8 @@ sequencePropsOf :: forall f rin rout. { | rin } -> f { | rout } sequencePropsOf = - map (flip Builder.build {}) <<< hfoldlWithIndex (SequencePropOf :: SequencePropOf f) (pure identity) + map (flip Builder.build {}) + <<< hfoldlWithIndex (SequencePropOf :: SequencePropOf f) (pure identity :: f (Builder {} {})) test :: Maybe _ test = @@ -211,7 +211,7 @@ instance replaceLeftH :: replaceLeft :: forall rvals rin rout. HMapWithIndex (ReplaceLeft rvals) { | rin } { | rout } => { | rvals } -> - { | rin } -> + { | rin } -> { | rout } replaceLeft = hmapWithIndex <<< ReplaceLeft @@ -236,7 +236,7 @@ instance replaceRightH :: replaceRight :: forall rvals rin rout. HMapWithIndex (ReplaceRight rvals) { | rin } { | rout } => { | rvals } -> - { | rin } -> + { | rin } -> { | rout } replaceRight = hmapWithIndex <<< ReplaceRight @@ -251,15 +251,15 @@ testReplaceRight = , b: Right 1 } -testReplaceBoth :: forall rvals rin rout rs. - RowToList rout rs => - HMapWithIndex (ReplaceLeft rvals) { | rin } { | rout } => - HMapWithIndex (ReplaceRight rvals) { | rin } { | rout } => - MapRecordWithIndex rs (ReplaceRight rvals) rout rout => +testReplaceBoth :: forall rvals rin rmid rout. + HMapWithIndex (ReplaceLeft rvals) { | rin } { | rmid } => + HMapWithIndex (ReplaceRight rvals) { | rmid } { | rout } => { | rvals } -> { | rin } -> { | rout } -testReplaceBoth vals = replaceRight vals <<< replaceLeft vals +testReplaceBoth vals = + (replaceLeft vals :: { | rin } -> { | rmid }) >>> + (replaceRight vals :: { | rmid } -> { | rout }) ----- -- Verify that multiple folds can be used in constraints. From 46d67588cbd7ba293dd259aa2dab23b29fd6c5e8 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Sun, 21 Oct 2018 12:17:16 -0700 Subject: [PATCH 6/9] No need for annotations --- test/Record.purs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Record.purs b/test/Record.purs index d30f604..6d8b740 100644 --- a/test/Record.purs +++ b/test/Record.purs @@ -258,8 +258,7 @@ testReplaceBoth :: forall rvals rin rmid rout. { | rin } -> { | rout } testReplaceBoth vals = - (replaceLeft vals :: { | rin } -> { | rmid }) >>> - (replaceRight vals :: { | rmid } -> { | rout }) + replaceLeft vals >>> replaceRight vals ----- -- Verify that multiple folds can be used in constraints. From 02f20c29fde0b7b5072a34b0a2107de3bb195d8c Mon Sep 17 00:00:00 2001 From: "Thomas R. Honeyman" Date: Sun, 21 Oct 2018 15:20:22 -0700 Subject: [PATCH 7/9] Simplify types in replaceBoth test --- test/Record.purs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Record.purs b/test/Record.purs index 6d8b740..d0ccd63 100644 --- a/test/Record.purs +++ b/test/Record.purs @@ -251,12 +251,12 @@ testReplaceRight = , b: Right 1 } -testReplaceBoth :: forall rvals rin rmid rout. - HMapWithIndex (ReplaceLeft rvals) { | rin } { | rmid } => - HMapWithIndex (ReplaceRight rvals) { | rmid } { | rout } => +testReplaceBoth :: forall rvals r. + HMapWithIndex (ReplaceLeft rvals) { | r } { | r } => + HMapWithIndex (ReplaceRight rvals) { | r } { | r } => { | rvals } -> - { | rin } -> - { | rout } + { | r } -> + { | r } testReplaceBoth vals = replaceLeft vals >>> replaceRight vals From e95b573d2d6cc57d1a21d0f9a7ec8f036903d9ef Mon Sep 17 00:00:00 2001 From: "Thomas R. Honeyman" Date: Sun, 21 Oct 2018 15:35:17 -0700 Subject: [PATCH 8/9] Update readme to include a note on annotations --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 9e2a3b7..0566604 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,30 @@ showRecord { a: "foo" , b: 42 , c: false } "{ a: \"foo\", b: 42, c: false }" ``` +## Helping type inference along + +The compiler will not always be able to infer all types for the maps and folds +we write for heterogeneous types. That's because it will attempt to determine +an output from any combination of folding function, accumulator, and input (for +folds) or mapping function and input (for maps). This ensures that multiple +mapping and folding operations can be supported for the same underlying input, +but has the downside that the compiler will not infer these types. + +You will need to provide annotations for any folding, mapping, accumulator, and +input types that are not determined in some other way. + +For example, this sample `showWithIndex` function for showing a heterogeneous +list requires an annotation for the accumulator type: + +```purescript +showWithIndex :: forall hlist. + HFoldlWithIndex ShowWithIndex (Array (Tuple Int String)) hlist (Array (Tuple Int String)) => + hlist -> + Array (Tuple Int String) +showWithIndex = + hfoldlWithIndex ShowWithIndex ([] :: Array (Tuple Int String)) +``` + ## Documentation - Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-heterogeneous). From 873f922c0385d74837143f74f03d36951243c5f2 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Sun, 21 Oct 2018 15:36:35 -0700 Subject: [PATCH 9/9] Adjust indentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0566604..3f8b10e 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ showWithIndex :: forall hlist. hlist -> Array (Tuple Int String) showWithIndex = - hfoldlWithIndex ShowWithIndex ([] :: Array (Tuple Int String)) + hfoldlWithIndex ShowWithIndex ([] :: Array (Tuple Int String)) ``` ## Documentation