Skip to content

Commit 5134ea6

Browse files
Encourage ExceptT (NonEmptyList ForeignError) directly (#87)
* Use ExceptT (NonEmptyList ForeignError) directly * Copy doc comments to module; discourage usage * Added changelog entry
1 parent bc0d079 commit 5134ea6

File tree

10 files changed

+66
-39
lines changed

10 files changed

+66
-39
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ New features:
1212
Bugfixes:
1313

1414
Other improvements:
15+
- Replace all usages of `F` and `FT` with `Except`/`ExceptT (NonEmptyList ForeignError)` (#87 by @JordanMartinez)
16+
17+
Often times, the `F` and `FT` aliases did more to hinder usage of this library than help. These aliases
18+
haven't been deprecated, but usage of them is now discouraged. All code in the library now uses
19+
the full type that is aliased by `F` and `FT`.
1520

1621
## [v6.0.1](https://github.com/purescript/purescript-foreign/releases/tag/v6.0.1) - 2021-04-20
1722

examples/Applicative.purs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ module Example.Applicative where
22

33
import Prelude
44

5-
import Control.Monad.Except (runExcept)
5+
import Control.Monad.Except (Except, runExcept)
6+
import Data.List.NonEmpty (NonEmptyList)
67
import Effect (Effect)
78
import Effect.Console (logShow)
89
import Example.Util.Value (foreignValue)
9-
import Foreign (F, Foreign, readNumber)
10+
import Foreign (Foreign, ForeignError, readNumber)
1011
import Foreign.Index ((!))
1112

1213
data Point = Point Number Number Number
1314

1415
instance showPoint :: Show Point where
1516
show (Point x y z) = "(Point " <> show [x, y, z] <> ")"
1617

17-
readPoint :: Foreign -> F Point
18+
readPoint :: Foreign -> Except (NonEmptyList ForeignError) Point
1819
readPoint value = do
1920
Point
2021
<$> (value ! "x" >>= readNumber)

examples/Complex.purs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ module Example.Complex where
22

33
import Prelude
44

5-
import Control.Monad.Except (runExcept)
5+
import Control.Monad.Except (Except, runExcept)
6+
import Data.List.NonEmpty (NonEmptyList)
67
import Data.Maybe (Maybe)
78
import Data.Traversable (traverse)
89
import Effect (Effect)
910
import Effect.Console (logShow)
1011
import Example.Util.Value (foreignValue)
11-
import Foreign (F, Foreign, readArray, readBoolean, readNumber, readString, readNullOrUndefined)
12+
import Foreign (Foreign, ForeignError, readArray, readBoolean, readNumber, readString, readNullOrUndefined)
1213
import Foreign.Index ((!))
1314

1415
type SomeObject =
@@ -18,7 +19,7 @@ type SomeObject =
1819
, list :: Array ListItem
1920
}
2021

21-
readSomeObject :: Foreign -> F SomeObject
22+
readSomeObject :: Foreign -> Except (NonEmptyList ForeignError) SomeObject
2223
readSomeObject value = do
2324
foo <- value ! "foo" >>= readString
2425
bar <- value ! "bar" >>= readBoolean
@@ -32,7 +33,7 @@ type ListItem =
3233
, z :: Maybe Number
3334
}
3435

35-
readListItem :: Foreign -> F ListItem
36+
readListItem :: Foreign -> Except (NonEmptyList ForeignError) ListItem
3637
readListItem value = do
3738
x <- value ! "x" >>= readNumber
3839
y <- value ! "y" >>= readNumber

examples/Objects.purs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ module Example.Objects where
22

33
import Prelude
44

5-
import Control.Monad.Except (runExcept)
5+
import Control.Monad.Except (Except, runExcept)
6+
import Data.List.NonEmpty (NonEmptyList)
67
import Effect (Effect)
78
import Effect.Console (logShow)
89
import Example.Util.Value (foreignValue)
9-
import Foreign (F, Foreign, readNumber)
10+
import Foreign (Foreign, ForeignError, readNumber)
1011
import Foreign.Index ((!))
1112

1213
type Point = { x :: Number, y :: Number }
1314

