Is there a shorthand for operations like `fromNewtype . f . toNewtype`?
Clash Royale CLAN TAG#URR8PPP
up vote
8
down vote
favorite
A pattern that presents itself the more often the more type safety is being introduced via newtype
is to project a value (or several values) to a newtype
wrapper, do some operations, and then retract the projection. An ubiquitous example is that of Sum
and Product
monoids:
û x + y = getSum $ Sum x `mappend` Sum y
û 1 + 2
3
I imagine a collection of functions like withSum
, withSum2
, and so on, may be automagically rolled out for each newtype
. Or maybe a parametrized Identity
may be created, for use with ApplicativeDo
. Or maybe there are some other approaches that I could not think of.
I wonder if there is some prior art or theory around this.
P.S. Â I am unhappy with coerce
, for two reasons:
safety  I thought it is not very safe. After being pointed that it is actually safe, I
tried a few things and I could not do anything harmful, because it requires a type annotation
when there is a possibility of ambiguity. For example:û newtype F = F Int deriving Show
û newtype G = G Int deriving Show
û coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G
G 2
û coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G
G 1
û coerce . (mappend 1) . coerce $ F 1 :: G
...
⢠Couldn't match representation of type âÂÂa0â with that of âÂÂIntâÂÂ
arising from a use of âÂÂcoerceâÂÂ
...But I would still not welcome
coerce
, because it is far too easy to strip a safety label and
shoot someone, once the reaching for it becomes habitual. Imagine that, in a cryptographic
application, there are two values:x :: Prime Int
andx' :: Sum Int
. I would much rather
typegetPrime
andgetSum
every time I use them, thancoerce
everything and have one day
made a catastrophic mistake.usefulness Â
coerce
does not bring much to the table regarding a shorthand for
certain operations. The leading example of my post, that I repeat here:û getSum $ Sum 1 `mappend` Sum 2
3â Turns into something along the lines of this spiked monster:
û coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3â Which is hardly of any benfit.
haskell newtype coerce
add a comment |Â
up vote
8
down vote
favorite
A pattern that presents itself the more often the more type safety is being introduced via newtype
is to project a value (or several values) to a newtype
wrapper, do some operations, and then retract the projection. An ubiquitous example is that of Sum
and Product
monoids:
û x + y = getSum $ Sum x `mappend` Sum y
û 1 + 2
3
I imagine a collection of functions like withSum
, withSum2
, and so on, may be automagically rolled out for each newtype
. Or maybe a parametrized Identity
may be created, for use with ApplicativeDo
. Or maybe there are some other approaches that I could not think of.
I wonder if there is some prior art or theory around this.
P.S. Â I am unhappy with coerce
, for two reasons:
safety  I thought it is not very safe. After being pointed that it is actually safe, I
tried a few things and I could not do anything harmful, because it requires a type annotation
when there is a possibility of ambiguity. For example:û newtype F = F Int deriving Show
û newtype G = G Int deriving Show
û coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G
G 2
û coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G
G 1
û coerce . (mappend 1) . coerce $ F 1 :: G
...
⢠Couldn't match representation of type âÂÂa0â with that of âÂÂIntâÂÂ
arising from a use of âÂÂcoerceâÂÂ
...But I would still not welcome
coerce
, because it is far too easy to strip a safety label and
shoot someone, once the reaching for it becomes habitual. Imagine that, in a cryptographic
application, there are two values:x :: Prime Int
andx' :: Sum Int
. I would much rather
typegetPrime
andgetSum
every time I use them, thancoerce
everything and have one day
made a catastrophic mistake.usefulness Â
coerce
does not bring much to the table regarding a shorthand for
certain operations. The leading example of my post, that I repeat here:û getSum $ Sum 1 `mappend` Sum 2
3â Turns into something along the lines of this spiked monster:
û coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3â Which is hardly of any benfit.
haskell newtype coerce
2
See my answer regarding your criticism ofcoerce
as shorthand. You don't coerce the inputs and outputs (that is indeed little if any better than applying newtype constructors/getters); insteadcoerce
the function you want to call so it has a different type. As for safety; it's no more or less safe than manually unwrapping. If you want to make that impossible, don't export the newtype constructors, andcoerce
won't work either. If you can mess up with coerce, you can also mess up with manual newtype unwrapping.
â Ben
Aug 19 at 9:36
I agree with your uneasiness about coerce -- though coerce won't do anything that you couldn't otherwise do safely. In particular, newtypes are not coercible unless their constructors are in scope. So if you are properly using smart constructors, for example, coerce can't hurt you.
â luqui
Aug 21 at 1:51
add a comment |Â
up vote
8
down vote
favorite
up vote
8
down vote
favorite
A pattern that presents itself the more often the more type safety is being introduced via newtype
is to project a value (or several values) to a newtype
wrapper, do some operations, and then retract the projection. An ubiquitous example is that of Sum
and Product
monoids:
û x + y = getSum $ Sum x `mappend` Sum y
û 1 + 2
3
I imagine a collection of functions like withSum
, withSum2
, and so on, may be automagically rolled out for each newtype
. Or maybe a parametrized Identity
may be created, for use with ApplicativeDo
. Or maybe there are some other approaches that I could not think of.
I wonder if there is some prior art or theory around this.
P.S. Â I am unhappy with coerce
, for two reasons:
safety  I thought it is not very safe. After being pointed that it is actually safe, I
tried a few things and I could not do anything harmful, because it requires a type annotation
when there is a possibility of ambiguity. For example:û newtype F = F Int deriving Show
û newtype G = G Int deriving Show
û coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G
G 2
û coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G
G 1
û coerce . (mappend 1) . coerce $ F 1 :: G
...
⢠Couldn't match representation of type âÂÂa0â with that of âÂÂIntâÂÂ
arising from a use of âÂÂcoerceâÂÂ
...But I would still not welcome
coerce
, because it is far too easy to strip a safety label and
shoot someone, once the reaching for it becomes habitual. Imagine that, in a cryptographic
application, there are two values:x :: Prime Int
andx' :: Sum Int
. I would much rather
typegetPrime
andgetSum
every time I use them, thancoerce
everything and have one day
made a catastrophic mistake.usefulness Â
coerce
does not bring much to the table regarding a shorthand for
certain operations. The leading example of my post, that I repeat here:û getSum $ Sum 1 `mappend` Sum 2
3â Turns into something along the lines of this spiked monster:
û coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3â Which is hardly of any benfit.
haskell newtype coerce
A pattern that presents itself the more often the more type safety is being introduced via newtype
is to project a value (or several values) to a newtype
wrapper, do some operations, and then retract the projection. An ubiquitous example is that of Sum
and Product
monoids:
û x + y = getSum $ Sum x `mappend` Sum y
û 1 + 2
3
I imagine a collection of functions like withSum
, withSum2
, and so on, may be automagically rolled out for each newtype
. Or maybe a parametrized Identity
may be created, for use with ApplicativeDo
. Or maybe there are some other approaches that I could not think of.
I wonder if there is some prior art or theory around this.
P.S. Â I am unhappy with coerce
, for two reasons:
safety  I thought it is not very safe. After being pointed that it is actually safe, I
tried a few things and I could not do anything harmful, because it requires a type annotation
when there is a possibility of ambiguity. For example:û newtype F = F Int deriving Show
û newtype G = G Int deriving Show
û coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G
G 2
û coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G
G 1
û coerce . (mappend 1) . coerce $ F 1 :: G
...
⢠Couldn't match representation of type âÂÂa0â with that of âÂÂIntâÂÂ
arising from a use of âÂÂcoerceâÂÂ
...But I would still not welcome
coerce
, because it is far too easy to strip a safety label and
shoot someone, once the reaching for it becomes habitual. Imagine that, in a cryptographic
application, there are two values:x :: Prime Int
andx' :: Sum Int
. I would much rather
typegetPrime
andgetSum
every time I use them, thancoerce
everything and have one day
made a catastrophic mistake.usefulness Â
coerce
does not bring much to the table regarding a shorthand for
certain operations. The leading example of my post, that I repeat here:û getSum $ Sum 1 `mappend` Sum 2
3â Turns into something along the lines of this spiked monster:
û coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3â Which is hardly of any benfit.
haskell newtype coerce
edited Aug 19 at 8:01
asked Aug 19 at 6:47
Ignat Insarov
1,7321023
1,7321023
2
See my answer regarding your criticism ofcoerce
as shorthand. You don't coerce the inputs and outputs (that is indeed little if any better than applying newtype constructors/getters); insteadcoerce
the function you want to call so it has a different type. As for safety; it's no more or less safe than manually unwrapping. If you want to make that impossible, don't export the newtype constructors, andcoerce
won't work either. If you can mess up with coerce, you can also mess up with manual newtype unwrapping.
â Ben
Aug 19 at 9:36
I agree with your uneasiness about coerce -- though coerce won't do anything that you couldn't otherwise do safely. In particular, newtypes are not coercible unless their constructors are in scope. So if you are properly using smart constructors, for example, coerce can't hurt you.
â luqui
Aug 21 at 1:51
add a comment |Â
2
See my answer regarding your criticism ofcoerce
as shorthand. You don't coerce the inputs and outputs (that is indeed little if any better than applying newtype constructors/getters); insteadcoerce
the function you want to call so it has a different type. As for safety; it's no more or less safe than manually unwrapping. If you want to make that impossible, don't export the newtype constructors, andcoerce
won't work either. If you can mess up with coerce, you can also mess up with manual newtype unwrapping.
â Ben
Aug 19 at 9:36
I agree with your uneasiness about coerce -- though coerce won't do anything that you couldn't otherwise do safely. In particular, newtypes are not coercible unless their constructors are in scope. So if you are properly using smart constructors, for example, coerce can't hurt you.
â luqui
Aug 21 at 1:51
2
2
See my answer regarding your criticism of
coerce
as shorthand. You don't coerce the inputs and outputs (that is indeed little if any better than applying newtype constructors/getters); instead coerce
the function you want to call so it has a different type. As for safety; it's no more or less safe than manually unwrapping. If you want to make that impossible, don't export the newtype constructors, and coerce
won't work either. If you can mess up with coerce, you can also mess up with manual newtype unwrapping.â Ben
Aug 19 at 9:36
See my answer regarding your criticism of
coerce
as shorthand. You don't coerce the inputs and outputs (that is indeed little if any better than applying newtype constructors/getters); instead coerce
the function you want to call so it has a different type. As for safety; it's no more or less safe than manually unwrapping. If you want to make that impossible, don't export the newtype constructors, and coerce
won't work either. If you can mess up with coerce, you can also mess up with manual newtype unwrapping.â Ben
Aug 19 at 9:36
I agree with your uneasiness about coerce -- though coerce won't do anything that you couldn't otherwise do safely. In particular, newtypes are not coercible unless their constructors are in scope. So if you are properly using smart constructors, for example, coerce can't hurt you.
â luqui
Aug 21 at 1:51
I agree with your uneasiness about coerce -- though coerce won't do anything that you couldn't otherwise do safely. In particular, newtypes are not coercible unless their constructors are in scope. So if you are properly using smart constructors, for example, coerce can't hurt you.
â luqui
Aug 21 at 1:51
add a comment |Â
3 Answers
3
active
oldest
votes
up vote
9
down vote
Your "spiked monster" example is better handled by putting the summands into a list and using the ala
function available here, which has type:
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
where
a
is the unwrapped base type.b
is the newtype that wrapsa
.a -> b
is the newtype constructor.((a -> b) -> c -> b')
is a function that, knowing how to wrap values of the base typea
, knows how to process a value of typec
(almost always a container ofa
s) and return a wrapped resultb'
. In practice this function is almost alwaysfoldMap
.a'
the unwrapped final result. The unwrapping is handled byala
itself.
in your case, it would be something like:
ala Sum foldMap [1,2::Integer]
"ala" functions can be implemented through means other than coerce
, for example using generics to handle the unwrapping, or even lenses.
1
I'm happy to seecoercible-utils
mentioned here! :) Would you mind changing the link toala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear thatcoercible-utils
is available from Hackage.
â sjakobi
Aug 20 at 17:26
add a comment |Â
up vote
6
down vote
coerce
from Data.Coerce can be pretty great for this sort of thing. You can use it to convert between different types with the same representation (like between a type and a newtype wrapper, or vice versa). For example:
û coerce (3 :: Int) :: Sum Int
Sum getSum = 3
it :: Sum Int
û coerce (3 :: Sum Int) :: Int
3
it :: Int
It was developed to solve the problem that it is cost-free to e.g. convert an Int
into a Sum Int
by applying Sum
, but it isn't necessarily cost-free to e.g convert a [Int]
to a [Sum Int]
by applying map Sum
. The compiler might be able to optimise away the traversal of the list spine from map
or it might not, but we know that the same structure in memory can serve as either a [Int]
or a [Sum Int]
, because the list structure doesn't depend on any properties of the elements and the element types have identical representation between those two cases. coerce
(plus the role system) allows us to make use of this fact to convert between the two in a way that is guaranteed not to do any runtime work, but still have the compiler check that it's safe to do so:
û coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum getSum = 1,Sum getSum = 2,Sum getSum = 3]
it :: [Sum Int]
Something that wasn't at all obvious to me at first is that coerce
is not limited to coercing "structures"! Because all it's doing is allowing us to substitute types (including parts of compound types) when the representations are identical, it works just as well to coerce code:
û addInt = (+) @ Int
addInt :: Int -> Int -> Int
û let addSum :: Sum Int -> Sum Int -> Sum Int
| addSum = coerce addInt
|
addSum :: Sum Int -> Sum Int -> Sum Int
û addSum (Sum 3) (Sum 19)
Sum getSum = 22
it :: Sum Int
(In the above example I had to define a monotype version of +
because coerce
is so generic the type system otherwise doesn't know which version of +
I'm asking to coerce to Sum Int -> Sum Int -> Sum Int
; I could instead have given an inline type signature on the argument to coerce
, but that looks less tidy. Often in real usage the context is sufficient to determine the "source" and "target" types of the coerce
)
I once wrote a library that provided a few different ways of paramterising types via newtypes, and provided similar APIs with each scheme. The modules implementing the APIs were full of type signatures and foo' = coerce foo
style definitions; it felt really nice that I was barely doing any work other than stating the types that I wanted.
Your example (using mappend
on Sum
to implement addition, without having to explicitly convert back and forth) could look like:
û let (+) :: Int -> Int -> Int
| (+) = coerce (mappend @ (Sum Int))
|
(+) :: Int -> Int -> Int
û 3 + 8
11
it :: Int
add a comment |Â
up vote
6
down vote
Yes, there's! It's a coerce
function from base
package. It allows to convert from newtype
and to newtype
automatically. GHC actually has a big chunk of theory behind coercions.
In relude
I called this function under
.
ghci> newtype Foo = Foo Bool deriving Show
ghci> under not (Foo True)
Foo False
ghci> newtype Bar = Bar String deriving Show
ghci> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"
You can see the whole module here:
- http://hackage.haskell.org/package/relude-0.2.0/docs/Relude-Extra-Newtype.html
It's also possible to implement custom functions for binary operators as well:
ghci> import Data.Coerce
ghci> :set -XScopedTypeVariables
ghci> :set -XTypeApplications
ghci> : via = coerce
ghci
ghci> :
ghci
ghci> via @(Sum Int) @Int (<>) 3 4
7
ghci> viaF @Sum @Int (<>) 3 5
8
I should have mentioned thatcoerce
is unsuitable for this purpose, since it defies type safety.
â Ignat Insarov
Aug 19 at 7:13
@IgnatInsarov Why doescoerce
defy type safety? Are you sure you're not thinking ofunsafeCoerce
?
â David Young
Aug 19 at 7:13
4
@IgnatInsarov This is a common misconception.coerce
is completely safe. You can'tcoerce
integer to string for example. It's possible withunsafeCoerce
only.
â Shersh
Aug 19 at 7:14
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@IgnatInsarov It's possible to implement function likevia @Sum @Int (<>) 3 4
which results into3 + 4
usingcoerce
.
â Shersh
Aug 19 at 8:08
 |Â
