tfausak / flow

:droplet: Write more understandable Haskell.
https://hackage.haskell.org/package/flow
MIT License
200 stars 11 forks source link

Inlining? #35

Open ratherforky opened 5 months ago

ratherforky commented 5 months ago

I love this library and use it in basically every Haskell project, I just have one quibble: inlining

In Prelude, the source code for (.) is:

{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

But (.>), (<.), (|>) etc. in Flow don't have any inline pragmas. I only checked this because (|>) showed up as a non-zero cost centre in some code I was profiling and I was surprised.

Changing the library to inline nicely should be fairly straightforward and I'd be happy to make a pull request for it. It'd mostly be a case of adding inline pragmas for everything, with the biggest change being compose f g x = g (f x) to compose f g = \x -> g (f x) for the same reason (.) is like that.[^1][^2]

[^1]: > Moreover, GHC will only inline the function if it is fully applied, where “fully applied” means applied to as many arguments as appear (syntactically) on the LHS of the function definition. [^2]: I only learned about this behaviour after asking r/haskell to explain some surprising performance of foldl' and patching it in base

tfausak commented 5 months ago

👍 I'd happily accept a PR for improving performance. Bonus points for a benchmark that actually shows the improvement.

tfausak commented 5 months ago

I wonder if it would be sufficient to change how these operators are defined. The current definitions are perhaps overly verbose.

-- current
x |> f = apply x f
f <| x = apply x f
apply x f = f x

f .> g = compose f g
g <. f = compose f g
compose f g x = g (f x)

x !> f = apply' x f
f <! x = apply' x f
apply' x f = seq x (apply x f)
-- could be
(|>) = apply
(<|) = flip apply
apply = (Data.Function.&)
-- or flip ($)
-- neither (&) nor flip have INLINE pragmas

(.>) = compose
(<.) = flip compose
compose = (Control.Category.>>>)
-- or flip (.)
-- "Note [INLINE on >>>]" https://hackage.haskell.org/package/base-4.19.1.0/docs/src/Control.Category.html#line-82

(!>) = apply'
(<!) = flip apply'
apply' = flip ($!)