Discussion:
Proposal: Export (#.) and (.#) from Data.Coerce
Daniel Cartwright
2018-04-20 19:56:02 UTC
Permalink
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}

(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}

The first of these is exported from Data.Functor.Util, and used in many
places inside of base for efficiency over '.' (compose), However no module
in base actually exports these. I have recently been using Data.Coerce more
frequently and think it would be useful to go ahead and export these from
somewhere in base.

For convenience, I will paste the note about (#.) from Data.Functor.Util:

"Note [Function coercion]
~~~~~~~~~~~~~~~~~~~~~~~

Several functions here use (#.) instead of (.) to avoid potential efficiency
problems relating to #7542. The problem, in a nutshell:

If N is a newtype constructor, then N x will always have the same
representation as x (something similar applies for a newtype deconstructor).
However, if f is a function,

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

This looks almost the same as f, but the eta expansion lifts it--the lhs
could
be _|_, but the rhs never is. This can lead to very inefficient code. Thus
we
steal a technique from Shachaf and Edward Kmett and adapt it to the current
(rather clean) setting. Instead of using N . f, we use N #. f, which is
just

coerce f `asTypeOf` (N . f)

That is, we just *pretend* that f has the right type, and thanks to the
safety
of coerce, the type checker guarantees that nothing really goes wrong. We
still
have to be a bit careful, though: remember that #. completely ignores the
*value* of its left operand.
"
Li-yao Xia
2018-04-21 14:06:55 UTC
Permalink
That seems useful indeed! Using only 'coerce' requires too many type
annotations.

Would it make sense to generalize the type so it's clear that one
argument is unused?

(#.) :: Coercible b c => p b c -> (a -> b) -> (a -> c)
(.#) :: Coercible a b => (b -> c) -> p a b -> (a -> c)

Li-yao
Post by Daniel Cartwright
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}
The first of these is exported from Data.Functor.Util, and used in many
places inside of base for efficiency over '.' (compose), However no module
in base actually exports these. I have recently been using Data.Coerce more
frequently and think it would be useful to go ahead and export these from
somewhere in base.
"Note [Function coercion]
~~~~~~~~~~~~~~~~~~~~~~~
Several functions here use (#.) instead of (.) to avoid potential efficiency
If N is a newtype constructor, then N x will always have the same
representation as x (something similar applies for a newtype deconstructor).
However, if f is a function,
N . f = \x -> N (f x)
This looks almost the same as f, but the eta expansion lifts it--the lhs
could
be _|_, but the rhs never is. This can lead to very inefficient code. Thus
we
steal a technique from Shachaf and Edward Kmett and adapt it to the current
(rather clean) setting. Instead of using N . f, we use N #. f, which is
just
coerce f `asTypeOf` (N . f)
That is, we just *pretend* that f has the right type, and thanks to the
safety
of coerce, the type checker guarantees that nothing really goes wrong. We
still
have to be a bit careful, though: remember that #. completely ignores the
*value* of its left operand.
"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20180420/c5646504/attachment-0001.html>
Andrew Martin
2018-04-21 14:16:57 UTC
Permalink
Generalizing the type like that helps a lot. I had skimmed the documentation before, but I didn’t immediately comprehend what it meant. When I read the type signatures you gave them, it immediately made sense.

Also, +1 on exporting these.

Sent from my iPhone
That seems useful indeed! Using only 'coerce' requires too many type annotations.
Would it make sense to generalize the type so it's clear that one argument is unused?
(#.) :: Coercible b c => p b c -> (a -> b) -> (a -> c)
(.#) :: Coercible a b => (b -> c) -> p a b -> (a -> c)
Li-yao
Post by Daniel Cartwright
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}
The first of these is exported from Data.Functor.Util, and used in many
places inside of base for efficiency over '.' (compose), However no module
in base actually exports these. I have recently been using Data.Coerce more
frequently and think it would be useful to go ahead and export these from
somewhere in base.
"Note [Function coercion]
~~~~~~~~~~~~~~~~~~~~~~~
Several functions here use (#.) instead of (.) to avoid potential efficiency
If N is a newtype constructor, then N x will always have the same
representation as x (something similar applies for a newtype deconstructor).
However, if f is a function,
N . f = \x -> N (f x)
This looks almost the same as f, but the eta expansion lifts it--the lhs
could
be _|_, but the rhs never is. This can lead to very inefficient code. Thus
we
steal a technique from Shachaf and Edward Kmett and adapt it to the current
(rather clean) setting. Instead of using N . f, we use N #. f, which is
just
coerce f `asTypeOf` (N . f)
That is, we just *pretend* that f has the right type, and thanks to the
safety
of coerce, the type checker guarantees that nothing really goes wrong. We
still
have to be a bit careful, though: remember that #. completely ignores the
*value* of its left operand.
"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20180420/c5646504/attachment-0001.html>
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Edward Kmett
2018-04-21 23:26:52 UTC
Permalink
Note: this way of indicating which argument is unused means that this
confusingly looks almost precisely backwards relative to the more general
code
<https://hackage.haskell.org/package/profunctors-5.2.2/docs/Data-Profunctor-Unsafe.html>
that created those names and usage pattern in the first place. (I don't
have a particularly strong objection to allowing the same sort of tweak to
that code, assuming type inference works out in practice for the major
consumers of the combinators there. I don't really foresee a problem, but
I've been surprised by interactions before.)

I do have some concern that exporting these incompatible versions from
Data.Coerce would break a subset of the code that is using the more general
combinators in profunctors. e.g. in the lens library these were coined for
the code for prisms imports both.

This is why they were placed in a more obscure internal location to begin
with as just the special case was needed by base and it was easier to write
a copy locally than merge Profunctor to base.

-Edward
Post by Andrew Martin
Generalizing the type like that helps a lot. I had skimmed the
documentation before, but I didn’t immediately comprehend what it meant.
When I read the type signatures you gave them, it immediately made sense.
Also, +1 on exporting these.
Sent from my iPhone
Post by Li-yao Xia
That seems useful indeed! Using only 'coerce' requires too many type
annotations.
Post by Li-yao Xia
Would it make sense to generalize the type so it's clear that one
argument is unused?
Post by Li-yao Xia
(#.) :: Coercible b c => p b c -> (a -> b) -> (a -> c)
(.#) :: Coercible a b => (b -> c) -> p a b -> (a -> c)
Li-yao
Post by Daniel Cartwright
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}
The first of these is exported from Data.Functor.Util, and used in many
places inside of base for efficiency over '.' (compose), However no
module
Post by Li-yao Xia
Post by Daniel Cartwright
in base actually exports these. I have recently been using Data.Coerce
more
Post by Li-yao Xia
Post by Daniel Cartwright
frequently and think it would be useful to go ahead and export these
from
Post by Li-yao Xia
Post by Daniel Cartwright
somewhere in base.
For convenience, I will paste the note about (#.) from
"Note [Function coercion]
~~~~~~~~~~~~~~~~~~~~~~~
Several functions here use (#.) instead of (.) to avoid potential
efficiency
Post by Li-yao Xia
Post by Daniel Cartwright
If N is a newtype constructor, then N x will always have the same
representation as x (something similar applies for a newtype
deconstructor).
Post by Li-yao Xia
Post by Daniel Cartwright
However, if f is a function,
N . f = \x -> N (f x)
This looks almost the same as f, but the eta expansion lifts it--the lhs
could
be _|_, but the rhs never is. This can lead to very inefficient code.
Thus
Post by Li-yao Xia
Post by Daniel Cartwright
we
steal a technique from Shachaf and Edward Kmett and adapt it to the
current
Post by Li-yao Xia
Post by Daniel Cartwright
(rather clean) setting. Instead of using N . f, we use N #. f, which
is
Post by Li-yao Xia
Post by Daniel Cartwright
just
coerce f `asTypeOf` (N . f)
That is, we just *pretend* that f has the right type, and thanks to the
safety
of coerce, the type checker guarantees that nothing really goes wrong.
We
Post by Li-yao Xia
Post by Daniel Cartwright
still
have to be a bit careful, though: remember that #. completely ignores
the
Post by Li-yao Xia
Post by Daniel Cartwright
*value* of its left operand.
"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/
attachments/20180420/c5646504/attachment-0001.html>
Post by Li-yao Xia
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-04-21 23:44:19 UTC
Permalink
I don't personally think the slightly confusing appearance is worth
worrying about. If inference works out, I like Li-yao's approach a lot.
Post by Edward Kmett
Note: this way of indicating which argument is unused means that this
confusingly looks almost precisely backwards relative to the more general
code
<https://hackage.haskell.org/package/profunctors-5.2.2/docs/Data-Profunctor-Unsafe.html>
that created those names and usage pattern in the first place. (I don't
have a particularly strong objection to allowing the same sort of tweak to
that code, assuming type inference works out in practice for the major
consumers of the combinators there. I don't really foresee a problem, but
I've been surprised by interactions before.)
I do have some concern that exporting these incompatible versions from
Data.Coerce would break a subset of the code that is using the more general
combinators in profunctors. e.g. in the lens library these were coined
for the code for prisms imports both.
This is why they were placed in a more obscure internal location to begin
with as just the special case was needed by base and it was easier to write
a copy locally than merge Profunctor to base.
-Edward
Post by Andrew Martin
Generalizing the type like that helps a lot. I had skimmed the
documentation before, but I didn’t immediately comprehend what it meant.
When I read the type signatures you gave them, it immediately made sense.
Also, +1 on exporting these.
Sent from my iPhone
Post by Li-yao Xia
That seems useful indeed! Using only 'coerce' requires too many type
annotations.
Post by Li-yao Xia
Would it make sense to generalize the type so it's clear that one
argument is unused?
Post by Li-yao Xia
(#.) :: Coercible b c => p b c -> (a -> b) -> (a -> c)
(.#) :: Coercible a b => (b -> c) -> p a b -> (a -> c)
Li-yao
Post by Daniel Cartwright
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}
The first of these is exported from Data.Functor.Util, and used in many
places inside of base for efficiency over '.' (compose), However no
module
Post by Li-yao Xia
Post by Daniel Cartwright
in base actually exports these. I have recently been using Data.Coerce
more
Post by Li-yao Xia
Post by Daniel Cartwright
frequently and think it would be useful to go ahead and export these
from
Post by Li-yao Xia
Post by Daniel Cartwright
somewhere in base.
For convenience, I will paste the note about (#.) from
"Note [Function coercion]
~~~~~~~~~~~~~~~~~~~~~~~
Several functions here use (#.) instead of (.) to avoid potential
efficiency
Post by Li-yao Xia
Post by Daniel Cartwright
If N is a newtype constructor, then N x will always have the same
representation as x (something similar applies for a newtype
deconstructor).
Post by Li-yao Xia
Post by Daniel Cartwright
However, if f is a function,
N . f = \x -> N (f x)
This looks almost the same as f, but the eta expansion lifts it--the
lhs
Post by Li-yao Xia
Post by Daniel Cartwright
could
be _|_, but the rhs never is. This can lead to very inefficient code.
Thus
Post by Li-yao Xia
Post by Daniel Cartwright
we
steal a technique from Shachaf and Edward Kmett and adapt it to the
current
Post by Li-yao Xia
Post by Daniel Cartwright
(rather clean) setting. Instead of using N . f, we use N #. f,
which is
Post by Li-yao Xia
Post by Daniel Cartwright
just
coerce f `asTypeOf` (N . f)
That is, we just *pretend* that f has the right type, and thanks to the
safety
of coerce, the type checker guarantees that nothing really goes wrong.
We
Post by Li-yao Xia
Post by Daniel Cartwright
still
have to be a bit careful, though: remember that #. completely ignores
the
Post by Li-yao Xia
Post by Daniel Cartwright
*value* of its left operand.
"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/
20180420/c5646504/attachment-0001.html>
Post by Li-yao Xia
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Edward Kmett
2018-04-22 16:59:28 UTC
Permalink
It looks like inference works out.

I've hacked up profunctors HEAD in a similar manner and pre-emptively
narrowed my Data.Coerce imports there in case we do decide to export these
from a more common location.

I did have to drop support for ghc < 7.8 to get the more general signatures
to work out nicely though, as the old default definitions aren't valid
there afterwards.

-Edward
Post by David Feuer
I don't personally think the slightly confusing appearance is worth
worrying about. If inference works out, I like Li-yao's approach a lot.
Post by Edward Kmett
Note: this way of indicating which argument is unused means that this
confusingly looks almost precisely backwards relative to the more general
code
<https://hackage.haskell.org/package/profunctors-5.2.2/docs/Data-Profunctor-Unsafe.html>
that created those names and usage pattern in the first place. (I don't
have a particularly strong objection to allowing the same sort of tweak to
that code, assuming type inference works out in practice for the major
consumers of the combinators there. I don't really foresee a problem, but
I've been surprised by interactions before.)
I do have some concern that exporting these incompatible versions from
Data.Coerce would break a subset of the code that is using the more general
combinators in profunctors. e.g. in the lens library these were coined
for the code for prisms imports both.
This is why they were placed in a more obscure internal location to begin
with as just the special case was needed by base and it was easier to write
a copy locally than merge Profunctor to base.
-Edward
On Sat, Apr 21, 2018 at 10:16 AM, Andrew Martin <
Post by Andrew Martin
Generalizing the type like that helps a lot. I had skimmed the
documentation before, but I didn’t immediately comprehend what it meant.
When I read the type signatures you gave them, it immediately made sense.
Also, +1 on exporting these.
Sent from my iPhone
Post by Li-yao Xia
That seems useful indeed! Using only 'coerce' requires too many type
annotations.
Post by Li-yao Xia
Would it make sense to generalize the type so it's clear that one
argument is unused?
Post by Li-yao Xia
(#.) :: Coercible b c => p b c -> (a -> b) -> (a -> c)
(.#) :: Coercible a b => (b -> c) -> p a b -> (a -> c)
Li-yao
Post by Daniel Cartwright
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
{-# INLINE (#.) #-}
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)
(.#) f _ = coerce f
{-# INLINE (.#) #-}
The first of these is exported from Data.Functor.Util, and used in
many
Post by Li-yao Xia
Post by Daniel Cartwright
places inside of base for efficiency over '.' (compose), However no
module
Post by Li-yao Xia
Post by Daniel Cartwright
in base actually exports these. I have recently been using
Data.Coerce more
Post by Li-yao Xia
Post by Daniel Cartwright
frequently and think it would be useful to go ahead and export these
from
Post by Li-yao Xia
Post by Daniel Cartwright
somewhere in base.
For convenience, I will paste the note about (#.) from
"Note [Function coercion]
~~~~~~~~~~~~~~~~~~~~~~~
Several functions here use (#.) instead of (.) to avoid potential
efficiency
Post by Li-yao Xia
Post by Daniel Cartwright
If N is a newtype constructor, then N x will always have the same
representation as x (something similar applies for a newtype
deconstructor).
Post by Li-yao Xia
Post by Daniel Cartwright
However, if f is a function,
N . f = \x -> N (f x)
This looks almost the same as f, but the eta expansion lifts it--the
lhs
Post by Li-yao Xia
Post by Daniel Cartwright
could
be _|_, but the rhs never is. This can lead to very inefficient
code. Thus
Post by Li-yao Xia
Post by Daniel Cartwright
we
steal a technique from Shachaf and Edward Kmett and adapt it to the
current
Post by Li-yao Xia
Post by Daniel Cartwright
(rather clean) setting. Instead of using N . f, we use N #. f,
which is
Post by Li-yao Xia
Post by Daniel Cartwright
just
coerce f `asTypeOf` (N . f)
That is, we just *pretend* that f has the right type, and thanks to
the
Post by Li-yao Xia
Post by Daniel Cartwright
safety
of coerce, the type checker guarantees that nothing really goes
wrong. We
Post by Li-yao Xia
Post by Daniel Cartwright
still
have to be a bit careful, though: remember that #. completely ignores
the
Post by Li-yao Xia
Post by Daniel Cartwright
*value* of its left operand.
"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/201
80420/c5646504/attachment-0001.html>
Post by Li-yao Xia
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Joachim Breitner
2018-04-22 03:15:54 UTC
Permalink
Hi,
I have recently been using Data.Coerce more frequently and think it
would be useful to go ahead and export these from somewhere in base.
I expect the target audience of these functions to be pretty small
(they need to know what coerce does, they need to care and know about
the problem with composing newtype constructors with functions etc.) I
don’t think any of these will struggle to define their (#.) locally –
or simply call coerce directly on `f`, without specifying the unused
newtype constructor.

I am, however, worried about people who are not the target audience to
see these operators and shoot in their foot with them (e.g. passing a
function that “does something” as the first argument to #. and then
being very confused that things don’t work.).

The benefits don’t obviously outweigh the risks, so a mild hesitant -1
from me.

Cheers,
Joachim
--
Joachim “nomeata” Breitner
***@joachim-breitner.de
https://www.joachim-breitner.de/
Loading...