show 2 more comments
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
9
down vote
Your "spiked monster" example is better handled by putting the summands into a list and using the ala
function available here, which has type:
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
where
a
is the unwrapped base type.b
is the newtype that wrapsa
.a -> b
is the newtype constructor.((a -> b) -> c -> b')
is a function that, knowing how to wrap values of the base typea
, knows how to process a value of typec
(almost always a container ofa
s) and return a wrapped resultb'
. In practice this function is almost alwaysfoldMap
.a'
the unwrapped final result. The unwrapping is handled byala
itself.
in your case, it would be something like:
ala Sum foldMap [1,2::Integer]
"ala" functions can be implemented through means other than coerce
, for example using generics to handle the unwrapping, or even lenses.
1
I'm happy to seecoercible-utils
mentioned here! :) Would you mind changing the link toala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear thatcoercible-utils
is available from Hackage.
â sjakobi
Aug 20 at 17:26
add a comment |Â
up vote
9
down vote
Your "spiked monster" example is better handled by putting the summands into a list and using the ala
function available here, which has type:
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
where
a
is the unwrapped base type.b
is the newtype that wrapsa
.a -> b
is the newtype constructor.((a -> b) -> c -> b')
is a function that, knowing how to wrap values of the base typea
, knows how to process a value of typec
(almost always a container ofa
s) and return a wrapped resultb'
. In practice this function is almost alwaysfoldMap
.a'
the unwrapped final result. The unwrapping is handled byala
itself.
in your case, it would be something like:
ala Sum foldMap [1,2::Integer]
"ala" functions can be implemented through means other than coerce
, for example using generics to handle the unwrapping, or even lenses.
1
I'm happy to seecoercible-utils
mentioned here! :) Would you mind changing the link toala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear thatcoercible-utils
is available from Hackage.
â sjakobi
Aug 20 at 17:26
add a comment |Â
up vote
9
down vote
up vote
9
down vote
Your "spiked monster" example is better handled by putting the summands into a list and using the ala
function available here, which has type:
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
where
a
is the unwrapped base type.b
is the newtype that wrapsa
.a -> b
is the newtype constructor.((a -> b) -> c -> b')
is a function that, knowing how to wrap values of the base typea
, knows how to process a value of typec
(almost always a container ofa
s) and return a wrapped resultb'
. In practice this function is almost alwaysfoldMap
.a'
the unwrapped final result. The unwrapping is handled byala
itself.
in your case, it would be something like:
ala Sum foldMap [1,2::Integer]
"ala" functions can be implemented through means other than coerce
, for example using generics to handle the unwrapping, or even lenses.
Your "spiked monster" example is better handled by putting the summands into a list and using the ala
function available here, which has type:
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
where
a
is the unwrapped base type.b
is the newtype that wrapsa
.a -> b
is the newtype constructor.((a -> b) -> c -> b')
is a function that, knowing how to wrap values of the base typea
, knows how to process a value of typec
(almost always a container ofa
s) and return a wrapped resultb'
. In practice this function is almost alwaysfoldMap
.a'
the unwrapped final result. The unwrapping is handled byala
itself.
in your case, it would be something like:
ala Sum foldMap [1,2::Integer]
"ala" functions can be implemented through means other than coerce
, for example using generics to handle the unwrapping, or even lenses.
edited Aug 20 at 18:16
answered Aug 19 at 9:12
danidiaz
16k32655
16k32655
1
I'm happy to seecoercible-utils
mentioned here! :) Would you mind changing the link toala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear thatcoercible-utils
is available from Hackage.
â sjakobi
Aug 20 at 17:26
add a comment |Â
1
I'm happy to seecoercible-utils
mentioned here! :) Would you mind changing the link toala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear thatcoercible-utils
is available from Hackage.
â sjakobi
Aug 20 at 17:26
1
1
I'm happy to see
coercible-utils
mentioned here! :) Would you mind changing the link to ala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear that coercible-utils
is available from Hackage.â sjakobi
Aug 20 at 17:26
I'm happy to see
coercible-utils
mentioned here! :) Would you mind changing the link to ala
to hackage.haskell.org/package/coercible-utils-0.0.0/docs/⦠though? This way it's more clear that coercible-utils
is available from Hackage.â sjakobi
Aug 20 at 17:26
add a comment |Â
up vote
6
down vote
coerce
from Data.Coerce can be pretty great for this sort of thing. You can use it to convert between different types with the same representation (like between a type and a newtype wrapper, or vice versa). For example:
û coerce (3 :: Int) :: Sum Int
Sum getSum = 3
it :: Sum Int
û coerce (3 :: Sum Int) :: Int
3
it :: Int
It was developed to solve the problem that it is cost-free to e.g. convert an Int
into a Sum Int
by applying Sum
, but it isn't necessarily cost-free to e.g convert a [Int]
to a [Sum Int]
by applying map Sum
. The compiler might be able to optimise away the traversal of the list spine from map
or it might not, but we know that the same structure in memory can serve as either a [Int]
or a [Sum Int]
, because the list structure doesn't depend on any properties of the elements and the element types have identical representation between those two cases. coerce
(plus the role system) allows us to make use of this fact to convert between the two in a way that is guaranteed not to do any runtime work, but still have the compiler check that it's safe to do so:
û coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum getSum = 1,Sum getSum = 2,Sum getSum = 3]
it :: [Sum Int]
Something that wasn't at all obvious to me at first is that coerce
is not limited to coercing "structures"! Because all it's doing is allowing us to substitute types (including parts of compound types) when the representations are identical, it works just as well to coerce code:
û addInt = (+) @ Int
addInt :: Int -> Int -> Int
û let addSum :: Sum Int -> Sum Int -> Sum Int
| addSum = coerce addInt
|
addSum :: Sum Int -> Sum Int -> Sum Int
û addSum (Sum 3) (Sum 19)
Sum getSum = 22
it :: Sum Int
(In the above example I had to define a monotype version of +
because coerce
is so generic the type system otherwise doesn't know which version of +
I'm asking to coerce to Sum Int -> Sum Int -> Sum Int
; I could instead have given an inline type signature on the argument to coerce
, but that looks less tidy. Often in real usage the context is sufficient to determine the "source" and "target" types of the coerce
)
I once wrote a library that provided a few different ways of paramterising types via newtypes, and provided similar APIs with each scheme. The modules implementing the APIs were full of type signatures and foo' = coerce foo
style definitions; it felt really nice that I was barely doing any work other than stating the types that I wanted.
Your example (using mappend
on Sum
to implement addition, without having to explicitly convert back and forth) could look like:
û let (+) :: Int -> Int -> Int
| (+) = coerce (mappend @ (Sum Int))
|
(+) :: Int -> Int -> Int
û 3 + 8
11
it :: Int
add a comment |Â
up vote
6
down vote
coerce
from Data.Coerce can be pretty great for this sort of thing. You can use it to convert between different types with the same representation (like between a type and a newtype wrapper, or vice versa). For example:
û coerce (3 :: Int) :: Sum Int
Sum getSum = 3
it :: Sum Int
û coerce (3 :: Sum Int) :: Int
3
it :: Int
It was developed to solve the problem that it is cost-free to e.g. convert an Int
into a Sum Int
by applying Sum
, but it isn't necessarily cost-free to e.g convert a [Int]
to a [Sum Int]
by applying map Sum
. The compiler might be able to optimise away the traversal of the list spine from map
or it might not, but we know that the same structure in memory can serve as either a [Int]
or a [Sum Int]
, because the list structure doesn't depend on any properties of the elements and the element types have identical representation between those two cases. coerce
(plus the role system) allows us to make use of this fact to convert between the two in a way that is guaranteed not to do any runtime work, but still have the compiler check that it's safe to do so:
û coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum getSum = 1,Sum getSum = 2,Sum getSum = 3]
it :: [Sum Int]
Something that wasn't at all obvious to me at first is that coerce
is not limited to coercing "structures"! Because all it's doing is allowing us to substitute types (including parts of compound types) when the representations are identical, it works just as well to coerce code:
û addInt = (+) @ Int
addInt :: Int -> Int -> Int
û let addSum :: Sum Int -> Sum Int -> Sum Int
| addSum = coerce addInt
|
addSum :: Sum Int -> Sum Int -> Sum Int
û addSum (Sum 3) (Sum 19)
Sum getSum = 22
it :: Sum Int
(In the above example I had to define a monotype version of +
because coerce
is so generic the type system otherwise doesn't know which version of +
I'm asking to coerce to Sum Int -> Sum Int -> Sum Int
; I could instead have given an inline type signature on the argument to coerce
, but that looks less tidy. Often in real usage the context is sufficient to determine the "source" and "target" types of the coerce
)
I once wrote a library that provided a few different ways of paramterising types via newtypes, and provided similar APIs with each scheme. The modules implementing the APIs were full of type signatures and foo' = coerce foo
style definitions; it felt really nice that I was barely doing any work other than stating the types that I wanted.
Your example (using mappend
on Sum
to implement addition, without having to explicitly convert back and forth) could look like:
û let (+) :: Int -> Int -> Int
| (+) = coerce (mappend @ (Sum Int))
|
(+) :: Int -> Int -> Int
û 3 + 8
11
it :: Int
add a comment |Â
up vote
6
down vote
up vote
6
down vote
coerce
from Data.Coerce can be pretty great for this sort of thing. You can use it to convert between different types with the same representation (like between a type and a newtype wrapper, or vice versa). For example:
û coerce (3 :: Int) :: Sum Int
Sum getSum = 3
it :: Sum Int
û coerce (3 :: Sum Int) :: Int
3
it :: Int
It was developed to solve the problem that it is cost-free to e.g. convert an Int
into a Sum Int
by applying Sum
, but it isn't necessarily cost-free to e.g convert a [Int]
to a [Sum Int]
by applying map Sum
. The compiler might be able to optimise away the traversal of the list spine from map
or it might not, but we know that the same structure in memory can serve as either a [Int]
or a [Sum Int]
, because the list structure doesn't depend on any properties of the elements and the element types have identical representation between those two cases. coerce
(plus the role system) allows us to make use of this fact to convert between the two in a way that is guaranteed not to do any runtime work, but still have the compiler check that it's safe to do so:
û coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum getSum = 1,Sum getSum = 2,Sum getSum = 3]
it :: [Sum Int]
Something that wasn't at all obvious to me at first is that coerce
is not limited to coercing "structures"! Because all it's doing is allowing us to substitute types (including parts of compound types) when the representations are identical, it works just as well to coerce code:
û addInt = (+) @ Int
addInt :: Int -> Int -> Int
û let addSum :: Sum Int -> Sum Int -> Sum Int
| addSum = coerce addInt
|
addSum :: Sum Int -> Sum Int -> Sum Int
û addSum (Sum 3) (Sum 19)
Sum getSum = 22
it :: Sum Int
(In the above example I had to define a monotype version of +
because coerce
is so generic the type system otherwise doesn't know which version of +
I'm asking to coerce to Sum Int -> Sum Int -> Sum Int
; I could instead have given an inline type signature on the argument to coerce
, but that looks less tidy. Often in real usage the context is sufficient to determine the "source" and "target" types of the coerce
)
I once wrote a library that provided a few different ways of paramterising types via newtypes, and provided similar APIs with each scheme. The modules implementing the APIs were full of type signatures and foo' = coerce foo
style definitions; it felt really nice that I was barely doing any work other than stating the types that I wanted.
Your example (using mappend
on Sum
to implement addition, without having to explicitly convert back and forth) could look like:
û let (+) :: Int -> Int -> Int
| (+) = coerce (mappend @ (Sum Int))
|
(+) :: Int -> Int -> Int
û 3 + 8
11
it :: Int
coerce
from Data.Coerce can be pretty great for this sort of thing. You can use it to convert between different types with the same representation (like between a type and a newtype wrapper, or vice versa). For example:
û coerce (3 :: Int) :: Sum Int
Sum getSum = 3
it :: Sum Int
û coerce (3 :: Sum Int) :: Int
3
it :: Int
It was developed to solve the problem that it is cost-free to e.g. convert an Int
into a Sum Int
by applying Sum
, but it isn't necessarily cost-free to e.g convert a [Int]
to a [Sum Int]
by applying map Sum
. The compiler might be able to optimise away the traversal of the list spine from map
or it might not, but we know that the same structure in memory can serve as either a [Int]
or a [Sum Int]
, because the list structure doesn't depend on any properties of the elements and the element types have identical representation between those two cases. coerce
(plus the role system) allows us to make use of this fact to convert between the two in a way that is guaranteed not to do any runtime work, but still have the compiler check that it's safe to do so:
û coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum getSum = 1,Sum getSum = 2,Sum getSum = 3]
it :: [Sum Int]
Something that wasn't at all obvious to me at first is that coerce
is not limited to coercing "structures"! Because all it's doing is allowing us to substitute types (including parts of compound types) when the representations are identical, it works just as well to coerce code:
û addInt = (+) @ Int
addInt :: Int -> Int -> Int
û let addSum :: Sum Int -> Sum Int -> Sum Int
| addSum = coerce addInt
|
addSum :: Sum Int -> Sum Int -> Sum Int
û addSum (Sum 3) (Sum 19)
Sum getSum = 22
it :: Sum Int
(In the above example I had to define a monotype version of +
because coerce
is so generic the type system otherwise doesn't know which version of +
I'm asking to coerce to Sum Int -> Sum Int -> Sum Int
; I could instead have given an inline type signature on the argument to coerce
, but that looks less tidy. Often in real usage the context is sufficient to determine the "source" and "target" types of the coerce
)
I once wrote a library that provided a few different ways of paramterising types via newtypes, and provided similar APIs with each scheme. The modules implementing the APIs were full of type signatures and foo' = coerce foo
style definitions; it felt really nice that I was barely doing any work other than stating the types that I wanted.
Your example (using mappend
on Sum
to implement addition, without having to explicitly convert back and forth) could look like:
û let (+) :: Int -> Int -> Int
| (+) = coerce (mappend @ (Sum Int))
|
(+) :: Int -> Int -> Int
û 3 + 8
11
it :: Int
edited Aug 19 at 7:33
answered Aug 19 at 7:20
Ben
44.4k1291135
44.4k1291135
add a comment |Â
add a comment |Â
up vote
6
down vote
Yes, there's! It's a coerce
function from base
package. It allows to convert from newtype
and to newtype
automatically. GHC actually has a big chunk of theory behind coercions.
In relude
I called this function under
.
ghci> newtype Foo = Foo Bool deriving Show
ghci> under not (Foo True)
Foo False
ghci> newtype Bar = Bar String deriving Show
ghci> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"
You can see the whole module here:
- http://hackage.haskell.org/package/relude-0.2.0/docs/Relude-Extra-Newtype.html
It's also possible to implement custom functions for binary operators as well:
ghci> import Data.Coerce
ghci> :set -XScopedTypeVariables
ghci> :set -XTypeApplications
ghci> : via = coerce
ghci
ghci> :
ghci
ghci> via @(Sum Int) @Int (<>) 3 4
7
ghci> viaF @Sum @Int (<>) 3 5
8
I should have mentioned thatcoerce
is unsuitable for this purpose, since it defies type safety.
â Ignat Insarov
Aug 19 at 7:13
@IgnatInsarov Why doescoerce
defy type safety? Are you sure you're not thinking ofunsafeCoerce
?
â David Young
Aug 19 at 7:13
4
@IgnatInsarov This is a common misconception.coerce
is completely safe. You can'tcoerce
integer to string for example. It's possible withunsafeCoerce
only.
â Shersh
Aug 19 at 7:14
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@IgnatInsarov It's possible to implement function likevia @Sum @Int (<>) 3 4
which results into3 + 4
usingcoerce
.
â Shersh
Aug 19 at 8:08
 |Â
