on develop help sign in
18 files changed+216−235
since splitsnapshots
unifiedsplit
.github/workflows/haskell.yml+41 .travis.yml−177 ChangeLog.md+31−1 Quickref.md+1 README.md+4−4 cabal.project.freeze−2 reflex.cabal+20−8 src/Reflex/Dynamic.hs+1−1 src/Reflex/Host/Headless.hs+11−9 src/Reflex/PerformEvent/Class.hs−1 src/Reflex/PostBuild/Base.hs−1 src/Reflex/PostBuild/Class.hs−1 src/Reflex/Profiled.hs+1−1 src/Reflex/Requester/Base.hs+91−26 src/Reflex/Spider/Internal.hs+2−2 test/GC.hs+1 test/Headless.hs+10 test/hlint.hs+2−1
.github/workflows/haskell.ymlnew file+41
@@ -0,0 +1,41 @@
1+name: github-action
2+
3+on: [push, pull_request]
4+
5+jobs:
6+ build:
7+ strategy:
8+ matrix:
9+ ghc: ['8.4.4', '8.6.5', '8.8.4', '8.10.2']
10+ os: ['ubuntu-latest', 'macos-latest']
11+ runs-on: ${{ matrix.os }}
12+
13+ name: GHC ${{ matrix.ghc }} on ${{ matrix.os }}
14+ steps:
15+ - uses: actions/checkout@v2
16+ - uses: actions/setup-haskell@v1
17+ with:
18+ ghc-version: ${{ matrix.ghc }}
19+ - name: Cache
20+ uses: actions/cache@v1
21+ env:
22+ cache-name: cache-cabal
23+ with:
24+ path: ~/.cabal
25+ key: ${{ runner.os }}-${{ matrix.ghc }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }}
26+ restore-keys: |
27+ ${{ runner.os }}-${{ matrix.ghc }}-build-${{ env.cache-name }}-
28+ ${{ runner.os }}-${{ matrix.ghc }}-build-
29+ ${{ runner.os }}-${{ matrix.ghc }}-
30+ ${{ runner.os }}
31+
32+ - name: Install dependencies
33+ run: |
34+ cabal update
35+ cabal build --only-dependencies --enable-tests --enable-benchmarks
36+ - name: Build
37+ run: cabal build --enable-tests --enable-benchmarks all
38+ - name: Run tests
39+ run: cabal test all
40+ - name: Build Docs
41+ run: cabal haddock
.travis.ymldeleted−177
@@ -1,177 +0,0 @@
1−# This Travis job script has been generated by a script via
2−#
3−# haskell-ci '--config=cabal.haskell-ci' 'cabal.project'
4−#
5−# For more information, see https://github.com/haskell-CI/haskell-ci
6−#
7−# version: 0.3.20190814
8−#
9−language: c
10−dist: xenial
11−sudo: required
12−git:
13− # whether to recursively clone submodules
14− submodules: false
15−cache:
16− directories:
17− - $HOME/.cabal/packages
18− - $HOME/.cabal/store
19−before_cache:
20− - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
21− # remove files that are regenerated by 'cabal update'
22− - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
23− - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
24− - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
25− - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
26− - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
27− - rm -rfv $CABALHOME/packages/head.hackage
28−matrix:
29− include:
30− - compiler: ghcjs-8.4
31− addons: {"apt":{"sources":["hvr-ghc"],"packages":["cabal-install-3.0"]}}
32− - compiler: ghc-8.8.1
33− addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
34− - compiler: ghc-8.6.5
35− addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-2.4"]}}
36− - compiler: ghc-8.4.4
37− addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-2.4"]}}
38− - compiler: ghc-8.2.2
39− addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-2.4"]}}
40− - compiler: ghc-8.0.2
41− addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-2.4"]}}
42−before_install:
43− - |
44− if [ "$TRAVIS_OS_NAME" = "linux" ]; then
45− sudo add-apt-repository -y ppa:hvr/ghcjs;
46− curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add -
47− sudo apt-add-repository 'https://deb.nodesource.com/node_8.x xenial main'
48− sudo apt-get update;
49− sudo apt-get install $CC cabal-install-3.0 nodejs;
50− fi
51− - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
52− - WITHCOMPILER="-w $HC"
53− - |
54− if echo $CC | grep -q ghcjs; then
55− GHCJS=true
56− HC=${HC}js
57− WITHCOMPILER="--ghcjs ${WITHCOMPILER}js"
58− else
59− GHCJS=false;
60− fi
61− - if $GHCJS ; then sudo apt-get install -y ghc-8.4.4 ; fi
62− - if $GHCJS ; then PATH="/opt/ghc/8.4.4/bin:$PATH" ; fi
63− - HCPKG="$HC-pkg"
64− - unset CC
65− - CABAL=/opt/ghc/bin/cabal
66− - CABALHOME=$HOME/.cabal
67− - export PATH="$CABALHOME/bin:$PATH"
68− - TOP=$(pwd)
69− - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
70− - echo $HCNUMVER
71− - CABAL="$CABAL -vnormal+nowrap+markoutput"
72− - set -o pipefail
73− - |
74− echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }' >> .colorful.awk
75− echo 'BEGIN { state = "output"; }' >> .colorful.awk
76− echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }' >> .colorful.awk
77− echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }' >> .colorful.awk
78− echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
79− echo ' if (state == "cabal") {' >> .colorful.awk
80− echo ' print blue($0)' >> .colorful.awk
81− echo ' } else {' >> .colorful.awk
82− echo ' print $0' >> .colorful.awk
83− echo ' }' >> .colorful.awk
84− echo '}' >> .colorful.awk
85− - cat .colorful.awk
86− - |
87− color_cabal_output () {
88− awk -f $TOP/.colorful.awk
89− }
90− - echo text | color_cabal_output
91−install:
92− - ${CABAL} --version
93− - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
94− - node --version
95− - echo $GHCJS
96− - TEST=--enable-tests
97− - BENCH=--enable-benchmarks
98− - BENCH=--disable-benchmarks
99− - HEADHACKAGE=false
100− - rm -f $CABALHOME/config
101− - |
102− echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config
103− echo "remote-build-reporting: anonymous" >> $CABALHOME/config
104− echo "write-ghc-environment-files: always" >> $CABALHOME/config
105− echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config
106− echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config
107− echo "world-file: $CABALHOME/world" >> $CABALHOME/config
108− echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config
109− echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config
110− echo "installdir: $CABALHOME/bin" >> $CABALHOME/config
111− echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config
112− echo "store-dir: $CABALHOME/store" >> $CABALHOME/config
113− echo "install-dirs user" >> $CABALHOME/config
114− echo " prefix: $CABALHOME" >> $CABALHOME/config
115− echo "repository hackage.haskell.org" >> $CABALHOME/config
116− echo " url: http://hackage.haskell.org/" >> $CABALHOME/config
117− - cat $CABALHOME/config
118− - rm -fv cabal.project cabal.project.local cabal.project.freeze
119− - travis_retry ${CABAL} v2-update -v
120− - if $GHCJS ; then (cd /tmp && ${CABAL} v2-install -w ghc-8.4.4 happy) ; fi
121− # Generate cabal.project
122− - rm -rf cabal.project cabal.project.local cabal.project.freeze
123− - touch cabal.project
124− - |
125− echo "packages: ." >> cabal.project
126− - |
127− - cat cabal.project || true
128− - cat cabal.project.local || true
129− - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
130− - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
131− - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
132− - rm cabal.project.freeze
133−script:
134− - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
135− # Packaging...
136− - ${CABAL} v2-sdist all | color_cabal_output
137− # Unpacking...
138− - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
139− - cd ${DISTDIR} || false
140− - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
141− - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \;
142− - PKGDIR_reflex="$(find . -maxdepth 1 -type d -regex '.*/reflex-[0-9.]*')"
143− # Generate cabal.project
144− - rm -rf cabal.project cabal.project.local cabal.project.freeze
145− - touch cabal.project
146− - |
147− echo "packages: ${PKGDIR_reflex}" >> cabal.project
148− - |
149− - cat cabal.project || true
150− - cat cabal.project.local || true
151− # Building...
152− # this builds all libraries and executables (without tests/benchmarks)
153− - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
154− # Building with tests and benchmarks...
155− # build & run tests, build benchmarks
156− - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
157− # Testing...
158− - if ! $GHCJS ; then ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output ; fi
159− # cabal check...
160− - (cd ${PKGDIR_reflex} && ${CABAL} -vnormal check)
161− # haddock...
162− - if ! $GHCJS ; then ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output ; fi
163− # Constraint sets
164− - rm -rf cabal.project.local
165− # Constraint set no-th
166− - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='reflex -use-template-haskell' all | color_cabal_output
167− # Constraint set old-these
168− - if $GHCJS || ! $GHCJS && [ $HCNUMVER -lt 80800 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='these <1' all | color_cabal_output ; fi
169− # Constraint set old-witherable
170− - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='witherable <0.3.2' all | color_cabal_output
171− # Constraint set debug-propagation
172− - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='reflex +debug-propagation' all | color_cabal_output
173− # Constraint set debug-cycles
174− - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='reflex +debug-cycles' all | color_cabal_output
175−
176−# REGENDATA ["--config=cabal.haskell-ci","cabal.project"]
177−# EOF
ChangeLog.md+31−1
@@ -1,7 +1,37 @@
11# Revision history for reflex
22
3−## Unreleased
3+## 0.8.2.0
4+
5+* Add `matchResponseMapWithRequests`, which it similar to `matchResponsesWithRequests` but allows processing of multiple responses at once.
6+
7+## 0.8.1.1
8+
9+* Allow newer hlint for older GHCs, and add upper bound for newer GHCs
10+
11+## 0.8.1.0
12+
13+* Add support for GHC 8.10
14+* Drop support for GHC < 8.4
15+
16+## 0.8.0.0
17+
18+* Replace 0.7.2.0 with 0.8.0.0 to reflect the `MonadHold` interface change. Deprecates 0.7.2.0.
19+
20+## 0.7.2.0 -- *Deprecated*
21+
422* ([#416](https://github.com/reflex-frp/reflex/pull/416)) Add `now :: m (Event t ())` to `MonadHold`.
23+* Extend some dependency version bounds
24+* Fix HLint 3 test
25+
26+## 0.7.1.1
27+
28+*Backport release*.
29+Changes do not carry forward to 0.7.2.0.
30+
31+* Add support for GHC 8.10
32+* Drop support for GHC < 8.4
33+* Extend some dependency version bounds
34+* Fix HLint 3 test
535
636## 0.7.1.0
737
Quickref.md+1
@@ -54,6 +54,7 @@ Since MonadHold depends on MonadSample, any [S] function also runs in [H] contex
5454[ ] difference :: Event a -> Event b -> Event a
5555[ ] align :: Event a -> Event b -> Event (These a b)
5656[ ] alignWith :: (These a b -> c) -> Event a -> Event b -> Event c
57+ -- Note align functions from Data.Align in semialign package
5758[ ] mergeWith :: (a -> a -> a) -> [Event a] -> Event a
5859[ ] leftmost :: [Event a] -> Event a
5960[ ] mergeList :: [Event a] -> Event (NonEmpty a)
README.md+4−4
@@ -1,6 +1,6 @@
1−## [Reflex](https://reflex-frp.org/)
1+# [Reflex](https://reflex-frp.org/)
22
3−[![Haskell](https://img.shields.io/badge/language-Haskell-orange.svg)](https://haskell.org) [![Hackage](https://img.shields.io/hackage/v/reflex.svg)](https://hackage.haskell.org/package/reflex) [![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/reflex/badge)](https://matrix.hackage.haskell.org/#/package/reflex) [![Travis CI](https://api.travis-ci.org/reflex-frp/reflex.svg?branch=master)](https://travis-ci.org/reflex-frp/reflex) [![BSD3 License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://github.com/reflex-frp/reflex/blob/master/LICENSE)
3+[![Haskell](https://img.shields.io/badge/language-Haskell-orange.svg)](https://haskell.org) [![Hackage](https://img.shields.io/hackage/v/reflex.svg)](https://hackage.haskell.org/package/reflex) [![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/reflex/badge)](https://matrix.hackage.haskell.org/#/package/reflex) [![BSD3 License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://github.com/reflex-frp/reflex/blob/master/LICENSE)
44
55Interactive programs without callbacks or side-effects. Functional Reactive Programming (FRP) uses composable events and time-varying values to describe interactive systems as pure functions. Just like other pure functional code, functional reactive code is easier to get right on the first try, maintain, and reuse.
66
@@ -8,7 +8,7 @@ Reflex is a fully-deterministic, higher-order Functional Reactive Programming in
88
99**Visit https://reflex-frp.org for more information, tutorials, documentation and [examples](https://examples.reflex-frp.org/).**
1010
11−### Resources
11+## Resources
1212
1313* [Official Website](https://reflex-frp.org)
1414* [Quick Reference](Quickref.md)
@@ -18,6 +18,6 @@ Reflex is a fully-deterministic, higher-order Functional Reactive Programming in
1818* [/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
1919* [irc.freenode.net #reflex-frp](http://webchat.freenode.net?channels=%23reflex-frp&uio=d4)
2020
21−### Hacking
21+## Hacking
2222
2323From the root of a [Reflex Platform](https://github.com/reflex-frp/reflex-platform) checkout, run `./scripts/hack-on haskell-overlays/reflex-packages/dep/reflex`. This will check out the reflex source code into the `haskell-overlays/reflex-packages/dep/reflex` directory. You can then point that checkout at your fork, make changes, etc. Use the `./try-reflex` or `./scripts/work-on` scripts to start a shell in which you can test your changes.
cabal.project.freezedeleted−2
@@ -1,2 +0,0 @@
1−constraints: any.text < 1.2.4.0
2− , hlint < 2.2.6 || > 2.2.6
reflex.cabal+20−8
@@ -1,5 +1,5 @@
11Name: reflex
2−Version: 0.7.1.0
2+Version: 0.8.2.0
33Synopsis: Higher-order Functional Reactive Programming
44Description:
55 Interactive programs without callbacks or side-effects.
@@ -28,8 +28,8 @@ extra-source-files:
2828 ChangeLog.md
2929
3030tested-with:
31− GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.1,
32− GHCJS ==8.4
31+ GHC ==8.4.4 || ==8.6.5 || ==8.8.1 || ==8.10.2,
32+ GHCJS ==8.6
3333
3434flag use-reflex-optimizer
3535 description: Use the GHC plugin Reflex.Optimizer on some of the modules in the package. This is still experimental.
@@ -71,9 +71,10 @@ library
7171 hs-source-dirs: src
7272 build-depends:
7373 MemoTrie == 0.6.*,
74− base >= 4.9 && < 4.14,
74+ base >= 4.11 && < 4.15,
7575 bifunctors >= 5.2 && < 5.6,
7676 comonad >= 5.0.4 && < 5.1,
77+ constraints >= 0.10 && <0.14,
7778 constraints-extras >= 0.3 && < 0.4,
7879 containers >= 0.6 && < 0.7,
7980 data-default >= 0.5 && < 0.8,
@@ -86,7 +87,7 @@ library
8687 patch >= 0.0.1 && < 0.1,
8788 prim-uniq >= 0.1.0.1 && < 0.3,
8889 primitive >= 0.5 && < 0.8,
89− profunctors >= 5.3 && < 5.6,
90+ profunctors >= 5.3 && < 5.7,
9091 random == 1.1.*,
9192 ref-tf == 0.4.*,
9293 reflection == 2.1.*,
@@ -96,7 +97,7 @@ library
9697 time >= 1.4 && < 1.10,
9798 transformers >= 0.5.6.0 && < 0.6,
9899 unbounded-delays >= 0.1.0.0 && < 0.2,
99− witherable >= 0.3 && < 0.4
100+ witherable >= 0.3 && < 0.5
100101
101102 if flag(split-these)
102103 build-depends: these >= 1 && <1.2,
@@ -185,7 +186,7 @@ library
185186 dependent-sum >= 0.6 && < 0.8,
186187 haskell-src-exts >= 1.16 && < 1.24,
187188 haskell-src-meta >= 0.6 && < 0.9,
188− template-haskell >= 2.9 && < 2.16
189+ template-haskell >= 2.9 && < 2.17
189190 exposed-modules:
190191 Reflex.Dynamic.TH
191192 other-extensions: TemplateHaskell
@@ -257,7 +258,10 @@ test-suite hlint
257258 , directory
258259 , filepath
259260 , filemanip
260− , hlint (< 2.1 || >= 2.2.2) && < 3
261+ if impl(ghc >= 8.8)
262+ build-depends: hlint >= 3 && < 4
263+ else
264+ build-depends: hlint (< 2.1 || >= 2.2.2) && < 4
261265 if impl(ghcjs)
262266 buildable: False
263267
@@ -342,6 +346,14 @@ test-suite RequesterT
342346 Reflex.Plan.Pure
343347 Test.Run
344348
349+test-suite Headless
350+ default-language: Haskell2010
351+ type: exitcode-stdio-1.0
352+ main-is: Headless.hs
353+ hs-source-dirs: test
354+ build-depends: base
355+ , reflex
356+
345357test-suite Adjustable
346358 default-language: Haskell2010
347359 type: exitcode-stdio-1.0
src/Reflex/Dynamic.hs+1−1
@@ -348,7 +348,7 @@ factorDyn d = buildDynamic (sample (current d) >>= holdKey) update where
348348--
349349-- > demuxed (demux d) k === fmap (== k) d
350350--
351−-- However, when getDemuxed is used multiple times, the complexity is only
351+-- However, when 'demuxed' is used multiple times, the complexity is only
352352-- /O(log(n))/, rather than /O(n)/ for fmap.
353353data Demux t k = Demux { demuxValue :: Behavior t k
354354 , demuxSelector :: EventSelector t (Const2 k Bool)
src/Reflex/Host/Headless.hs+11−9
@@ -7,6 +7,7 @@
77module Reflex.Host.Headless where
88
99import Control.Concurrent.Chan (newChan, readChan)
10+import Control.Monad (unless)
1011import Control.Monad.Fix (MonadFix, fix)
1112import Control.Monad.IO.Class (MonadIO, liftIO)
1213import Control.Monad.Primitive (PrimMonad)
@@ -15,7 +16,7 @@ import Data.Dependent.Sum (DSum (..), (==>))
1516import Data.Foldable (for_)
1617import Data.Functor.Identity (Identity(..))
1718import Data.IORef (IORef, readIORef)
18−import Data.Maybe (catMaybes)
19+import Data.Maybe (catMaybes, fromMaybe)
1920import Data.Traversable (for)
2021
2122import Reflex
@@ -82,32 +83,33 @@ runHeadlessApp guest =
8283 -- 'Nothing' if the guest application hasn't subscribed to this event.
8384 mPostBuildTrigger <- readRef postBuildTriggerRef
8485
85− -- When there is a subscriber to the post-build event, fire the event.
86− for_ mPostBuildTrigger $ \postBuildTrigger ->
87− fire [postBuildTrigger :=> Identity ()] $ pure ()
88−
8986 -- Subscribe to an 'Event' of that the guest application can use to
9087 -- request application shutdown. We'll check whether this 'Event' is firing
9188 -- to determine whether to terminate.
9289 shutdown <- subscribeEvent result
9390
91+ -- When there is a subscriber to the post-build event, fire the event.
92+ soa <- for mPostBuildTrigger $ \postBuildTrigger ->
93+ fire [postBuildTrigger :=> Identity ()] $ isFiring shutdown
94+
9495 -- The main application loop. We wait for new events and fire those that
9596 -- have subscribers. If we detect a shutdown request, the application
9697 -- terminates.
97− fix $ \loop -> do
98+ unless (or (fromMaybe [] soa)) $ fix $ \loop -> do
9899 -- Read the next event (blocking).
99100 ers <- liftIO $ readChan events
100101 stop <- do
101102 -- Fire events that have subscribers.
102103 fireEventTriggerRefs fc ers $
103104 -- Check if the shutdown 'Event' is firing.
104− readEvent shutdown >>= \case
105− Nothing -> pure False
106− Just _ -> pure True
105+ isFiring shutdown
107106 if or stop
108107 then pure ()
109108 else loop
110109 where
110+ isFiring ev = readEvent ev >>= \case
111+ Nothing -> pure False
112+ Just _ -> pure True
111113 -- Use the given 'FireCommand' to fire events that have subscribers
112114 -- and call the callback for the 'TriggerInvocation' of each.
113115 fireEventTriggerRefs
src/Reflex/PerformEvent/Class.hs−1
@@ -8,7 +8,6 @@
88{-# LANGUAGE RankNTypes #-}
99{-# LANGUAGE ScopedTypeVariables #-}
1010{-# LANGUAGE TypeFamilies #-}
11−{-# LANGUAGE TypeOperators #-}
1211{-# LANGUAGE UndecidableInstances #-}
1312#ifdef USE_REFLEX_OPTIMIZER
1413{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
src/Reflex/PostBuild/Base.hs−1
@@ -9,7 +9,6 @@
99{-# LANGUAGE RecursiveDo #-}
1010{-# LANGUAGE ScopedTypeVariables #-}
1111{-# LANGUAGE TypeFamilies #-}
12−{-# LANGUAGE TypeOperators #-}
1312{-# LANGUAGE UndecidableInstances #-}
1413#ifdef USE_REFLEX_OPTIMIZER
1514{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
src/Reflex/PostBuild/Class.hs−1
@@ -6,7 +6,6 @@
66{-# LANGUAGE FunctionalDependencies #-}
77{-# LANGUAGE ScopedTypeVariables #-}
88{-# LANGUAGE TypeFamilies #-}
9−{-# LANGUAGE TypeOperators #-}
109{-# LANGUAGE UndecidableInstances #-}
1110#ifdef USE_REFLEX_OPTIMIZER
1211{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
src/Reflex/Profiled.hs+1−1
@@ -147,7 +147,7 @@ instance Reflex t => Reflex (ProfiledTimeline t) where
147147 pushCheap f (Event_Profiled e) = coerce $ pushCheap (coerce f) $ profileEvent e
148148 pull = Behavior_Profiled . pull . coerce
149149 fanG (Event_Profiled e) = EventSelectorG $ coerce $ selectG (fanG $ profileEvent e)
150− mergeG :: forall (k :: z -> *) q v. GCompare k
150+ mergeG :: forall z (k :: z -> *) q v. GCompare k
151151 => (forall a. q a -> Event (ProfiledTimeline t) (v a))
152152 -> DMap k q -> Event (ProfiledTimeline t) (DMap k v)
153153 mergeG nt = Event_Profiled #. mergeG (coerce nt)
src/Reflex/Requester/Base.hs+91−26
@@ -1,6 +1,7 @@
11-- | This module provides 'RequesterT', the standard implementation of
22-- 'Requester'.
33{-# LANGUAGE AllowAmbiguousTypes #-}
4+{-# LANGUAGE ApplicativeDo #-}
45{-# LANGUAGE CPP #-}
56{-# LANGUAGE EmptyDataDecls #-}
67{-# LANGUAGE ExistentialQuantification #-}
@@ -32,6 +33,7 @@ module Reflex.Requester.Base
3233 , requesterDataToList
3334 , singletonRequesterData
3435 , matchResponsesWithRequests
36+ , matchResponseMapWithRequests
3537 , multiEntry
3638 , unMultiEntry
3739 , requesting'
@@ -56,6 +58,7 @@ import Control.Monad.Ref
5658import Control.Monad.State.Strict
5759import Data.Bits
5860import Data.Coerce
61+import Data.Constraint
5962import Data.Dependent.Map (DMap)
6063import qualified Data.Dependent.Map as DMap
6164import Data.Dependent.Sum (DSum (..))
@@ -81,19 +84,51 @@ newtype TagMap (f :: * -> *) = TagMap (IntMap Any)
8184
8285newtype RequesterData f = RequesterData (TagMap (Entry f))
8386
87+emptyRequesterData :: RequesterData f
88+emptyRequesterData = RequesterData $ TagMap IntMap.empty
89+
8490data RequesterDataKey a where
8591 RequesterDataKey_Single :: {-# UNPACK #-} !(MyTag (Single a)) -> RequesterDataKey a
8692 RequesterDataKey_Multi :: {-# UNPACK #-} !(MyTag Multi) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a --TODO: Don't put a second Int here (or in the other Multis); use a single Int instead
87− RequesterDataKey_Multi2 :: {-# UNPACK #-} !(MyTag (Multi2 k)) -> !(Some k) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a
93+ RequesterDataKey_Multi2 :: GCompare k => {-# UNPACK #-} !(MyTag (Multi2 k)) -> !(Some k) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a
8894 RequesterDataKey_Multi3 :: {-# UNPACK #-} !(MyTag Multi3) -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a
8995
9096singletonRequesterData :: RequesterDataKey a -> f a -> RequesterData f
9197singletonRequesterData rdk v = case rdk of
9298 RequesterDataKey_Single k -> RequesterData $ singletonTagMap k $ Entry v
9399 RequesterDataKey_Multi k k' k'' -> RequesterData $ singletonTagMap k $ Entry $ IntMap.singleton k' $ singletonRequesterData k'' v
94− RequesterDataKey_Multi2 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ Map.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v
100+ RequesterDataKey_Multi2 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ Multi2Contents
101+ { _multi2Contents_values = Map.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v
102+ , _multi2Contents_dict = Dict
103+ }
95104 RequesterDataKey_Multi3 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ IntMap.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v
96105
106+mergeRequesterData :: RequesterData f -> RequesterData f -> RequesterData f
107+mergeRequesterData (RequesterData a) (RequesterData b) = RequesterData $ mergeTagMap a b
108+
109+mergeTagMap :: forall f. TagMap (Entry f) -> TagMap (Entry f) -> TagMap (Entry f)
110+mergeTagMap (TagMap m) (TagMap n) =
111+ TagMap $ IntMap.unionWithKey (g' combiner) m n
112+ where
113+ combiner :: forall a. MyTag a -> Entry f a -> Entry f a -> Entry f a
114+ combiner k (Entry a) (Entry b) = Entry $ case myKeyType k of
115+ MyTagType_Single -> a
116+ MyTagType_Multi -> IntMap.unionWith mergeRequesterData a b
117+ MyTagType_Multi2 -> case _multi2Contents_dict a of
118+ Dict -> Multi2Contents
119+ { _multi2Contents_values = Map.unionWith (IntMap.unionWith mergeRequesterData) (_multi2Contents_values a) (_multi2Contents_values b)
120+ , _multi2Contents_dict = Dict
121+ }
122+ MyTagType_Multi3 -> IntMap.unionWith (IntMap.unionWith mergeRequesterData) a b
123+ g' :: (forall a. MyTag a -> Entry f a -> Entry f a -> Entry f a) -> Int -> Any -> Any -> Any
124+ g' f rawKey a b =
125+ let k = MyTag rawKey :: MyTag a
126+ fromAny :: Any -> Entry f a
127+ fromAny = unsafeCoerce
128+ toAny :: Entry f a -> Any
129+ toAny = unsafeCoerce
130+ in toAny $ f k (fromAny a) (fromAny b)
131+
97132requesterDataToList :: RequesterData f -> [DSum RequesterDataKey f]
98133requesterDataToList (RequesterData m) = do
99134 k :=> Entry e <- tagMapToList m
@@ -103,11 +138,12 @@ requesterDataToList (RequesterData m) = do
103138 (k', e') <- IntMap.toList e
104139 k'' :=> e'' <- requesterDataToList e'
105140 return $ RequesterDataKey_Multi k k' k'' :=> e''
106− MyTagType_Multi2 -> do
107− (k', e') <- Map.toList e
108− (k'', e'') <- IntMap.toList e'
109− k''' :=> e''' <- requesterDataToList e''
110− return $ RequesterDataKey_Multi2 k k' k'' k''' :=> e'''
141+ MyTagType_Multi2 -> case _multi2Contents_dict e of
142+ Dict -> do
143+ (k', e') <- Map.toList $ _multi2Contents_values e
144+ (k'', e'') <- IntMap.toList e'
145+ k''' :=> e''' <- requesterDataToList e''
146+ return $ RequesterDataKey_Multi2 k k' k'' k''' :=> e'''
111147 MyTagType_Multi3 -> do
112148 (k', e') <- IntMap.toList e
113149 (k'', e'') <- IntMap.toList e'
@@ -135,7 +171,13 @@ traverseRequesterData f (RequesterData m) = RequesterData <$> traverseTagMapWith
135171 go k (Entry request) = Entry <$> case myKeyType k of
136172 MyTagType_Single -> f request
137173 MyTagType_Multi -> traverse (traverseRequesterData f) request
138− MyTagType_Multi2 -> traverse (traverse (traverseRequesterData f)) request
174+ MyTagType_Multi2 -> case request of
175+ Multi2Contents { _multi2Contents_values = request', _multi2Contents_dict = Dict } -> do
176+ v <- traverse (traverse (traverseRequesterData f)) request'
177+ pure $ Multi2Contents
178+ { _multi2Contents_values = v
179+ , _multi2Contents_dict = Dict
180+ }
139181 MyTagType_Multi3 -> traverse (traverse (traverseRequesterData f)) request
140182
141183-- | 'traverseRequesterData' with its arguments flipped
@@ -179,9 +221,14 @@ instance MyTagTypeOffset Multi3 where
179221type family EntryContents request a where
180222 EntryContents request (Single a) = request a
181223 EntryContents request Multi = IntMap (RequesterData request)
182− EntryContents request (Multi2 k) = Map (Some k) (IntMap (RequesterData request))
224+ EntryContents request (Multi2 k) = Multi2Contents k request
183225 EntryContents request Multi3 = IntMap (IntMap (RequesterData request))
184226
227+data Multi2Contents k request = Multi2Contents
228+ { _multi2Contents_dict :: {-# UNPACK #-} !(Dict (GCompare k)) -- This is a Dict instead of an existential context because we only want to use it in certain circumstances
229+ , _multi2Contents_values :: {-# UNPACK #-} !(Map (Some k) (IntMap (RequesterData request)))
230+ }
231+
185232newtype Entry request x = Entry { unEntry :: EntryContents request x }
186233
187234{-# INLINE singleEntry #-}
@@ -310,7 +357,7 @@ tagRequest req = do
310357 return t
311358
312359{-# INLINE responseFromTag #-}
313−responseFromTag :: Monad m => MyTagWrap response (Entry response x) -> RequesterT t request response m (Event t (Entry response x))
360+responseFromTag :: forall m t request response x. Monad m => MyTagWrap response (Entry response x) -> RequesterT t request response m (Event t (Entry response x))
314361responseFromTag (MyTagWrap t) = do
315362 responses :: EventSelectorInt t Any <- RequesterT ask
316363 return $ (unsafeCoerce :: Event t Any -> Event t (Entry response x)) $ selectInt responses t
@@ -440,9 +487,9 @@ traverseDMapWithKeyWithAdjustRequesterTWith base mapPatch weakenPatchWith patchN
440487 let responses :: EventSelector t (Const2 (Some k) (IntMap (RequesterData response)))
441488 responses = fanMap $ fmapCheap unpack response
442489 unpack :: Entry response (Multi2 k) -> Map (Some k) (IntMap (RequesterData response))
443− unpack = unEntry
490+ unpack = _multi2Contents_values . unEntry
444491 pack :: Map (Some k) (IntMap (RequesterData request)) -> Entry request (Multi2 k)
445− pack = Entry
492+ pack m = Entry $ Multi2Contents { _multi2Contents_values = m, _multi2Contents_dict = Dict }
446493 f' :: forall a. k a -> Compose ((,) Int) v a -> m (Compose ((,) (Event t (IntMap (RequesterData request)))) v' a)
447494 f' k (Compose (n, v)) = do
448495 (result, myRequests) <- runRequesterT (f k v) $ mapMaybeCheap (IntMap.lookup n) $ select responses (Const2 (Some k))
@@ -463,9 +510,6 @@ traverseDMapWithKeyWithAdjustRequesterTWith base mapPatch weakenPatchWith patchN
463510data Decoder rawResponse response =
464511 forall a. Decoder (RequesterDataKey a) (rawResponse -> response a)
465512
466−-- | Matches incoming responses with previously-sent requests
467−-- and uses the provided request "decoder" function to process
468−-- incoming responses.
469513matchResponsesWithRequests
470514 :: forall t rawRequest rawResponse request response m.
471515 ( MonadFix m
@@ -484,7 +528,30 @@ matchResponsesWithRequests
484528 )
485529 -- ^ A map of outgoing wire-format requests and an event of responses keyed
486530 -- by the 'RequesterData' key of the associated outgoing request
487−matchResponsesWithRequests f send recv = do
531+matchResponsesWithRequests f send recv = matchResponseMapWithRequests f send $ uncurry Map.singleton <$> recv
532+
533+-- | Matches incoming responses with previously-sent requests
534+-- and uses the provided request "decoder" function to process
535+-- incoming responses.
536+matchResponseMapWithRequests
537+ :: forall t rawRequest rawResponse request response m.
538+ ( MonadFix m
539+ , MonadHold t m
540+ , Reflex t
541+ )
542+ => (forall a. request a -> (rawRequest, rawResponse -> response a))
543+ -- ^ Given a request (from 'Requester'), produces the wire format of the
544+ -- request and a function used to process the associated response
545+ -> Event t (RequesterData request)
546+ -- ^ The outgoing requests
547+ -> Event t (Map Int rawResponse)
548+ -- ^ A map of incoming responses, tagged by an identifying key
549+ -> m ( Event t (Map Int rawRequest)
550+ , Event t (RequesterData response)
551+ )
552+ -- ^ A map of outgoing wire-format requests and an event of responses keyed
553+ -- by the 'RequesterData' key of the associated outgoing request
554+matchResponseMapWithRequests f send recv = do
488555 rec nextId <- hold 1 $ fmap (\(next, _, _) -> next) outgoing
489556 waitingFor :: Incremental t (PatchMap Int (Decoder rawResponse response)) <-
490557 holdIncremental mempty $ leftmost
@@ -526,17 +593,15 @@ matchResponsesWithRequests f send recv = do
526593 processIncoming
527594 :: Incremental t (PatchMap Int (Decoder rawResponse response))
528595 -- A map of outstanding expected responses
529− -> Event t (Int, rawResponse)
596+ -> Event t (Map Int rawResponse)
530597 -- A incoming response paired with its identifying key
531598 -> Event t (RequesterData response, PatchMap Int v)
532599 -- The decoded response and a patch that clears the outstanding responses queue
533− processIncoming waitingFor inc = flip push inc $ \(n, rawRsp) -> do
600+ processIncoming waitingFor inc = flip push inc $ \rspMap -> do
534601 wf <- sample $ currentIncremental waitingFor
535− case Map.lookup n wf of
536− Nothing -> return Nothing -- TODO How should lookup failures be handled here? They shouldn't ever happen..
537− Just (Decoder k rspF) -> do
538− let rsp = rspF rawRsp
539− return $ Just
540− ( singletonRequesterData k rsp
541− , PatchMap $ Map.singleton n Nothing
542− )
602+ let match rawRsp (Decoder k rspF) =
603+ let rsp = rspF rawRsp
604+ in singletonRequesterData k rsp
605+ matches = Map.intersectionWith match rspMap wf
606+ pure $ if Map.null matches then Nothing else Just
607+ (Map.foldl' mergeRequesterData emptyRequesterData matches, PatchMap $ Nothing <$ matches)
src/Reflex/Spider/Internal.hs+2−2
@@ -9,7 +9,6 @@
99{-# LANGUAGE GADTs #-}
1010{-# LANGUAGE GeneralizedNewtypeDeriving #-}
1111{-# LANGUAGE LambdaCase #-}
12−{-# LANGUAGE MultiWayIf #-}
1312{-# LANGUAGE RankNTypes #-}
1413{-# LANGUAGE RoleAnnotations #-}
1514{-# LANGUAGE ScopedTypeVariables #-}
@@ -20,6 +19,7 @@
2019{-# LANGUAGE DeriveFunctor #-}
2120{-# LANGUAGE StandaloneDeriving #-}
2221{-# LANGUAGE InstanceSigs #-}
22+{-# LANGUAGE MultiWayIf #-}
2323
2424#ifdef USE_REFLEX_OPTIMIZER
2525{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
@@ -2775,7 +2775,7 @@ instance HasSpiderTimeline x => R.Reflex (SpiderTimeline x) where
27752775 fanG e = R.EventSelectorG $ SpiderEvent . selectG (fanG (unSpiderEvent e))
27762776 {-# INLINABLE mergeG #-}
27772777 mergeG
2778− :: forall (k :: k2 -> *) q (v :: k2 -> *). GCompare k
2778+ :: forall k2 (k :: k2 -> *) q (v :: k2 -> *). GCompare k
27792779 => (forall a. q a -> R.Event (SpiderTimeline x) (v a))
27802780 -> DMap k q
27812781 -> R.Event (SpiderTimeline x) (DMap k v)
test/GC.hs+1
@@ -18,6 +18,7 @@ import Data.GADT.Compare
1818import Data.IORef
1919import Data.Semigroup
2020import Data.These
21+import Data.Type.Equality ((:~:)(Refl))
2122
2223import Data.Functor.Misc
2324import Data.Patch
test/Headless.hsnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import Reflex
4+import Reflex.Host.Headless (runHeadlessApp)
5+
6+main :: IO ()
7+main = do
8+ runHeadlessApp $ do
9+ pb <- getPostBuild
10+ performEvent (pure <$> pb)
test/hlint.hs+2−1
@@ -1,7 +1,7 @@
11module Main where
22
33import Control.Monad
4−import Language.Haskell.HLint3 (hlint)
4+import Language.Haskell.HLint (hlint)
55import System.Directory
66import System.Exit (exitFailure, exitSuccess)
77import System.FilePath
@@ -22,6 +22,7 @@ main = do
2222 , "--ignore=Use unless"
2323 , "--ignore=Reduce duplication"
2424 , "--cpp-define=USE_TEMPLATE_HASKELL"
25+ , "--cpp-define=DEBUG"
2526 , "--ignore=Use tuple-section"
2627 ]
2728 recurseInto = and <$> sequence