Skip to content

Commit dd812f4

Browse files
committed
Support for polymorphic fields
1 parent cf6808b commit dd812f4

File tree

8 files changed

+94
-34
lines changed

8 files changed

+94
-34
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module Test.README where
2424
2525
import Prelude
2626
27-
import Data.Undefined.NoProblem (opt, Opt, undefined, (?), (!))
27+
import Data.Undefined.NoProblem (opt, Opt, (?), (!))
2828
import Data.Undefined.NoProblem.Closed (coerce) as Closed
2929
import Data.Undefined.NoProblem.Open (class Coerce, coerce) as Open
3030
import Effect (Effect)
@@ -123,7 +123,7 @@ optValues = do
123123
124124
assert
125125
$ (consumer { a: "test", b, c: { d: { e: { g, h: "test" }}}})
126-
== (if setup then 45.0 else 0.0)
126+
== (if setup then 25.0 else 0.0)
127127
```
128128

129129
### `NoProblem.Open.*` approach
@@ -164,7 +164,7 @@ When you reach for this type of coercing you can expect a better behavior in the
164164
closedCoerceArray ∷ Effect Unit
165165
closedCoerceArray = do
166166
let
167-
argument = { x: [] :: Array Int }
167+
argument = { x: [] }
168168
169169
r = (Closed.coerce argument :: OptionsWithArrayValue)
170170

packages.dhall

+5-24
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
1-
let upstream =
2-
https://raw.githubusercontent.com/purescript/package-sets/prepare-0.15/src/packages.dhall
3-
sha256:b1c6d06132b7cbf1e93b1e5343044fba1604b50bfbe02d8f80a3002e71569c59
4-
5-
in upstream
6-
with spec =
7-
{ repo = "https://github.com/purescript-spec/purescript-spec.git"
8-
, version = "master"
9-
, dependencies =
10-
[ "aff"
11-
, "ansi"
12-
, "avar"
13-
, "console"
14-
, "exceptions"
15-
, "foldable-traversable"
16-
, "fork"
17-
, "now"
18-
, "pipes"
19-
, "prelude"
20-
, "strings"
21-
, "transformers"
22-
]
23-
}
24-
with metadata.version = "v0.15.0-alpha-05"
1+
let upstream =
2+
https://raw.githubusercontent.com/purescript/package-sets/psc-0.15.0-20220516/src/packages.dhall
3+
sha256:b0bf932de16a10b7d69c6bbbb31ec9ca575237c43a999fa32e59e35eb8c024a1
4+
5+
in upstream

spago.dhall

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
{ name = "undefined-is-not-a-problem"
22
, dependencies =
3-
[ "assert"
3+
[ "arrays"
4+
, "assert"
45
, "effect"
56
, "either"
67
, "foreign"
78
, "maybe"
9+
, "newtype"
810
, "prelude"
911
, "random"
1012
, "tuples"
13+
, "type-equality"
1114
, "unsafe-coerce"
1215
]
1316
, license = "BSD-3-Clause"

src/Data/Undefined/NoProblem.purs

+20
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
module Data.Undefined.NoProblem where
22

33
import Prelude
4+
45
import Data.Eq (class Eq1, eq1)
56
import Data.Maybe (Maybe(..), maybe)
7+
import Data.Newtype (class Newtype)
68
import Foreign (Foreign)
79
import Foreign (isUndefined) as Foreign
810
import Prim.TypeError (Above, Beside, Quote, QuoteLabel, Text, Doc)
911
import Unsafe.Coerce (unsafeCoerce)
1012

13+
-- | Denotes a required record field, the opposite of `Opt`. Note that using
14+
-- | this type is only required for polymorphic fields, due to complicated type
15+
-- | system reasons. Fields that have concrete types are not required to use
16+
-- | `Req`. For example:
17+
-- |
18+
-- | type Args a =
19+
-- | { polymorphicField :: Req a -- `Req` is needed here
20+
-- | , optionalField :: Opt a
21+
-- | , concreteTypedField :: Int -- no need for `Req` here
22+
-- | }
23+
-- |
24+
newtype Req a = Req a
25+
derive instance Newtype (Req a) _
26+
derive newtype instance Show a => Show (Req a)
27+
28+
-- | Denotes an optional value, typically a record field, allowing the consumer
29+
-- | to omit such field when passing the parameter, but still allowing the
30+
-- | receiving function to work with the field.
1131
foreign import data OptType Type
1232

1333
instance eqOptEq a Eq (Opt a) where

src/Data/Undefined/NoProblem/Closed.purs

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module Data.Undefined.NoProblem.Closed where
33
import Data.Either (Either)
44
import Data.Maybe (Maybe)
55
import Data.Tuple (Tuple)
6-
import Data.Undefined.NoProblem (class RenderPath, type (:::), type (<>), type (|>), Opt, SNil, SList)
6+
import Data.Undefined.NoProblem (class RenderPath, type (:::), type (<>), type (|>), Opt, Req, SList, SNil)
77
import Effect (Effect)
88
import Prim.RowList (class RowToList, Cons, Nil, RowList)
99
import Prim.TypeError (class Fail, QuoteLabel, Text)
@@ -65,8 +65,9 @@ class CoerceProp (given :: Type) (expected :: Type) (debugPath ∷ SList) | expe
6565
-- -- |
6666
-- -- | The rest is handling errors and providing intances
6767
-- -- | for well known polymorphic types like `Maybe`, `Either`...
68-
instance coercePropMatch ::
69-
CoerceProp a a p
68+
instance coercePropReq
69+
(TypeEqualsOnPath a b p)
70+
CoerceProp a (Req b) p
7071
else instance coercePropOptValues
7172
(CoerceProp a b p)
7273
CoerceProp (Opt a) (Opt b) p
@@ -97,7 +98,7 @@ else instance coercePropEffect ∷
9798
(CoerceProp a b ("Effect" ::: p))
9899
CoerceProp (Effect a) (Effect b) p
99100
else instance coercePropUnify
100-
(TypeEqualsOnPath a b p)
101+
TypeEqualsOnPath a b p
101102
CoerceProp a b p
102103

103104
class TypeEqualsOnPath (a :: Type) (b :: Type) (pSList) | a b, b a

src/Data/Undefined/NoProblem/Open.purs

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ module Data.Undefined.NoProblem.Open where
33
import Data.Either (Either)
44
import Data.Maybe (Maybe)
55
import Data.Tuple (Tuple)
6-
import Data.Undefined.NoProblem (class RenderPath, class TypeMismatchErr, type (:::), type (<>), type (|>), Opt, SNil, SList)
6+
import Data.Undefined.NoProblem (class RenderPath, class TypeMismatchErr, type (:::), type (<>), type (|>), Opt, Req, SList, SNil)
77
import Effect (Effect)
88
import Prim.RowList (class RowToList, Cons, Nil, RowList)
99
import Prim.TypeError (class Fail, QuoteLabel, Text)
10+
import Type.Equality (class TypeEquals)
1011
import Unsafe.Coerce (unsafeCoerce)
1112

1213
class CoerceProps
@@ -68,7 +69,10 @@ class CoerceProp (given :: Type) (expected :: Type) (debugPath ∷ SList) | expe
6869
-- -- |
6970
-- -- | The rest is handling errors and providing intances
7071
-- -- | for well known polymorphic types like `Maybe`, `Either`...
71-
instance coercePropOptValues
72+
instance coercePropReq
73+
∷ (TypeEquals a b)
74+
CoerceProp a (Req b) p
75+
else instance coercePropOptValues
7276
∷ (CoerceProp a b p)
7377
CoerceProp (Opt a) (Opt b) p
7478
else instance coercePropOptValue

test/Main.purs

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
module Test.Main where
22

33
import Prelude
4+
45
import Effect (Effect)
6+
import Test.PolymorphicFields as PolyFields
57
import Test.README (closedCoerceArray, openCoerceArray, optValues, recordCoerce) as Test.README
68

79
main Effect Unit
@@ -10,3 +12,4 @@ main = do
1012
Test.README.optValues
1113
Test.README.openCoerceArray
1214
Test.README.closedCoerceArray
15+
PolyFields.test

test/PolymorphicFields.purs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module Test.PolymorphicFields where
2+
3+
import Prelude
4+
5+
import Data.Array (catMaybes)
6+
import Data.Maybe (Maybe(..))
7+
import Data.Newtype (unwrap)
8+
import Data.Undefined.NoProblem (Opt, Req(..), toMaybe)
9+
import Data.Undefined.NoProblem.Closed as Closed
10+
import Data.Undefined.NoProblem.Open as Open
11+
import Effect (Effect)
12+
import Test.Assert (assert)
13+
14+
type Args a = { x :: Req a, y :: Opt a, z :: Int }
15+
16+
closedConsumer :: forall args a. Closed.Coerce args (Args a) => Show a => args -> String
17+
closedConsumer args' = show $ catMaybes [Just (unwrap args.x), toMaybe args.y]
18+
where
19+
args = Closed.coerce args' :: Args a
20+
21+
openConsumer :: forall args a. Open.Coerce args (Args a) => Show a => args -> String
22+
openConsumer args' = show $ catMaybes [Just (unwrap args.x), toMaybe args.y]
23+
where
24+
args = Open.coerce args' :: Args a
25+
26+
test :: Effect Unit
27+
test = do
28+
assert $
29+
closedConsumer { x: "foo", z: 42 } == show ["foo"]
30+
assert $
31+
closedConsumer { x: "foo", y: "bar", z: 42 } == show ["foo", "bar"]
32+
assert $
33+
closedConsumer { x: true, z: 42 } == show [true]
34+
assert $
35+
closedConsumer { x: true, y: false, z: 42 } == show [true, false]
36+
assert $ -- Make sure explicitly wrapping the field in `Req` also works
37+
closedConsumer { x: Req 5, z: 42 } == show [5]
38+
39+
assert $
40+
openConsumer { x: "foo", z: 42 } == show ["foo"]
41+
assert $
42+
openConsumer { x: "foo", y: "bar", z: 42 } == show ["foo", "bar"]
43+
assert $
44+
openConsumer { x: true, z: 42 } == show [true]
45+
assert $
46+
openConsumer { x: true, y: false, z: 42 } == show [true, false]
47+
assert $ -- Make sure explicitly wrapping the field in `Req` also works
48+
openConsumer { x: Req 5, z: 42 } == show [5]

0 commit comments

Comments
 (0)