show 2 more comments
up vote
6
down vote
Yes, there's! It's a coerce
function from base
package. It allows to convert from newtype
and to newtype
automatically. GHC actually has a big chunk of theory behind coercions.
In relude
I called this function under
.
ghci> newtype Foo = Foo Bool deriving Show
ghci> under not (Foo True)
Foo False
ghci> newtype Bar = Bar String deriving Show
ghci> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"
You can see the whole module here:
- http://hackage.haskell.org/package/relude-0.2.0/docs/Relude-Extra-Newtype.html
It's also possible to implement custom functions for binary operators as well:
ghci> import Data.Coerce
ghci> :set -XScopedTypeVariables
ghci> :set -XTypeApplications
ghci> : via = coerce
ghci
ghci> :
ghci
ghci> via @(Sum Int) @Int (<>) 3 4
7
ghci> viaF @Sum @Int (<>) 3 5
8
I should have mentioned thatcoerce
is unsuitable for this purpose, since it defies type safety.
â Ignat Insarov
Aug 19 at 7:13
@IgnatInsarov Why doescoerce
defy type safety? Are you sure you're not thinking ofunsafeCoerce
?
â David Young
Aug 19 at 7:13
4
@IgnatInsarov This is a common misconception.coerce
is completely safe. You can'tcoerce
integer to string for example. It's possible withunsafeCoerce
only.
â Shersh
Aug 19 at 7:14
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@IgnatInsarov It's possible to implement function likevia @Sum @Int (<>) 3 4
which results into3 + 4
usingcoerce
.
â Shersh
Aug 19 at 8:08
 |Â
