Repository: tfausak/flow Branch: main Commit: 0b0d11049396 Files: 12 Total size: 19.3 KB Directory structure: gitextract_2a2vd5gq/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .hlint.yaml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── cabal.project ├── flow.cabal ├── hlint-flow.yaml └── source/ ├── library/ │ └── Flow.hs └── test-suite/ └── Main.hs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ { "updates": [ { "directory": "/", "package-ecosystem": "github-actions", "schedule": { "interval": "weekly" } } ], "version": 2 } ================================================ FILE: .github/workflows/ci.yml ================================================ jobs: build: name: GHC ${{ matrix.ghc }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - run: mkdir artifact - uses: haskell/ghcup-setup@v1 with: ghc: ${{ matrix.ghc }} cabal: latest - run: ghc-pkg list - run: cabal sdist --output-dir artifact - run: cabal configure --enable-documentation --enable-tests --flags=pedantic --haddock-for-hackage --jobs - run: cat cabal.project.local - run: cp cabal.project.local artifact - run: cabal update - run: cabal freeze - run: cat cabal.project.freeze - run: cp cabal.project.freeze artifact - run: cabal outdated --v2-freeze-file - uses: actions/cache@v5 with: key: ${{ matrix.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }} path: ~/.local/state/cabal restore-keys: ${{ matrix.os }}-${{ matrix.ghc }}- - run: cabal build --only-download - run: cabal build --only-dependencies - run: cabal build - run: cp dist-newstyle/flow-*-docs.tar.gz artifact - run: tar --create --file artifact.tar --verbose artifact - uses: actions/upload-artifact@v7 with: name: flow-${{ github.sha }}-${{ matrix.os }}-${{ matrix.ghc }} path: artifact.tar - run: cabal run -- flow-test-suite --randomize --strict strategy: matrix: include: - ghc: 9.12 os: macos-14 - ghc: '9.10' os: ubuntu-24.04 - ghc: 9.12 os: ubuntu-24.04 - ghc: 9.14 os: ubuntu-24.04 - ghc: 9.12 os: windows-2022 cabal: name: Cabal runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - run: cabal check gild: name: Gild runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: tfausak/cabal-gild-setup-action@v2 - run: cabal-gild --input flow.cabal --mode check hlint: name: HLint runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: haskell-actions/hlint-setup@v2 - uses: haskell-actions/hlint-run@v2 with: fail-on: status ormolu: name: Ormolu runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: haskell-actions/run-ormolu@v17 release: if: ${{ github.event_name == 'release' }} name: Release needs: build permissions: contents: write runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v8 with: name: flow-${{ github.sha }}-ubuntu-24.04-9.12 - run: tar --extract --file artifact.tar --verbose - uses: softprops/action-gh-release@v3 with: files: artifact/flow-${{ github.event.release.tag_name }}.tar.gz - run: cabal upload --publish --username '${{ secrets.HACKAGE_USERNAME }}' --password '${{ secrets.HACKAGE_PASSWORD }}' artifact/flow-${{ github.event.release.tag_name }}.tar.gz - run: cabal --http-transport=plain-http upload --documentation --publish --username '${{ secrets.HACKAGE_USERNAME }}' --password '${{ secrets.HACKAGE_PASSWORD }}' artifact/flow-${{ github.event.release.tag_name }}-docs.tar.gz name: CI on: pull_request: branches: - main push: branches: - main release: types: - created ================================================ FILE: .gitignore ================================================ /.vscode/ /cabal.project.* /dist-newstyle/ ================================================ FILE: .hlint.yaml ================================================ [ { "group": { "enabled": true, "name": "dollar" } }, { "group": { "enabled": true, "name": "generalise" } }, { "ignore": { "name": "Avoid lambda" } }, { "ignore": { "name": "Redundant lambda" } }, { "ignore": { "name": "Use lambda-case" } }, { "ignore": { "name": "Use tuple-section" } } ] ================================================ FILE: CHANGELOG.md ================================================ # Change log Flow follows the [Package Versioning Policy](https://pvp.haskell.org). You can find release notes [on GitHub](https://github.com/tfausak/flow/releases). ================================================ FILE: LICENSE.txt ================================================ MIT License Copyright (c) 2026 Taylor Fausak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # [Flow][] [![CI](https://github.com/tfausak/flow/actions/workflows/ci.yml/badge.svg)](https://github.com/tfausak/flow/actions/workflows/ci.yml) [![Hackage](https://badgen.net/hackage/v/flow)](https://hackage.haskell.org/package/flow) Write more understandable Haskell. Flow is a package that provides functions and operators for writing more understandable Haskell. It is an alternative to some common idioms like [`($)`][] for function application and [`(.)`][] for function composition. - [Requirements](#requirements) - [Installation](#installation) - [Usage](#usage) - [Cheat sheet](#cheat-sheet) ## Requirements Flow requires a Haskell compiler. It is tested with recent versions of GHC, but older or different compilers should be acceptable. For installation with Cabal, Flow requires at least Cabal 2.2. ## Installation To add Flow as a dependency to your package, add it to your Cabal file. ``` build-depends: flow ==2.0.* ``` ## Usage Flow is designed to be imported unqualified. It does not export anything that conflicts with [the base package][]. ``` hs import Flow ``` ### Cheat sheet Flow | Base --------------- | ------------- x |> f | `x & f` f <| x | `f $ x` `apply x f` | `f x` `f .> g` | `f >>> g` `g <. f` | `g . f` `compose f g x` | `g (f x)` `x !> f` | - `f hlint -h hlint-flow.yaml ``` or ``` sh > hlint --git -h hlint-flow.yaml ``` to check all Haskell source tracked by git. For more information about Flow, please read [the Haddock documentation][]. [HLint]: https://github.com/ndmitchell/hlint [Flow]: http://taylor.fausak.me/flow/ [`($)`]: http://hackage.haskell.org/package/base-4.8.0.0/docs/Prelude.html#v:-36- [`(.)`]: http://hackage.haskell.org/package/base-4.8.0.0/docs/Prelude.html#v:. [the base package]: http://hackage.haskell.org/package/base [the haddock documentation]: https://hackage.haskell.org/package/flow/docs/Flow.html ================================================ FILE: cabal.project ================================================ packages: . ================================================ FILE: flow.cabal ================================================ cabal-version: 2.2 name: flow version: 2.0.0.11 synopsis: Write more understandable Haskell. description: Flow provides operators for writing more understandable Haskell. build-type: Simple category: Combinators, Functions, Utility extra-doc-files: CHANGELOG.md README.md license-file: LICENSE.txt license: MIT maintainer: Taylor Fausak source-repository head location: https://github.com/tfausak/flow type: git flag pedantic default: False description: Enables @-Werror@, which turns warnings into errors. manual: True common library build-depends: base ^>=4.20.0.0 || ^>=4.21.0.0 || ^>=4.22.0.0 default-language: Haskell2010 ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude -Wno-missing-exported-signatures -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-safe if flag(pedantic) ghc-options: -Werror common executable import: library build-depends: flow ghc-options: -rtsopts -threaded library import: library -- cabal-gild: discover source/library exposed-modules: Flow hs-source-dirs: source/library test-suite flow-test-suite import: executable build-depends: HUnit ^>=1.6.2.0 hs-source-dirs: source/test-suite main-is: Main.hs type: exitcode-stdio-1.0 ================================================ FILE: hlint-flow.yaml ================================================ - hint: lhs: 'f $! x' note: 'Use ``' rhs: 'f |> x' - hint: lhs: 'f . g' note: 'Use `<.`' rhs: 'f <. g' - hint: lhs: 'f <. g' note: 'Use `.>` for natural reading direction' rhs: 'g .> f' ================================================ FILE: source/library/Flow.hs ================================================ -- | Flow provides operators for writing more understandable Haskell. It is an -- alternative to some common idioms like ('Prelude.$') for function -- application and ('Prelude..') for function composition. -- -- Flow is designed to be imported unqualified. It does not export anything -- that conflicts with the base package. -- -- >>> import Flow -- -- == Rationale -- -- I think that Haskell can be hard to read. It has two operators for applying -- functions. Both are not really necessary and only serve to reduce -- parentheses. But they make code hard to read. People who do not already -- know Haskell have no chance of guessing what @foo $ bar@ or @baz & qux@ -- mean. -- -- Those that do know Haskell are forced to read lines forwards and backwards -- at the same time, thanks to function composition. Even something simple, -- like finding the minimum element, bounces around: @f = head . sort@. -- -- I think we can do better. By using directional operators, we can allow -- readers to move their eye in only one direction, be that left-to-right or -- right-to-left. And by using idioms common in other programming languages, -- we can allow people who aren't familiar with Haskell to guess at the -- meaning. -- -- So instead of ('Prelude.$'), I propose ('<|'). It is a pipe, which anyone -- who has touched a Unix system should be familiar with. And it points in the -- direction it sends arguments along. Similarly, replace ('Data.Function.&') -- with ('|>'). And for composition, ('<.') replaces ('Prelude..'). I would -- have preferred @<<@, but its counterpart @>>@ is taken by Haskell's syntax. -- So-called "backwards" composition is normally expressed with -- ('Control.Category.>>>'), which Flow provides as ('.>'). module Flow ( -- * Function application (|>), (<|), apply, -- * Function composition (.>), (<.), compose, -- * Strict function application (!>), (>> 3 |> succ |> recip |> negate -- -0.25 -- -- Or use it anywhere you would use ('Data.Function.&'). -- -- prop> \ x -> (x |> f) == f x -- -- prop> \ x -> (x |> f |> g) == g (f x) infixl 0 |> {-# INLINE (|>) #-} (|>) :: a -> (a -> b) -> b (|>) = apply -- | Right-associative 'apply' operator. Read as "apply backward" or "pipe -- from". Use this to create long chains of computation that suggest which -- direction things move in. You may prefer this operator over ('|>') for -- 'Prelude.IO' actions since it puts the last function first. -- -- >>> print <| negate <| recip <| succ <| 3 -- -0.25 -- -- Or use it anywhere you would use ('Prelude.$'). -- -- Note that ('<|') and ('|>') have the same precedence, so they cannot be used -- together. -- -- >>> -- This doesn't work! -- >>> -- print <| 3 |> succ |> recip |> negate -- -- prop> \ x -> (f <| x) == f x -- -- prop> \ x -> (g <| f <| x) == g (f x) infixr 0 <| {-# INLINE (<|) #-} (<|) :: (a -> b) -> a -> b (<|) f = f -- | Function application. This function usually isn't necessary, but it can be -- more readable than some alternatives when used with higher-order functions -- like 'Prelude.map'. -- -- >>> map (apply 2) [succ, recip, negate] -- [3.0,0.5,-2.0] -- -- In general you should prefer using an explicit lambda or operator section. -- -- >>> map (\ f -> 2 |> f) [succ, recip, negate] -- [3.0,0.5,-2.0] -- >>> map (2 |>) [succ, recip, negate] -- [3.0,0.5,-2.0] -- >>> map (<| 2) [succ, recip, negate] -- [3.0,0.5,-2.0] -- -- prop> \ x -> apply x f == f x {-# INLINE apply #-} apply :: a -> (a -> b) -> b apply x f = f x -- | Left-associative 'compose' operator. Read as "compose forward" or "and -- then". Use this to create long chains of computation that suggest which -- direction things move in. -- -- >>> let f = succ .> recip .> negate -- >>> f 3 -- -0.25 -- -- Or use it anywhere you would use ('Control.Category.>>>'). -- -- prop> \ x -> (f .> g) x == g (f x) -- -- prop> \ x -> (f .> g .> h) x == h (g (f x)) infixl 9 .> {-# INLINE (.>) #-} (.>) :: (a -> b) -> (b -> c) -> (a -> c) f .> g = compose f g -- | Right-associative 'compose' operator. Read as "compose backward" or "but -- first". Use this to create long chains of computation that suggest which -- direction things move in. You may prefer this operator over ('.>') for -- 'Prelude.IO' actions since it puts the last function first. -- -- >>> let f = print <. negate <. recip <. succ -- >>> f 3 -- -0.25 -- -- Or use it anywhere you would use ('Prelude..'). -- -- Note that ('<.') and ('.>') have the same precedence, so they cannot be used -- together. -- -- >>> -- This doesn't work! -- >>> -- print <. succ .> recip .> negate -- -- prop> \ x -> (g <. f) x == g (f x) -- -- prop> \ x -> (h <. g <. f) x == h (g (f x)) infixr 9 <. {-# INLINE (<.) #-} (<.) :: (b -> c) -> (a -> b) -> (a -> c) g <. f = compose f g -- | Function composition. This function usually isn't necessary, but it can be -- more readable than some alternatives when used with higher-order functions -- like 'Prelude.map'. -- -- >>> let fs = map (compose succ) [recip, negate] -- >>> map (apply 3) fs -- [0.25,-4.0] -- -- In general you should prefer using an explicit lambda or operator section. -- -- >>> map (\ f -> f 3) (map (\ f -> succ .> f) [recip, negate]) -- [0.25,-4.0] -- >>> map (\ f -> f 3) (map (succ .>) [recip, negate]) -- [0.25,-4.0] -- >>> map (\ f -> f 3) (map (<. succ) [recip, negate]) -- [0.25,-4.0] -- -- prop> \ x -> compose f g x == g (f x) {-# INLINE compose #-} compose :: (a -> b) -> (b -> c) -> (a -> c) compose f g = \x -> g (f x) -- | Left-associative 'apply'' operator. Read as "strict apply forward" or -- "strict pipe into". Use this to create long chains of computation that -- suggest which direction things move in. -- -- >>> 3 !> succ !> recip !> negate -- -0.25 -- -- The difference between this and ('|>') is that this evaluates its argument -- before passing it to the function. -- -- >>> undefined |> const True -- True -- >>> undefined !> const True -- *** Exception: Prelude.undefined -- ... -- -- prop> \ x -> (x !> f) == seq x (f x) -- -- prop> \ x -> (x !> f !> g) == let y = seq x (f x) in seq y (g y) infixl 0 !> {-# INLINE (!>) #-} (!>) :: a -> (a -> b) -> b (!>) = \x f -> f ') for 'Prelude.IO' actions since it puts the last function first. -- -- >>> print >> const True <| undefined -- True -- >>> const True ') have the same precedence, so they cannot be used -- together. -- -- >>> -- This doesn't work! -- >>> -- print succ !> recip !> negate -- -- prop> \ x -> (f \ x -> (g b) -> a -> b (>> map (apply' 2) [succ, recip, negate] -- [3.0,0.5,-2.0] -- -- The different between this and 'apply' is that this evaluates its argument -- before passing it to the function. -- -- >>> apply undefined (const True) -- True -- >>> apply' undefined (const True) -- *** Exception: Prelude.undefined -- ... -- -- In general you should prefer using an explicit lambda or operator section. -- -- >>> map (\ f -> 2 !> f) [succ, recip, negate] -- [3.0,0.5,-2.0] -- >>> map (2 !>) [succ, recip, negate] -- [3.0,0.5,-2.0] -- >>> map ( \ x -> apply' x f == seq x (f x) {-# INLINE apply' #-} apply' :: a -> (a -> b) -> b apply' = (!>) ================================================ FILE: source/test-suite/Main.hs ================================================ import qualified Control.Monad as Monad import qualified Flow import qualified System.Exit as Exit import qualified Test.HUnit as Test main :: IO () main = do counts <- Test.runTestTT $ Test.TestList [ True Test.~?= True, (3 Flow.|> succ Flow.|> recip Flow.|> negate) Test.~?= (-0.25 :: Double), (negate Flow.<| recip Flow.<| succ Flow.<| 3) Test.~?= (-0.25 :: Double), fmap (Flow.apply 2) [succ, recip, negate] Test.~?= [3, 0.5, -2 :: Double], fmap (2 Flow.|>) [succ, recip, negate] Test.~?= [3, 0.5, -2 :: Double], fmap (2 Flow.|>) [succ, recip, negate] Test.~?= [3, 0.5, -2 :: Double], fmap (Flow.<| 2) [succ, recip, negate] Test.~?= [3, 0.5, -2 :: Double], fmap (Flow.apply 3 . Flow.compose succ) [recip, negate] Test.~?= [0.25, -4 :: Double], (succ Flow..> recip Flow..> negate) 3 Test.~?= (-0.25 :: Double), (negate Flow.<. recip Flow.<. succ) 3 Test.~?= (-0.25 :: Double), fmap ((\f -> f 3) . (succ Flow..>)) [recip, negate] Test.~?= [0.25, -4 :: Double], fmap ((\f -> f 3) . (succ Flow..>)) [recip, negate] Test.~?= [0.25, -4 :: Double], fmap ((\f -> f 3) . (Flow.<. succ)) [recip, negate] Test.~?= [0.25, -4 :: Double], (3 Flow.!> succ Flow.!> recip Flow.!> negate) Test.~?= (-0.25 :: Double), (undefined Flow.|> const True) Test.~?= True, (negate Flow.) [succ, recip, negate] Test.~?= [3, 0.5, -2 :: Double], fmap (2 Flow.!>) [succ, recip, negate] Test.~?= [3, 0.5, -2 :: Double], fmap (Flow.