Reputation: 314
I made a function as follows:
pen :: (a -> b) -> (b -> a) -> (b -> b) -> a -> a
pen conv rev app = rev . app . conv
which is used as follows:
pen read show (\x -> x + 1) "5"
"6"
I am quite new to Haskell, and I am wondering if a function like this exists in the Haskell standard library and what it is called, as I can't find it on Hoogle.
I'm also assuming there is some way to achieve this without (a -> b) -> (b -> a) -> ... and just having a single bijective function, but I'm also not sure how to do this.
Cheers!
Upvotes: 3
Views: 141
Reputation: 50829
I think the most standard name for a general version of this function is dimap
. It doesn't show up in a Hoogle search, unfortunately, since Hoogle lacks support for instances involving (->)
.
Anyway, dimap
is a type class method (for the Profunctor
class), so it's more general than what you want (the same way fmap
would be more general than what someone looking for map
actually wants). Specialized to the Profunctor
instance for functions, it's still more general than you want, as it allows an arbitrary transformation of its input and output arguments to any types, so its type signature specialized to functions is:
dimap :: (a -> b) -> (c -> d) -> (b -> c) -> (a -> d)
which can obviously be be further specialized to the function pen
that you want:
dimap :: (a -> b) -> (b -> a) -> (b -> b) -> (a -> a)
It's not in base
, but it's included in the lens
package or in the standalone profunctors
package:
> import Data.Profunctor -- from "profunctors"
> dimap read show (\x -> x + 1) "5"
"6"
Haskell functions of type a -> b
can't be "bijective" obviously, but if you use the lens
package, an Iso
represents a bijective function.
You can define an Iso
from a function and its inverse by writing:
> import Control.Lens
> showRead = iso show read
To apply a function using this Iso
as a wrapper/unwrapper, you can use the Lens function under
:
> under showRead (+1) "5"
"6"
I guess it's worth noting that showRead
maybe isn't a great Iso
(i.e., isn't fully law-abiding), since show
and read
aren't perfect inverses. (That is, some show
instances produce values that can't be read
back to reproduce the value.)
Upvotes: 8
Reputation: 48572
One of the benefits of Haskell is that it's easy to look up functions that you don't know the name of, just by their type. Put (a -> b) -> (b -> a) -> (b -> b) -> a -> a
in Hoogle and try it. In this case, you get 4 hits, but they're all in third-party libraries. Two of them do exactly the same thing as your function, and two are almost the same but are more strict.
Also, note that your function has a type signature less general than it could, and all of the results have a more general one: (a -> b) -> (c -> d) -> (b -> c) -> a -> d
. Using less general type signatures than you can is somewhat dangerous, as it allows mistakes that would otherwise be caught by the compiler. For example, pen conv rev app = rev . conv
is wrong since it skips app
. With your type signature, this would compile fine but you'd have a runtime bug. With the more general type signature, it would be a type error caught at compile time.
Upvotes: 1