[Haskell-cafe] Proposal: (.:) operator in base.

92 views
Skip to first unread message

Alex Belanger

unread,
Aug 17, 2016, 1:43:49 PM8/17/16
to Haskell Cafe
Hi,

Some of you might be familiar with (.:) = (.) . (.).

It has type :: (c -> d) -> (a -> b -> c) -> a -> b -> d

It allows the composition of two functions, the first one, accepting one operand, and the second, two operands.

This appears to be a very common pattern, referenced a bit everywhere, almost always defined on lambdabot and found in multiple codebases in the wild.

I'd like the know the general sentiment about this operator, as well as how its inclusion in base, probably Data.Function, would be perceived before I actually try to make it happen.

Cheers,
Alex (nitrix).

Tony Morris

unread,
Aug 17, 2016, 5:15:52 PM8/17/16
to haskel...@haskell.org

You'd generalise it to:

fmap . fmap :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)

And then, would you do the same for Traversable, Foldable and Applicative?

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.

signature.asc

Csongor Kiss

unread,
Aug 17, 2016, 5:39:47 PM8/17/16
to haskel...@haskell.org, Tony Morris
Can you provide a bit more detail on why you think (fmap . fmap) captures
the concept behind (.) . (.) ? 
It certainly does generalise it in one way - and I do get how, but is it the *right* generalisation?

Or does it just coincide with (-> e)’s fmap being defined as (.), and in fact we would prefer to
generalise the composition aspect, in which case Category would be a more ‘obvious’ place to look?

Csongor Kiss

unread,
Aug 17, 2016, 5:45:24 PM8/17/16
to Tony Morris, haskel...@haskell.org
Of course I meant to write ((->) e)

MarLinn via Haskell-Cafe

unread,
Aug 17, 2016, 7:56:12 PM8/17/16
to haskel...@haskell.org

Some of you might be familiar with (.:) = (.) . (.).

It has type :: (c -> d) -> (a -> b -> c) -> a -> b -> d

First of all, I always called it "dot". Don't know where I picked that one up, though. (.:) looks like it could conflict with some lens or something. Would need checking.

Also, which version? This simple one? Or the Category one?
    (.).(.) :: (Category cat) => cat c d -> (a -> cat b c) -> a -> cat b d

I know little about Category Theory, but throwing together some random words I collected over time that looks like... a "contravariant bind in the monad of endocategories", maybe? I just invented that and it's almost certainly meaningless gibberish, but knowing our community it might well be that there's a lib for that.

Also, why is
    h = f .: g == (f .) . g 

not good enough?

So many questions, so much bikeshedding to do, so little time...

Alex Belanger

unread,
Aug 17, 2016, 10:08:36 PM8/17/16
to MarLinn, Haskell Cafe
Considering this would be in Data.Function along (.) and ($), I do not think generalizing it is the goal.
The aim should be to keep it for functions only... or at least, that's how I've seen (.:) used so far.

I'm all ears though. This is already getting interesting.

Albert Y. C. Lai

unread,
Aug 18, 2016, 12:22:51 AM8/18/16
to haskel...@haskell.org
(.:) is already used in both aeson and cassava. And not for (.) . (.)

Oleg Grenrus

unread,
Aug 18, 2016, 12:43:51 AM8/18/16
to Albert Y. C. Lai, haskel...@haskell.org
Aeson (and cassava) use is very isolated. 

FWIW (.=) is in aeson (and other serialising libs) and lens, and do
totally different things. I never needed both in the same module.

Bikeshedding: if (.:) goes to base, why not also (.:.) and (.::) and …

I’m -1, there is `composition` package [1], and I rather see base shrink,
not grow.

- Oleg

signature.asc

Mike Ledger

unread,
Aug 18, 2016, 5:57:35 PM8/18/16
to haskell-cafe

(Forgot reply all)