show 2 more comments
up vote
6
down vote
up vote
6
down vote
Yes, there's! It's a coerce
function from base
package. It allows to convert from newtype
and to newtype
automatically. GHC actually has a big chunk of theory behind coercions.
In relude
I called this function under
.
ghci> newtype Foo = Foo Bool deriving Show
ghci> under not (Foo True)
Foo False
ghci> newtype Bar = Bar String deriving Show
ghci> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"
You can see the whole module here:
- http://hackage.haskell.org/package/relude-0.2.0/docs/Relude-Extra-Newtype.html
It's also possible to implement custom functions for binary operators as well:
ghci> import Data.Coerce
ghci> :set -XScopedTypeVariables
ghci> :set -XTypeApplications
ghci> : via = coerce
ghci
ghci> :
ghci
ghci> via @(Sum Int) @Int (<>) 3 4
7
ghci> viaF @Sum @Int (<>) 3 5
8
Yes, there's! It's a coerce
function from base
package. It allows to convert from newtype
and to newtype
automatically. GHC actually has a big chunk of theory behind coercions.
In relude
I called this function under
.
ghci> newtype Foo = Foo Bool deriving Show
ghci> under not (Foo True)
Foo False
ghci> newtype Bar = Bar String deriving Show
ghci> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"
You can see the whole module here:
- http://hackage.haskell.org/package/relude-0.2.0/docs/Relude-Extra-Newtype.html
It's also possible to implement custom functions for binary operators as well:
ghci> import Data.Coerce
ghci> :set -XScopedTypeVariables
ghci> :set -XTypeApplications
ghci> : via = coerce
ghci
ghci> :
ghci
ghci> via @(Sum Int) @Int (<>) 3 4
7
ghci> viaF @Sum @Int (<>) 3 5
8
edited Aug 19 at 15:41
answered Aug 19 at 7:11
Shersh
5,28831439
5,28831439
I should have mentioned thatcoerce
is unsuitable for this purpose, since it defies type safety.
â Ignat Insarov
Aug 19 at 7:13
@IgnatInsarov Why doescoerce
defy type safety? Are you sure you're not thinking ofunsafeCoerce
?
â David Young
Aug 19 at 7:13
4
@IgnatInsarov This is a common misconception.coerce
is completely safe. You can'tcoerce
integer to string for example. It's possible withunsafeCoerce
only.
â Shersh
Aug 19 at 7:14
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@IgnatInsarov It's possible to implement function likevia @Sum @Int (<>) 3 4
which results into3 + 4
usingcoerce
.
â Shersh
Aug 19 at 8:08
 |Â
