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][]
[](https://github.com/tfausak/flow/actions/workflows/ci.yml)
[](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.