14-
readPoint :: Foreign -> F Point
15+
readPoint :: Foreign -> Except (NonEmptyList ForeignError) Point
1516
readPoint value = do
1617
x <- value ! "x" >>= readNumber
1718
y <- value ! "y" >>= readNumber

examples/ParseErrors.purs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ module Example.ParseErrors where
22

33
import Prelude
44

5-
import Control.Monad.Except (runExcept)
5+
import Control.Monad.Except (Except, runExcept)
6+
import Data.List.NonEmpty (NonEmptyList)
67
import Data.Traversable (traverse)
78
import Effect (Effect)
89
import Effect.Console (logShow)
910
import Example.Util.Value (foreignValue)
10-
import Foreign (F, Foreign, readArray, readBoolean, readNumber, readString)
11+
import Foreign (Foreign, ForeignError, readArray, readBoolean, readNumber, readString)
1112
import Foreign.Index ((!))
1213

1314
newtype Point = Point { x :: Number, y :: Number }
1415

1516
instance showPoint :: Show Point where
1617
show (Point o) = "(Point { x: " <> show o.x <> ", y: " <> show o.y <> " })"
1718

18-
readPoint :: Foreign -> F Point
19+
readPoint :: Foreign -> Except (NonEmptyList ForeignError) Point
1920
readPoint value = do
2021
x <- value ! "x" >>= readNumber
2122
y <- value ! "y" >>= readNumber

examples/Union.purs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ module Example.Union where
22

33
import Prelude
44

5-
import Control.Monad.Except (runExcept)
5+
import Control.Monad.Except (Except, runExcept)
6+
import Data.List.NonEmpty (NonEmptyList)
67
import Effect (Effect)
78
import Effect.Console (logShow)
89
import Example.Util.Value (foreignValue)
9-
import Foreign (F, Foreign, readBoolean, readString)
10+
import Foreign (Foreign, ForeignError, readBoolean, readString)
1011
import Foreign.Index ((!))
1112

1213
data StringList = Nil | Cons String StringList
@@ -15,7 +16,7 @@ instance showStringList :: Show StringList where
1516
show Nil = "Nil"
1617
show (Cons s l) = "(Cons " <> show s <> " " <> show l <> ")"
1718

18-
readStringList :: Foreign -> F StringList
19+
readStringList :: Foreign -> Except (NonEmptyList ForeignError) StringList
1920
readStringList value =
2021
value ! "nil" >>=
2122
readBoolean >>=

examples/Util/Value.purs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ module Example.Util.Value where
22

33
import Prelude
44

5+
import Control.Monad.Except (Except)
56
import Data.Function.Uncurried (Fn3, runFn3)
7+
import Data.List.NonEmpty (NonEmptyList)
68

7-
import Foreign (F, Foreign, ForeignError(..), fail)
9+
import Foreign (Foreign, ForeignError(..), fail)
810

911
foreign import foreignValueImpl :: forall r. Fn3 (String -> r) (Foreign -> r) String r
1012

11-
foreignValue :: String -> F Foreign
13+
foreignValue :: String -> Except (NonEmptyList ForeignError) Foreign
1214
foreignValue json = runFn3 foreignValueImpl (fail <<< ForeignError) pure json

