diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 1c6afb9..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "extends": "eslint:recommended", - "rules": { - "strict": [2, "global"], - "block-scoped-var": 2, - "consistent-return": 2, - "eqeqeq": [2, "smart"], - "guard-for-in": 2, - "no-caller": 2, - "no-extend-native": 2, - "no-loop-func": 2, - "no-new": 2, - "no-param-reassign": 2, - "no-return-assign": 2, - "no-unused-expressions": 2, - "no-use-before-define": 2, - "radix": [2, "always"], - "indent": [2, 2], - "quotes": [2, "double"], - "semi": [2, "always"] - } -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c69237a..66bd3aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,30 +6,33 @@ on: pull_request: branches: [master] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - uses: purescript-contrib/setup-purescript@main - with: - purescript: "unstable" + - uses: actions/checkout@v6 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v6 with: - node-version: "14.x" + node-version: "24" - name: Install dependencies - run: | - npm install -g bower - npm install - bower install --production + run: npm install - name: Build source - run: npm run-script build + run: npm run build - name: Run tests - run: | - bower install - npm run-script test --if-present + run: npm test + + - name: Run benchmarks + run: npm run bench + + - name: Lint + run: npm run lint + + - name: Check formatting + run: npm run format:check diff --git a/.gitignore b/.gitignore index 7224331..e634a28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ /.* !/.gitignore -!/.eslintrc.json +!/.tidyrc.json +!/.oxfmtrc.json !/.github/ -package-lock.json +/package-lock.json +/spago.lock /bower_components/ /node_modules/ /output/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..757fd64 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,3 @@ +{ + "trailingComma": "es5" +} diff --git a/.tidyrc.json b/.tidyrc.json new file mode 100644 index 0000000..9e39cf1 --- /dev/null +++ b/.tidyrc.json @@ -0,0 +1,10 @@ +{ + "importSort": "ide", + "importWrap": "source", + "indent": 2, + "operatorsFile": null, + "ribbon": 1, + "typeArrowPlacement": "first", + "unicode": "never", + "width": null +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 661ab3a..0d71c28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [Unreleased](https://github.com/purescript/purescript-foreign-object/compare/v4.1.1...HEAD) Breaking changes: @@ -12,30 +12,42 @@ Bugfixes: Other improvements: +## [v4.1.1](https://github.com/purescript/purescript-foreign-object/releases/tag/v4.1.1) - 2026-10-17 + +Other improvements: + +- Migrate from Bower/Pulp to Spago and update CI, linting, formatting, and the benchmark runner (#31 by @thomashoneyman). + ## [v4.1.0](https://github.com/purescript/purescript-foreign-object/releases/tag/v4.1.0) - 2022-10-27 New features: + - Add `fromFoldableWithIndex` (#30 by @ptrfrncsmrph) ## [v4.0.0](https://github.com/purescript/purescript-foreign-object/releases/tag/v4.0.0) - 2022-04-27 Breaking changes: + - Migrate FFI to ES modules (#27 by @JordanMartinez) ## [v3.0.0](https://github.com/purescript/purescript-foreign-object/releases/tag/v3.0.0) - 2021-02-26 Breaking changes: + - Added support for PureScript 0.14 and dropped support for all previous versions (#17) - Fixed the `Semigroup` instance for `Foreign.Object.Object` (#19) New features: + - Added roles declarations to allow safe coercions (#16) - Added `unionWith` (#21) Bugfixes: + - Fixed test using `Arbitrary` instance for `Instruction String v` (#22) Other improvements: + - Migrated CI to GitHub Actions and updated installation instructions to use Spago (#20) - Added a CHANGELOG.md file and pull request template (#24, #25) - Improved docs for `runST` (#12) diff --git a/README.md b/README.md index e210358..39db7fc 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,7 @@ example = do -- convert homogeneous records to Object converted = FO.fromHomogeneous { a: 1, b: 2, c: 3} -- check that the converted is equal to a regularly built Object - built - = FO.empty + built = FO.empty # FO.insert "a" 1 # FO.insert "b" 2 # FO.insert "c" 3 diff --git a/bench/spago.yaml b/bench/spago.yaml new file mode 100644 index 0000000..07a55f2 --- /dev/null +++ b/bench/spago.yaml @@ -0,0 +1,15 @@ +package: + name: foreign-object-bench + build: + strict: true + pedanticPackages: true + run: + main: Bench.Main + dependencies: + - console: ">=6.0.0 <7.0.0" + - effect: ">=4.0.0 <5.0.0" + - foreign-object: "*" + - lists: ">=7.0.0 <8.0.0" + - minibench: ">=4.0.0 <5.0.0" + - prelude: ">=6.0.0 <7.0.0" + - tuples: ">=7.0.0 <8.0.0" diff --git a/bench/Bench/Foreign/Object.purs b/bench/src/Bench/Foreign/Object.purs similarity index 81% rename from bench/Bench/Foreign/Object.purs rename to bench/src/Bench/Foreign/Object.purs index 36a906e..1dd90b4 100644 --- a/bench/Bench/Foreign/Object.purs +++ b/bench/src/Bench/Foreign/Object.purs @@ -1,13 +1,13 @@ module Bench.Foreign.Object where import Prelude -import Effect (Effect) -import Effect.Console (log) -import Performance.Minibench (benchWith) -import Data.Tuple (Tuple(..)) import Data.List as L +import Data.Tuple (Tuple(..)) +import Effect (Effect) +import Effect.Console (log) import Foreign.Object as Object +import Performance.Minibench (benchWith) benchForeignObject :: Effect Unit benchForeignObject = do @@ -16,9 +16,10 @@ benchForeignObject = do where benchFromFoldable = do - let natStrs = show <$> L.range 0 99999 - natPairs = (flip Tuple) unit <$> natStrs - shortPairList = L.take 10000 natPairs + let + natStrs = show <$> L.range 0 99999 + natPairs = (flip Tuple) unit <$> natStrs + shortPairList = L.take 10000 natPairs log $ "fromFoldable (" <> show (L.length shortPairList) <> ")" benchWith 100 \_ -> Object.fromFoldable shortPairList diff --git a/bench/Bench/Main.purs b/bench/src/Bench/Main.purs similarity index 100% rename from bench/Bench/Main.purs rename to bench/src/Bench/Main.purs index 3016e30..abcdb7f 100644 --- a/bench/Bench/Main.purs +++ b/bench/src/Bench/Main.purs @@ -1,10 +1,10 @@ module Bench.Main where import Prelude -import Effect (Effect) -import Effect.Console (log) import Bench.Foreign.Object (benchForeignObject) +import Effect (Effect) +import Effect.Console (log) main :: Effect Unit main = do diff --git a/bower.json b/bower.json deleted file mode 100644 index e72c61c..0000000 --- a/bower.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "purescript-foreign-object", - "homepage": "https://github.com/purescript/purescript-foreign-object", - "authors": [ - "Phil Freeman ", - "John A. De Goes " - ], - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-foreign-object.git" - }, - "license": "BSD-3-Clause", - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "dependencies": { - "purescript-arrays": "^7.0.0", - "purescript-foldable-traversable": "^6.0.0", - "purescript-functions": "^6.0.0", - "purescript-gen": "^4.0.0", - "purescript-lists": "^7.0.0", - "purescript-maybe": "^6.0.0", - "purescript-prelude": "^6.0.0", - "purescript-st": "^6.0.0", - "purescript-tailrec": "^6.0.0", - "purescript-tuples": "^7.0.0", - "purescript-typelevel-prelude": "^7.0.0", - "purescript-unfoldable": "^6.0.0" - }, - "devDependencies": { - "purescript-assert": "^6.0.0", - "purescript-minibench": "^4.0.0", - "purescript-ordered-collections": "^3.0.0", - "purescript-quickcheck": "^8.0.1" - } -} diff --git a/package.json b/package.json index 50da717..6523f19 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,20 @@ { "private": true, "scripts": { - "clean": "rimraf output && rimraf .pulp-cache", - "build": "eslint src && pulp build -- --censor-lib --strict", - "test": "pulp test", - "bench:build": "purs compile 'bench/**/*.purs' 'src/**/*.purs' 'bower_components/*/src/**/*.purs'", - "bench:run": "node -e 'require(\"./output/Bench.Main/index.js\").main()'", - "bench": "npm run bench:build && npm run bench:run" + "build": "spago build", + "test": "spago test -p foreign-object", + "bench:build": "spago build -p foreign-object-bench", + "bench:run": "spago run -p foreign-object-bench", + "bench": "npm run bench:build && npm run bench:run", + "lint": "oxlint --deny-warnings src test bench", + "format": "purs-tidy format-in-place src test bench && oxfmt --write .", + "format:check": "purs-tidy check src test bench && oxfmt --check ." }, "devDependencies": { - "eslint": "^7.15.0", - "pulp": "16.0.0-0", - "purescript-psa": "^0.8.2", - "rimraf": "^3.0.2" + "oxfmt": "^0.73.0", + "oxlint": "^1.88.0", + "purescript": "^0.15.16", + "purs-tidy": "^0.11.1", + "spago": "^1.0.5" } } diff --git a/spago.yaml b/spago.yaml new file mode 100644 index 0000000..2d1c627 --- /dev/null +++ b/spago.yaml @@ -0,0 +1,39 @@ +package: + name: foreign-object + description: Functions for working with homogeneous JavaScript objects + build: + strict: true + pedanticPackages: true + publish: + version: 4.1.1 + license: BSD-3-Clause + location: + githubOwner: purescript + githubRepo: purescript-foreign-object + dependencies: + - arrays: ">=7.0.0 <8.0.0" + - foldable-traversable: ">=6.0.0 <7.0.0" + - functions: ">=6.0.0 <7.0.0" + - gen: ">=4.0.0 <5.0.0" + - lists: ">=7.0.0 <8.0.0" + - maybe: ">=6.0.0 <7.0.0" + - prelude: ">=6.0.0 <7.0.0" + - st: ">=6.0.0 <7.0.0" + - tailrec: ">=6.0.0 <7.0.0" + - tuples: ">=7.0.0 <8.0.0" + - typelevel-prelude: ">=7.0.0 <8.0.0" + - unfoldable: ">=6.0.0 <7.0.0" + - unsafe-coerce: ">=6.0.0 <7.0.0" + test: + main: Test.Main + strict: true + pedanticPackages: true + dependencies: + - assert: ">=6.0.0 <7.0.0" + - console: ">=6.0.0 <7.0.0" + - effect: ">=4.0.0 <5.0.0" + - ordered-collections: ">=3.0.0 <4.0.0" + - partial: ">=4.0.0 <5.0.0" + - quickcheck: ">=8.0.1 <9.0.0" + - transformers: ">=6.0.0 <7.0.0" +workspace: {} diff --git a/src/Foreign/Object.js b/src/Foreign/Object.js index 9196412..991e9b5 100644 --- a/src/Foreign/Object.js +++ b/src/Foreign/Object.js @@ -111,6 +111,10 @@ export function toArrayWithKey(f) { }; } -export const keys = Object.keys || toArrayWithKey(function (k) { - return function () { return k; }; -}); +export const keys = + Object.keys || + toArrayWithKey(function (k) { + return function () { + return k; + }; + }); diff --git a/src/Foreign/Object.purs b/src/Foreign/Object.purs index e84a138..84e7cf0 100644 --- a/src/Foreign/Object.purs +++ b/src/Foreign/Object.purs @@ -51,9 +51,9 @@ import Data.Array as A import Data.Eq (class Eq1) import Data.Foldable (class Foldable, foldl, foldr, for_) import Data.FoldableWithIndex (class FoldableWithIndex, forWithIndex_) -import Data.Function.Uncurried (Fn2, runFn2, Fn4, runFn4) +import Data.Function.Uncurried (Fn2, Fn4, runFn2, runFn4) import Data.FunctorWithIndex (class FunctorWithIndex) -import Data.Maybe (Maybe(..), maybe, fromMaybe) +import Data.Maybe (Maybe(..), fromMaybe, maybe) import Data.Traversable (class Traversable, traverse) import Data.TraversableWithIndex (class TraversableWithIndex, traverseWithIndex) import Data.Tuple (Tuple(..), fst, uncurry) @@ -168,7 +168,8 @@ foreign import empty :: forall a. Object a -- | Test whether one map contains all of the keys and values contained in another map isSubmap :: forall a. Eq a => Object a -> Object a -> Boolean -isSubmap m1 m2 = all f m1 where +isSubmap m1 m2 = all f m1 + where f k v = runFn4 _lookup false ((==) v) k m2 -- | Test whether a map is empty @@ -234,17 +235,19 @@ foreign import _lookupST :: forall a r z. Fn4 z (a -> z) String (STObject r a) ( -- | Create an `Object a` from a foldable collection of key/value pairs, using the -- | specified function to combine values for duplicate keys. fromFoldableWith :: forall f a. Foldable f => (a -> a -> a) -> f (Tuple String a) -> Object a -fromFoldableWith f l = runST (do - s <- OST.new - for_ l (\(Tuple k v) -> runFn4 _lookupST v (f v) k s >>= \v' -> OST.poke k v' s) - pure s) +fromFoldableWith f l = runST + ( do + s <- OST.new + for_ l (\(Tuple k v) -> runFn4 _lookupST v (f v) k s >>= \v' -> OST.poke k v' s) + pure s + ) -- | Create an `Object a` from a homogeneous record, i.e. all of the record -- | fields are of the same type. fromHomogeneous :: forall r a. Homogeneous r a => { | r } -> Object a fromHomogeneous = unsafeCoerce -foreign import toArrayWithKey :: forall a b . (String -> a -> b) -> Object a -> Array b +foreign import toArrayWithKey :: forall a b. (String -> a -> b) -> Object a -> Array b -- | Unfolds a map into a list of key/value pairs toUnfoldable :: forall f a. Unfoldable f => Object a -> f (Tuple String a) @@ -303,7 +306,7 @@ filterWithKey predicate m = runST go m' <- OST.new foldM step m' m where - step acc k v = if predicate k v then OST.poke k v acc else pure acc + step acc k v = if predicate k v then OST.poke k v acc else pure acc -- | Filter out those key/value pairs of a map for which a predicate -- | on the key fails to hold. diff --git a/src/Foreign/Object/Gen.purs b/src/Foreign/Object/Gen.purs index cf81ab8..d3b96b7 100644 --- a/src/Foreign/Object/Gen.purs +++ b/src/Foreign/Object/Gen.purs @@ -4,14 +4,14 @@ import Prelude import Control.Monad.Gen (class MonadGen, chooseInt, resize, sized, unfoldable) import Control.Monad.Rec.Class (class MonadRec) -import Foreign.Object (Object, fromFoldable) -import Data.Tuple (Tuple(..)) import Data.List (List) +import Data.Tuple (Tuple(..)) +import Foreign.Object (Object, fromFoldable) -- | Generates a `Object` using the specified key and value generators. genForeignObject :: forall m a - . MonadRec m + . MonadRec m => MonadGen m => m String -> m a diff --git a/src/Foreign/Object/ST.purs b/src/Foreign/Object/ST.purs index 7fad439..e1b61ad 100644 --- a/src/Foreign/Object/ST.purs +++ b/src/Foreign/Object/ST.purs @@ -11,7 +11,7 @@ module Foreign.Object.ST , delete ) where -import Control.Monad.ST (ST, Region) +import Control.Monad.ST (Region, ST) import Data.Maybe (Maybe(..)) -- | A reference to a mutable object diff --git a/test/Test/Foreign/Object.purs b/test/Test/Foreign/Object.purs index 34323e9..754214a 100644 --- a/test/Test/Foreign/Object.purs +++ b/test/Test/Foreign/Object.purs @@ -6,7 +6,7 @@ import Control.Monad.Writer (runWriter, tell) import Data.Array as A import Data.Array.NonEmpty as NEA import Data.Foldable (foldl, foldr, for_) -import Data.FoldableWithIndex (foldlWithIndex, foldrWithIndex, foldMapWithIndex) +import Data.FoldableWithIndex (foldMapWithIndex, foldlWithIndex, foldrWithIndex) import Data.Function (on) import Data.List as L import Data.List.NonEmpty as NEL @@ -23,7 +23,7 @@ import Foreign.Object as O import Foreign.Object.Gen (genForeignObject) import Partial.Unsafe (unsafePartial) import Test.Assert (assertEqual) -import Test.QuickCheck ((), quickCheck, quickCheck', (===)) +import Test.QuickCheck (quickCheck, quickCheck', (), (===)) import Test.QuickCheck.Arbitrary (class Arbitrary, arbitrary) import Test.QuickCheck.Gen as Gen @@ -111,73 +111,84 @@ objectTests = do let tree :: O.Object Int tree = O.insert k v (runInstructions instrs O.empty) - in O.lookup k tree == Just v ("instrs:\n " <> show instrs <> "\nk:\n " <> show k <> "\nv:\n " <> show v) + in + O.lookup k tree == Just v ("instrs:\n " <> show instrs <> "\nk:\n " <> show k <> "\nv:\n " <> show v) log "Singleton to list" quickCheck $ \k v -> O.toUnfoldable (O.singleton k v :: O.Object Int) == L.singleton (Tuple k v) log "filterWithKey gives submap" quickCheck $ \(TestObject (s :: O.Object Int)) p -> - O.isSubmap (O.filterWithKey p s) s + O.isSubmap (O.filterWithKey p s) s log "filterWithKey keeps those keys for which predicate is true" quickCheck $ \(TestObject (s :: O.Object Int)) p -> - A.all (uncurry p) (O.toAscUnfoldable (O.filterWithKey p s) :: Array (Tuple String Int)) + A.all (uncurry p) (O.toAscUnfoldable (O.filterWithKey p s) :: Array (Tuple String Int)) log "filterKeys gives submap" quickCheck $ \(TestObject (s :: O.Object Int)) p -> - O.isSubmap (O.filterKeys p s) s + O.isSubmap (O.filterKeys p s) s log "filterKeys keeps those keys for which predicate is true" quickCheck $ \(TestObject (s :: O.Object Int)) p -> - A.all p (O.keys (O.filterKeys p s)) + A.all p (O.keys (O.filterKeys p s)) log "filter gives submap" quickCheck $ \(TestObject (s :: O.Object Int)) p -> - O.isSubmap (O.filter p s) s + O.isSubmap (O.filter p s) s log "filter keeps those values for which predicate is true" quickCheck $ \(TestObject (s :: O.Object Int)) p -> - A.all p (O.values (O.filter p s)) + A.all p (O.values (O.filter p s)) log "fromFoldable [] = empty" - quickCheck (O.fromFoldable [] == (O.empty :: O.Object Unit) - "was not empty") + quickCheck + ( O.fromFoldable [] == (O.empty :: O.Object Unit) + "was not empty" + ) log "fromFoldable & key collision" do - let nums = O.fromFoldable [Tuple "0" "zero", Tuple "1" "what", Tuple "1" "one"] + let nums = O.fromFoldable [ Tuple "0" "zero", Tuple "1" "what", Tuple "1" "one" ] quickCheck (O.lookup "0" nums == Just "zero" "invalid lookup - 0") - quickCheck (O.lookup "1" nums == Just "one" "invalid lookup - 1") - quickCheck (O.lookup "2" nums == Nothing "invalid lookup - 2") + quickCheck (O.lookup "1" nums == Just "one" "invalid lookup - 1") + quickCheck (O.lookup "2" nums == Nothing "invalid lookup - 2") log "fromFoldableWithIndex" do - let numsMap = M.fromFoldable [Tuple "0" "zero", Tuple "1" "one", Tuple "2" "two"] - nums = O.fromFoldableWithIndex numsMap + let + numsMap = M.fromFoldable [ Tuple "0" "zero", Tuple "1" "one", Tuple "2" "two" ] + nums = O.fromFoldableWithIndex numsMap quickCheck (O.lookup "0" nums == Just "zero" "invalid lookup - 0") - quickCheck (O.lookup "1" nums == Just "one" "invalid lookup - 1") - quickCheck (O.lookup "2" nums == Just "two" "invalid lookup - 2") + quickCheck (O.lookup "1" nums == Just "one" "invalid lookup - 1") + quickCheck (O.lookup "2" nums == Just "two" "invalid lookup - 2") log "fromFoldableWith const [] = empty" - quickCheck (O.fromFoldableWith const [] == (O.empty :: O.Object Unit) - "was not empty") + quickCheck + ( O.fromFoldableWith const [] == (O.empty :: O.Object Unit) + "was not empty" + ) log "fromFoldableWith (+) & key collision" do - let nums = O.fromFoldableWith (+) [Tuple "0" 1, Tuple "1" 1, Tuple "1" 1] - quickCheck (O.lookup "0" nums == Just 1 "invalid lookup - 0") - quickCheck (O.lookup "1" nums == Just 2 "invalid lookup - 1") + let nums = O.fromFoldableWith (+) [ Tuple "0" 1, Tuple "1" 1, Tuple "1" 1 ] + quickCheck (O.lookup "0" nums == Just 1 "invalid lookup - 0") + quickCheck (O.lookup "1" nums == Just 2 "invalid lookup - 1") quickCheck (O.lookup "2" nums == Nothing "invalid lookup - 2") log "toUnfoldable . fromFoldable = id" - quickCheck $ \arr -> let f x = O.toUnfoldable (O.fromFoldable x) - in f (f arr) == f (arr :: L.List (Tuple String Int)) show arr + quickCheck $ \arr -> + let + f x = O.toUnfoldable (O.fromFoldable x) + in + f (f arr) == f (arr :: L.List (Tuple String Int)) show arr log "fromFoldable . toUnfoldable = id" quickCheck $ \(TestObject m) -> - let f m1 = O.fromFoldable ((O.toUnfoldable m1) :: L.List (Tuple String Int)) in - O.toUnfoldable (f m) == (O.toUnfoldable m :: L.List (Tuple String Int)) show m + let + f m1 = O.fromFoldable ((O.toUnfoldable m1) :: L.List (Tuple String Int)) + in + O.toUnfoldable (f m) == (O.toUnfoldable m :: L.List (Tuple String Int)) show m log "fromFoldableWithIndex = id" quickCheck $ \(TestObject m :: _ Int) -> @@ -185,100 +196,127 @@ objectTests = do log "fromFoldableWithIndex = fromFoldable . toUnfoldable" quickCheck $ \(TestObject m) -> - let f m1 = O.fromFoldable ((O.toUnfoldable m1) :: L.List (Tuple String Int)) in - O.fromFoldableWithIndex m == f m show m + let + f m1 = O.fromFoldable ((O.toUnfoldable m1) :: L.List (Tuple String Int)) + in + O.fromFoldableWithIndex m == f m show m log "fromFoldableWith const = fromFoldable" - quickCheck $ \arr -> O.fromFoldableWith const arr == - O.fromFoldable (arr :: L.List (Tuple String Int)) show arr + quickCheck $ \arr -> + O.fromFoldableWith const arr == + O.fromFoldable (arr :: L.List (Tuple String Int)) show arr log "fromFoldableWith (<>) = fromFoldable . collapse with (<>) . group on fst" quickCheck $ \arr -> - let combine (Tuple s a) (Tuple _ b) = (Tuple s $ b <> a) - foldl1 g = unsafePartial \(L.Cons x xs) -> foldl g x xs - f = O.fromFoldable <<< map (foldl1 combine <<< NEL.toList) <<< - L.groupBy ((==) `on` fst) <<< L.sortBy (compare `on` fst) in - O.fromFoldableWith (<>) arr == f (arr :: L.List (Tuple String String)) show arr + let + combine (Tuple s a) (Tuple _ b) = (Tuple s $ b <> a) + foldl1 g = unsafePartial \(L.Cons x xs) -> foldl g x xs + f = O.fromFoldable <<< map (foldl1 combine <<< NEL.toList) + <<< L.groupBy ((==) `on` fst) + <<< L.sortBy (compare `on` fst) + in + O.fromFoldableWith (<>) arr == f (arr :: L.List (Tuple String String)) show arr log "unionWith" - for_ [Tuple (+) 0, Tuple (*) 1] $ \(Tuple op ident) -> + for_ [ Tuple (+) 0, Tuple (*) 1 ] $ \(Tuple op ident) -> quickCheck $ \(TestObject m1) (TestObject m2) k -> - let u = O.unionWith op m1 m2 :: O.Object Int - in case O.lookup k u of - Nothing -> not (O.member k m1 || O.member k m2) - Just v -> v == op (fromMaybe ident (O.lookup k m1)) (fromMaybe ident (O.lookup k m2)) + let + u = O.unionWith op m1 m2 :: O.Object Int + in + case O.lookup k u of + Nothing -> not (O.member k m1 || O.member k m2) + Just v -> v == op (fromMaybe ident (O.lookup k m1)) (fromMaybe ident (O.lookup k m2)) log "unionWith argument order" quickCheck $ \(TestObject m1) (TestObject m2) k -> - let u = O.unionWith (-) m1 m2 :: O.Object Int - in1 = O.member k m1 - v1 = O.lookup k m1 - in2 = O.member k m2 - v2 = O.lookup k m2 - in case O.lookup k u of - Just v | in1 && in2 -> Just v == ((-) <$> v1 <*> v2) - Just v | in1 -> Just v == v1 - Just v -> Just v == v2 - Nothing -> not (in1 || in2) + let + u = O.unionWith (-) m1 m2 :: O.Object Int + in1 = O.member k m1 + v1 = O.lookup k m1 + in2 = O.member k m2 + v2 = O.lookup k m2 + in + case O.lookup k u of + Just v | in1 && in2 -> Just v == ((-) <$> v1 <*> v2) + Just v | in1 -> Just v == v1 + Just v -> Just v == v2 + Nothing -> not (in1 || in2) log "fromFoldable = zip keys values" quickCheck $ \(TestObject m) -> O.toUnfoldable m == A.zipWith Tuple (O.keys m) (O.values m :: Array Int) log "mapWithKey is correct" - quickCheck $ \(TestObject m :: TestObject Int) -> let - f k v = k <> show v - resultViaMapWithKey = m # O.mapWithKey f - resultViaLists = m # O.toUnfoldable # map (\(Tuple k v) → Tuple k (f k v)) # (O.fromFoldable :: forall a. L.List (Tuple String a) -> O.Object a) - in resultViaMapWithKey === resultViaLists + quickCheck $ \(TestObject m :: TestObject Int) -> + let + f k v = k <> show v + resultViaMapWithKey = m # O.mapWithKey f + resultViaLists = m # O.toUnfoldable # map (\(Tuple k v) -> Tuple k (f k v)) # (O.fromFoldable :: forall a. L.List (Tuple String a) -> O.Object a) + in + resultViaMapWithKey === resultViaLists log "foldl = foldlWithIndex <<< const" quickCheck \(TestObject m :: TestObject String) -> - let f z v = z <> "," <> v - in foldl f "" m === foldlWithIndex (const f) "" m + let + f z v = z <> "," <> v + in + foldl f "" m === foldlWithIndex (const f) "" m log "foldr = foldrWithIndex <<< const" quickCheck \(TestObject m :: TestObject String) -> - let f v z = v <> "," <> z - in foldr f "" m === foldrWithIndex (const f) "" m + let + f v z = v <> "," <> z + in + foldr f "" m === foldrWithIndex (const f) "" m log "foldlWithIndex = foldrWithIndex with flipped operation" quickCheck \(TestObject m :: TestObject String) -> - let f k z v = z <> "," <> k <> ":" <> v - g k v z = k <> ":" <> v <> "," <> z - in foldlWithIndex f "" m <> "," === "," <> foldrWithIndex g "" m + let + f k z v = z <> "," <> k <> ":" <> v + g k v z = k <> ":" <> v <> "," <> z + in + foldlWithIndex f "" m <> "," === "," <> foldrWithIndex g "" m log "foldMapWithIndex f ~ traverseWithIndex (\\k v -> tell (f k v))" quickCheck \(TestObject m :: TestObject Int) -> - let f _ v = "(" <> "k" <> "," <> show v <> ")" - resultA = foldMapWithIndex f m - resultB = snd (runWriter (traverseWithIndex (\k v -> tell (f k v)) m)) - in resultA === resultB + let + f _ v = "(" <> "k" <> "," <> show v <> ")" + resultA = foldMapWithIndex f m + resultB = snd (runWriter (traverseWithIndex (\k v -> tell (f k v)) m)) + in + resultA === resultB log "traverse = traverseWithIndex <<< const (for m = Writer)" quickCheck \(TestObject m :: TestObject String) -> runWriter (traverse tell m) === - runWriter (traverseWithIndex (const tell) m) + runWriter (traverseWithIndex (const tell) m) log "sequence works (for m = Array)" quickCheck \(TestObject mOfSmallArrays :: TestObject (SmallArray Int)) -> - let m = (\(SmallArray a) -> a) <$> mOfSmallArrays - Tuple keys values = A.unzip (toAscArray m) - resultViaArrays = (O.fromFoldable <<< A.zip keys) <$> sequence values - in A.sort (sequence m) === A.sort (resultViaArrays) + let + m = (\(SmallArray a) -> a) <$> mOfSmallArrays + Tuple keys values = A.unzip (toAscArray m) + resultViaArrays = (O.fromFoldable <<< A.zip keys) <$> sequence values + in + A.sort (sequence m) === A.sort (resultViaArrays) log "sequence works (for m = Maybe)" quickCheck \(TestObject m :: TestObject (Maybe Int)) -> - let Tuple keys values = A.unzip (toAscArray m) - resultViaArrays = (O.fromFoldable <<< A.zip keys) <$> sequence values - in sequence m === resultViaArrays + let + Tuple keys values = A.unzip (toAscArray m) + resultViaArrays = (O.fromFoldable <<< A.zip keys) <$> sequence values + in + sequence m === resultViaArrays log "Bug #63: accidental observable mutation in foldMap" quickCheck \(TestObject m) -> - let lhs = go m - rhs = go m - go :: O.Object (Array Ordering) -> Array Ordering - go = O.foldMap (const identity) - in lhs == rhs ("lhs: " <> show lhs <> ", rhs: " <> show rhs) + let + lhs = go m + rhs = go m + + go :: O.Object (Array Ordering) -> Array Ordering + go = O.foldMap (const identity) + in + lhs == rhs ("lhs: " <> show lhs <> ", rhs: " <> show rhs) log "fromFoldable stack safety" do diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 0b9edd7..5f1dbef 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -18,8 +18,8 @@ example = do -- insert to an empty Object inserted = FO.insert "a" 1 empty - -- or: use the singleton function - -- singleton FO.singleton "a" 1 + -- or: use the singleton function + -- singleton FO.singleton "a" 1 -- lookup values for existing in the Object as a result of Maybe let lookup = FO.lookup "a" inserted @@ -31,10 +31,9 @@ example = do let -- convert homogeneous records to Object - converted = FO.fromHomogeneous { a: 1, b: 2, c: 3} + converted = FO.fromHomogeneous { a: 1, b: 2, c: 3 } -- check that the converted is equal to a regularly built Object - built - = FO.empty + built = FO.empty # FO.insert "a" 1 # FO.insert "b" 2 # FO.insert "c" 3