show 2 more comments
I should have mentioned thatcoerce
is unsuitable for this purpose, since it defies type safety.
â Ignat Insarov
Aug 19 at 7:13
@IgnatInsarov Why doescoerce
defy type safety? Are you sure you're not thinking ofunsafeCoerce
?
â David Young
Aug 19 at 7:13
4
@IgnatInsarov This is a common misconception.coerce
is completely safe. You can'tcoerce
integer to string for example. It's possible withunsafeCoerce
only.
â Shersh
Aug 19 at 7:14
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@IgnatInsarov It's possible to implement function likevia @Sum @Int (<>) 3 4
which results into3 + 4
usingcoerce
.
â Shersh
Aug 19 at 8:08
I should have mentioned that
coerce
is unsuitable for this purpose, since it defies type safety.â Ignat Insarov
Aug 19 at 7:13
I should have mentioned that
coerce
is unsuitable for this purpose, since it defies type safety.â Ignat Insarov
Aug 19 at 7:13
@IgnatInsarov Why does
coerce
defy type safety? Are you sure you're not thinking of unsafeCoerce
?â David Young
Aug 19 at 7:13
@IgnatInsarov Why does
coerce
defy type safety? Are you sure you're not thinking of unsafeCoerce
?â David Young
Aug 19 at 7:13
4
4
@IgnatInsarov This is a common misconception.
coerce
is completely safe. You can't coerce
integer to string for example. It's possible with unsafeCoerce
only.â Shersh
Aug 19 at 7:14
@IgnatInsarov This is a common misconception.
coerce
is completely safe. You can't coerce
integer to string for example. It's possible with unsafeCoerce
only.â Shersh
Aug 19 at 7:14
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@DavidYoung Not like totally defies. It is just that I do not trust myself with it. See the postscriptum I added to my question.
â Ignat Insarov
Aug 19 at 8:03
@IgnatInsarov It's possible to implement function like
via @Sum @Int (<>) 3 4
which results into 3 + 4
using coerce
.â Shersh
Aug 19 at 8:08
@IgnatInsarov It's possible to implement function like
via @Sum @Int (<>) 3 4
which results into 3 + 4
using coerce
.â Shersh
Aug 19 at 8:08
 |Â
show 2 more comments
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51915219%2fis-there-a-shorthand-for-operations-like-fromnewtype-f-tonewtype%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
2
See my answer regarding your criticism of
coerce
as shorthand. You don't coerce the inputs and outputs (that is indeed little if any better than applying newtype constructors/getters); insteadcoerce
the function you want to call so it has a different type. As for safety; it's no more or less safe than manually unwrapping. If you want to make that impossible, don't export the newtype constructors, andcoerce
won't work either. If you can mess up with coerce, you can also mess up with manual newtype unwrapping.â Ben
Aug 19 at 9:36
I agree with your uneasiness about coerce -- though coerce won't do anything that you couldn't otherwise do safely. In particular, newtypes are not coercible unless their constructors are in scope. So if you are properly using smart constructors, for example, coerce can't hurt you.
â luqui
Aug 21 at 1:51