src/Foreign.purs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
-- | This module defines types and functions for working with _foreign_
22
-- | data.
3-
3+
-- |
4+
-- | `ExceptT (NonEmptyList ForeignError) m` is used in this library
5+
-- | to encode possible failures when dealing with foreign data.
6+
-- |
7+
-- | The `Alt` instance for `ExceptT` allows us to accumulate errors,
8+
-- | unlike `Either`, which preserves only the last error.
49
module Foreign
510
( Foreign
611
, ForeignError(..)
@@ -76,12 +81,20 @@ renderForeignError (ErrorAtIndex i e) = "Error at array index " <> show i <> ":
7681
renderForeignError (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> renderForeignError e
7782
renderForeignError (TypeMismatch exp act) = "Type mismatch: expected " <> exp <> ", found " <> act
7883

84+
-- | While this alias is not deprecated, it is recommended
85+
-- | that one use `Except (NonEmptyList ForeignError)` directly
86+
-- | for all future usages rather than this type alias.
87+
-- |
7988
-- | An error monad, used in this library to encode possible failures when
8089
-- | dealing with foreign data.
8190
-- |
8291
-- | The `Alt` instance for `Except` allows us to accumulate errors,
8392
-- | unlike `Either`, which preserves only the last error.
8493
type F = Except MultipleErrors
94+
95+
-- | While this alias is not deprecated, it is recommended
96+
-- | that one use `ExceptT (NonEmptyList ForeignError)` directly
97+
-- | for all future usages rather than this type alias.
8598
type FT = ExceptT MultipleErrors
8699

87100
-- | Coerce any value to the a `Foreign` value.
@@ -107,7 +120,7 @@ foreign import tagOf :: Foreign -> String
107120

108121
-- | Unsafely coerce a `Foreign` value when the value has a particular `tagOf`
109122
-- | value.
110-
unsafeReadTagged :: forall m a. Monad m => String -> Foreign -> FT m a
123+
unsafeReadTagged :: forall m a. Monad m => String -> Foreign -> ExceptT (NonEmptyList ForeignError) m a
111124
unsafeReadTagged tag value
112125
| tagOf value == tag = pure (unsafeFromForeign value)
113126
| otherwise = fail $ TypeMismatch tag (tagOf value)
@@ -122,52 +135,52 @@ foreign import isUndefined :: Foreign -> Boolean
122135
foreign import isArray :: Foreign -> Boolean
123136

124137
-- | Attempt to coerce a foreign value to a `String`.
125-
readString :: forall m. Monad m => Foreign -> FT m String
138+
readString :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m String
126139
readString = unsafeReadTagged "String"
127140

128141
-- | Attempt to coerce a foreign value to a `Char`.
129-
readChar :: forall m. Monad m => Foreign -> FT m Char
142+
readChar :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m Char
130143
readChar value = mapExceptT (map $ either (const error) fromString) (readString value)
131144
where
132145
fromString = maybe error pure <<< toChar
133146
error = Left $ NEL.singleton $ TypeMismatch "Char" (tagOf value)
134147

135148
-- | Attempt to coerce a foreign value to a `Boolean`.
136-
readBoolean :: forall m. Monad m => Foreign -> FT m Boolean
149+
readBoolean :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m Boolean
137150
readBoolean = unsafeReadTagged "Boolean"
138151

139152
-- | Attempt to coerce a foreign value to a `Number`.
140-
readNumber :: forall m. Monad m => Foreign -> FT m Number
153+
readNumber :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m Number
141154
readNumber = unsafeReadTagged "Number"
142155

143156
-- | Attempt to coerce a foreign value to an `Int`.
144-
readInt :: forall m. Monad m => Foreign -> FT m Int
157+
readInt :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m Int
145158
readInt value = mapExceptT (map $ either (const error) fromNumber) (readNumber value)
146159
where
147160
fromNumber = maybe error pure <<< Int.fromNumber
148161
error = Left $ NEL.singleton $ TypeMismatch "Int" (tagOf value)
149162

150163
-- | Attempt to coerce a foreign value to an array.
151-
readArray :: forall m. Monad m => Foreign -> FT m (Array Foreign)
164+
readArray :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m (Array Foreign)
152165
readArray value
153166
| isArray value = pure $ unsafeFromForeign value
154167
| otherwise = fail $ TypeMismatch "array" (tagOf value)
155168

156-
readNull :: forall m. Monad m => Foreign -> FT m (Maybe Foreign)
169+
readNull :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m (Maybe Foreign)
157170
readNull value
158171
| isNull value = pure Nothing
159172
| otherwise = pure (Just value)
160173

161-
readUndefined :: forall m. Monad m => Foreign -> FT m (Maybe Foreign)
174+
readUndefined :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m (Maybe Foreign)
162175
readUndefined value
163176
| isUndefined value = pure Nothing
164177
| otherwise = pure (Just value)
165178

166-
readNullOrUndefined :: forall m. Monad m => Foreign -> FT m (Maybe Foreign)
179+
readNullOrUndefined :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m (Maybe Foreign)
167180
readNullOrUndefined value
168181
| isNull value || isUndefined value = pure Nothing
169182
| otherwise = pure (Just value)
170183

171-
-- | Throws a failure error in `FT`.
172-
fail :: forall m a. Monad m => ForeignError -> FT m a
184+
-- | Throws a failure error in `ExceptT (NonEmptyList ForeignError) m`.
185+
fail :: forall m a. Monad m => ForeignError -> ExceptT (NonEmptyList ForeignError) m a
173186
fail = throwError <<< NEL.singleton

src/Foreign/Index.purs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,36 @@ import Prelude
1717

1818
import Control.Monad.Except.Trans (ExceptT)
1919

20-
import Foreign (Foreign, FT, ForeignError(..), typeOf, isUndefined, isNull, fail)
20+
import Foreign (Foreign, ForeignError(..), typeOf, isUndefined, isNull, fail)
2121
import Data.Function.Uncurried (Fn2, runFn2, Fn4, runFn4)
2222
import Data.List.NonEmpty (NonEmptyList)
2323

2424
-- | This type class identifies types that act like _property indices_.
2525
-- |
2626
-- | The canonical instances are for `String`s and `Int`s.
2727
class Index i m | i -> m where
28-
index :: Foreign -> i -> FT m Foreign
28+
index :: Foreign -> i -> ExceptT (NonEmptyList ForeignError) m Foreign
2929
hasProperty :: i -> Foreign -> Boolean
3030
hasOwnProperty :: i -> Foreign -> Boolean
3131
errorAt :: i -> ForeignError -> ForeignError
3232

3333
class Indexable a m | a -> m where
34-
ix :: forall i. Index i m => a -> i -> FT m Foreign
34+
ix :: forall i. Index i m => a -> i -> ExceptT (NonEmptyList ForeignError) m Foreign
3535

3636
infixl 9 ix as !
3737

3838
foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign r
3939

40-
unsafeReadProp :: forall k m. Monad m => k -> Foreign -> FT m Foreign
40+
unsafeReadProp :: forall k m. Monad m => k -> Foreign -> ExceptT (NonEmptyList ForeignError) m Foreign
4141
unsafeReadProp k value =
4242
runFn4 unsafeReadPropImpl (fail (TypeMismatch "object" (typeOf value))) pure k value
4343

4444
-- | Attempt to read a value from a foreign value property
45-
readProp :: forall m. Monad m => String -> Foreign -> FT m Foreign
45+
readProp :: forall m. Monad m => String -> Foreign -> ExceptT (NonEmptyList ForeignError) m Foreign
4646
readProp = unsafeReadProp
4747

4848
-- | Attempt to read a value from a foreign value at the specified numeric index
49-
readIndex :: forall m. Monad m => Int -> Foreign -> FT m Foreign
49+
readIndex :: forall m. Monad m => Int -> Foreign -> ExceptT (NonEmptyList ForeignError) m Foreign
5050
readIndex = unsafeReadProp
5151

5252
foreign import unsafeHasOwnProperty :: forall k. Fn2 k Foreign Boolean

src/Foreign/Keys.purs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ module Foreign.Keys
77

88
import Prelude
99

10-
import Foreign (FT, Foreign, ForeignError(..), typeOf, isUndefined, isNull, fail)
10+
import Foreign (Foreign, ForeignError(..), typeOf, isUndefined, isNull, fail)
11+
import Control.Monad.Except (ExceptT)
12+
import Data.List.NonEmpty (NonEmptyList)
1113

1214
foreign import unsafeKeys :: Foreign -> Array String
1315

1416
-- | Get an array of the properties defined on a foreign value
15-
keys :: forall m. Monad m => Foreign -> FT m (Array String)
17+
keys :: forall m. Monad m => Foreign -> ExceptT (NonEmptyList ForeignError) m (Array String)
1618
keys value
1719
| isNull value = fail $ TypeMismatch "object" "null"
1820
| isUndefined value = fail $ TypeMismatch "object" "undefined"

0 commit comments

Comments
 (0)