---------- Forwarded message ----------
From: "Mike Ledger" <eleven...@gmail.com>
Date: 18 Aug 2016 7:52 AM
Subject: Re: [Haskell-cafe] Proposal: (.:) operator in base.
To: "Csongor Kiss" <kiss.cso...@gmail.com>
Cc:

IMO: I've never seen a compelling use of this; I think it decreases clarity, and barely shaves off any characters from equivalent "pointful" expressions.

Alexander Berntsen

unread,
Aug 19, 2016, 9:08:48 AM8/19/16
to haskel...@haskell.org
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

On 18/08/16 23:57, Mike Ledger wrote:
> IMO: I've never seen a compelling use of this; I think it decreases
> clarity, and barely shaves off any characters from equivalent "pointful"
> expressions.

I think it quite compelling. An example:

asSeconds :: Hour h -> Minute m -> Second s -> Second t
- -- | asSeconds take some 'TimeUnit's and convert them to 'Second's.
asSeconds h m s = MkSecond $ 3600 * timeVal h + 60 * timeVal m + timeVal s

hmsToDiffTime :: Hour h -> Minute m -> Second s -> DiffTime
- -- | 'hmsToDiffTime' converts time of day in hours, minutes and seconds to the
- -- time from midnight as a 'DiffTime'.
hmsToDiffTime = (secondsToDiffTime . timeVal) .:. asSeconds


As for putting it in base -- meh. I use the composition package, or
define it myself. So I agree with Oleg; -1.
- --
Alexander
alex...@plaimi.net
https://secure.plaimi.net/~alexander
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAEBCgAGBQJXtwTXAAoJENQqWdRUGk8BP/UQAIcKzqVRhkVs0qtFDneTpSIi
jLPPXMELbMnQe/ZQH2c5SaVM4+eDJIky33FEzrh9FWMeDftz1QFrsC+pPq+5/Esw
U3h1HJrAJKl+zZ/bC1GQj9ObWKNROj3jEEt9pyNBcoHpxVnCohD3Y6Gx2LknE7m0
hnFlO0rCZZamk+pSeSlmLkkDM/MNxTx/FQcuZT0omxZtvzrz4cN7a069Gx35C+vr
RK6NpiD5NUqTvMCL0zeI2yRpg2GV2PQcwV0CEWjLCsJYMcoeiF8+prSBdwzatgjH
7k1bHUdGwvcRzB74dyxIOPyHBgIDUFzj7bcE+NT0V20WXaz/Jgl73zZaIG9XtW19
aJ+RzW+aBSLEh8+uERc+Al9NpM9dLR94mDWIK/MZWEmlFHo8+W/md27+OxvM5BOr
YCRpoQYeYDkepTAyRK2wRlszFXydDze8K6PMAq4DUE7xtUXofEbcodWA0quLxP4N
ro2A8E4kqnO6BVD7iN+qch8TxmEiVYpqZnAjNz8eSyQCdn9UORpcMLT8yQ963nml
17yKRjHYDwg/g8EC1UjqJ9KwVFRhqQwdk//IupRyZ/5PPkEChy8t/ZjmmGKfLy0H
CfhpRiNi0ozR+bYpUJcOdNM0ckCzgq5++9+M643RGjNWvD4fKDI8idNNn9eLlcom
kaABFBC5Gbx2O6/rgpHc
=X/Rb
-----END PGP SIGNATURE-----

Alex Belanger

unread,
Aug 19, 2016, 10:52:01 AM8/19/16
to Alexander Berntsen, Haskell Cafe

I didn't even know about the composition package. I just wanted it to be readily available to everyone.

A quick look, `composition` seems really nice and is probably more aligned with Haskell ecosystem's philosophy of keeping base to what is only stricly needed by GHC.

