insou
insou

Reputation: 314

Function to automatically unwrap / rewrap values in haskell?

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

Answers (2)

K. A. Buhr
K. A. Buhr

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

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

Related Questions