These were some valuable feedback. I think I got my answer and we can let the thread die (:

Much appreciated,
Alex

Theodore Lief Gannon

unread,
Aug 19, 2016, 2:17:06 PM8/19/16
to Haskell Cafe
Well... there's that rather worrisome introductory paragraph of the Data.Composition docs, though:

"This module is for convenience and demonstrative purposes more than it is for providing actual value. I do not recommend that you rely on this module for performance-sensitive code. Because this module is not based on Prelude's (.), some chances at optimization might be missed by your compiler."

Joachim Breitner

unread,
Aug 19, 2016, 2:50:58 PM8/19/16
to haskel...@haskell.org
Hi,

Am Freitag, den 19.08.2016, 11:16 -0700 schrieb Theodore Lief Gannon:
> Well... there's that rather worrisome introductory paragraph of the
> Data.Composition docs, though:
>
> "This module is for convenience and demonstrative purposes more than
> it is for providing actual value. I do not recommend that you rely on
> this module for performance-sensitive code. Because this module is
> not based on Prelude's (.), some chances at optimization might be
> missed by your compiler."

I wonder if that is really something to worry about. Prelude’s (.) is
not special in any way:

-- | Function composition.
{-# 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)

The INLINE pragma and the definition with the right arity could be used
in Data.Composition as well, and it would yield the same results.

Greetings,
Joachim

--
Joachim “nomeata” Breitner
  ma...@joachim-breitner.dehttps://www.joachim-breitner.de/
  XMPP: nom...@joachim-breitner.de • OpenPGP-Key: 0xF0FBF51F
  Debian Developer: nom...@debian.org
signature.asc

David Menendez

unread,
Aug 19, 2016, 10:38:29 PM8/19/16
to Joachim Breitner, haskell
On Fri, Aug 19, 2016 at 2:50 PM, Joachim Breitner <ma...@joachim-breitner.de> wrote:
Hi,

Am Freitag, den 19.08.2016, 11:16 -0700 schrieb Theodore Lief Gannon:
> Well... there's that rather worrisome introductory paragraph of the
> Data.Composition docs, though:
>
> "This module is for convenience and demonstrative purposes more than
> it is for providing actual value. I do not recommend that you rely on
> this module for performance-sensitive code. Because this module is
> not based on Prelude's (.), some chances at optimization might be
> missed by your compiler."

I wonder if that is really something to worry about. Prelude’s (.) is
not special in any way: 
<snip> 
The INLINE pragma and the definition with the right arity could be used
in Data.Composition as well, and it would yield the same results.

Any RULEs involving Prelude’s (.) would need to be copied as well.

--

David Feuer

unread,
Aug 20, 2016, 1:29:44 PM8/20/16
to David Menendez, Joachim Breitner, haskell
On Fri, Aug 19, 2016 at 10:38 PM, David Menendez <da...@zednenem.com> wrote:

> Any RULEs involving Prelude’s (.) would need to be copied as well.

There aren't any, and if there are any then they're almost certainly
broken. The INLINE pragma on (.) isn't marked with a simplifier phase,
so it will be inlined almost immediately when optimization begins, as
long as it has at least two arguments. There is no time for rules
involving it to take effect. Rewrite rules end up working with the
inlined version. For example, Data.Sequence has a rule saying

forall f g xs . fmapSeq f (fmapSeq g xs) = fmapSeq (f . g) xs

So if you write

fmap f . fmap g

for sequences, this will specialize to

fmapSeq f . fmapSeq g

and inline to

\xs -> fmapSeq f (fmapSeq g xs)

at which point the rewrite rule will fire, changing it to

\xs -> fmapSeq (f . g) xs

This, in turn, will inline as well, to

\xs -> fmapSeq (\x -> f (g x)) xs

wren romano

unread,
Aug 23, 2016, 1:23:17 AM8/23/16
to haskell
On Fri, Aug 19, 2016 at 11:50 AM, Joachim Breitner
<ma...@joachim-breitner.de> wrote:
> Hi,
>
> Am Freitag, den 19.08.2016, 11:16 -0700 schrieb Theodore Lief Gannon:
>> Well... there's that rather worrisome introductory paragraph of the
>> Data.Composition docs, though:
>>
>> "This module is for convenience and demonstrative purposes more than
>> it is for providing actual value. I do not recommend that you rely on
>> this module for performance-sensitive code. Because this module is
>> not based on Prelude's (.), some chances at optimization might be
>> missed by your compiler."
>
> I wonder if that is really something to worry about. Prelude’s (.) is
> not special in any way:

Yes and no. Prelude's (.) is not special in that GHC doesn't identify
it as something that must be treated differently from what's expected
from the definition (as opposed to, say, ($)). However, (.) is
inherently a special case because of performance issues about
closures. For example, whether we define:

(.) f g = \x -> f (g x)

vs:

(.) f g x = f (g x)

has ramifications, though it's fairly easy to guess which one of those
two will be most performant. However, it's much less clear which of
the following definitions will be the most performant:

(.:) = (.) . (.)

(.:) f g = (f .) . g

(.:) f g = \x y -> f (g x y)

For the version implemented in pointless-fun:Data.Function.Pointless,
I actually did some benchmarking to determine which was fastest. It is
(a) faster by a surprisingly large margin, and (b) not the one I
expected to be the fastest. I haven't run those benchmarks on the
latest versions of GHC, but that just underscores the point. Because
the performance difference is significant and unexpected, having a
single blessed (and maintained!) definition would be beneficial to
anyone who actually uses this function in production code— whether
that blessed definition is in base or some other library everyone has
and knows about.

--
Live well,
~wren

Tom Ellis

unread,
Aug 23, 2016, 3:09:27 AM8/23/16
to haskel...@haskell.org
On Mon, Aug 22, 2016 at 10:23:07PM -0700, wren romano wrote:
> (.) f g = \x -> f (g x)
>
> vs:
>
> (.) f g x = f (g x)
>
> has ramifications, though it's fairly easy to guess which one of those
> two will be most performant.

Are these not synonyms? What is the meaning of

fargs var = expr

if not

fargs = \var -> expr

?

Ben

unread,
Aug 23, 2016, 3:19:51 AM8/23/16
to haskel...@haskell.org
At the semantic level of "does my program compute correct results" they're identical. At the operational level of "how fast does my program run" they're different.


On August 23, 2016 5:09:19 PM GMT+10:00, Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> wrote:
On Mon, Aug 22, 2016 at 10:23:07PM -0700, wren romano wrote:
(.) f g = \x -> f (g x)

vs:

(.) f g x = f (g x)

has ramifications, though it's fairly easy to guess which one of those
two will be most performant.

Are these not synonyms? What is the meaning of

fargs var = expr

if not

fargs = \var -> expr

?


Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.

--
Sent from my Android device with K-9 Mail. Please excuse my brevity.

Tom Ellis

unread,
Aug 23, 2016, 3:33:10 AM8/23/16
to haskel...@haskell.org
On the contrary, they're exactly the same (on GHC 7.6.3):

module Foo where

comp1 f g x = f (g x)

comp2 f g = \x -> f (g x)


% ghc -O0 -dsuppress-all -fforce-recomp -no-link -ddump-prep test.hs
[1 of 1] Compiling Foo ( test.hs, test.o )

==================== CorePrep ====================
Result size of CorePrep = {terms: 24, types: 36, coercions: 0}

comp2
comp2 =
\ @ t_aeU @ t1_aeV @ t2_aeW f_sfz g_sfy x_sfx ->
let {
sat_sfI
sat_sfI = g_sfy x_sfx } in
f_sfz sat_sfI

comp1
comp1 =
\ @ t_af5 @ t1_af6 @ t2_af7 f_sfG g_sfF x_sfE ->
let {
sat_sfJ
sat_sfJ = g_sfF x_sfE } in
f_sfG sat_sfJ

(the same holds for -O2, if you compile them separately)

On Tue, Aug 23, 2016 at 05:19:38PM +1000, Ben wrote:
> At the semantic level of "does my program compute correct results" they're
> identical. At the operational level of "how fast does my program run"
> they're different.
>
>
> On August 23, 2016 5:09:19 PM GMT+10:00, Tom Ellis <tom-lists-has...@jaguarpaw.co.uk> wrote:
> >On Mon, Aug 22, 2016 at 10:23:07PM -0700, wren romano wrote:
> >> (.) f g = \x -> f (g x)
> >>
> >> vs:
> >>
> >> (.) f g x = f (g x)
> >>
> >> has ramifications, though it's fairly easy to guess which one of
> >those
> >> two will be most performant.
> >
> >Are these not synonyms? What is the meaning of
> >
> > fargs var = expr
> >
> >if not
> >
> > fargs = \var -> expr
> >
> >?

_______________________________________________

Matthew Pickering

unread,
Aug 23, 2016, 4:56:33 AM8/23/16
to Tom Ellis, Haskell Cafe
I think the point which no-one has articulated yet is that the
source-level arity of (.) affects whether GHC will decide to inline
it.
Only fully saturated applications are inlined, thus by having two
explicit arguments GHC will inline (.) in the common case where is it
used with two arguments which will improve optimisation opportunities
for code which composes many functions.

On the other hand, it is not very common to write

> (.) f g x

which is why (.) is defined in such a way.

Matt

On Tue, Aug 23, 2016 at 8:33 AM, Tom Ellis

Tom Ellis

unread,
Aug 23, 2016, 5:22:22 AM8/23/16
to haskel...@haskell.org
*Very* interesting. Here is the key section of the GHC users guide:

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

That this is a good heuristic is *extremely* counterintuitive to me. I
would have supposed that being more explicit, for example

{-# INLINE (.) f g #-}

to inline on all static applications of at least two arguments would have
been a much clearer way to communicate this message.

What's the rationale behind the current behaviour?

Tom


On Tue, Aug 23, 2016 at 09:56:25AM +0100, Matthew Pickering wrote:
> I think the point which no-one has articulated yet is that the
> source-level arity of (.) affects whether GHC will decide to inline it.
>
> Only fully saturated applications are inlined

[...]

Alan & Kim Zimmerman

unread,
Aug 23, 2016, 5:42:50 AM8/23/16
to haskell, Tom Ellis

Suddenly the eta reduce hlint warning becomes much more important.

Alan


On 23 Aug 2016 11:22 a.m., "Tom Ellis" <tom-lists-has...@jaguarpaw.co.uk> wrote:
*Very* interesting.  Here is the key section of the GHC users guide:

    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

That this is a good heuristic is *extremely* counterintuitive to me.  I
would have supposed that being more explicit, for example

    {-# INLINE (.) f g #-}

to inline on all static applications of at least two arguments would have
been a much clearer way to communicate this message.

What's the rationale behind the current behaviour?

Tom


On Tue, Aug 23, 2016 at 09:56:25AM +0100, Matthew Pickering wrote:
> I think the point which no-one has articulated yet is that the
> source-level arity of (.) affects whether GHC will decide to inline it.
>
> Only fully saturated applications are inlined
[...]
>
> On Tue, Aug 23, 2016 at 8:33 AM, Tom Ellis

David Feuer

unread,
Aug 23, 2016, 9:29:53 AM8/23/16
to Alan & Kim Zimmerman, Tom Ellis, haskel...@haskell.org

And also sometimes wrong. I don't really understand why, but sometimes eta *expansion* is important when working around RULES.


On Aug 23, 2016 5:42 AM, "Alan & Kim Zimmerman" <alan...@gmail.com> wrote:

Suddenly the eta reduce hlint warning becomes much more important.

Alan

Conal Elliott

unread,
Aug 23, 2016, 5:59:24 PM8/23/16
to Tony Morris, Haskell Cafe
Exactly! I'd be disappointed to see (.:) get enshrined in the base when it's such a special case: just one functor ((->) a), just one class (Functor), and just two levels deep. Once people learn the general patterns, they're easy to read & write and *much* more general & flexible. See http://conal.net/blog/posts/semantic-editor-combinators and the lens libraries.

-- Conal
Reply all
Reply to author
Forward
0 new messages