我做了一个函数,如下所示:
pen :: (a -> b) -> (b -> a) -> (b -> b) -> a -> a
pen conv rev app = rev . app . conv
其用途如下:
pen read show (x -> x + 1) "5"
"6"
我是Haskell的新手,我想知道Haskell标准库中是否存在这样的函数,以及它的名称,因为我在Hoogle上找不到它。
我还假设有一些方法可以在没有(a->b(->(b->a(->。。。只有一个双射函数,但我也不知道该怎么做。
干杯!
我认为这个函数的通用版本最标准的名称是dimap
。不幸的是,它没有出现在Hoogle搜索中,因为Hoogle缺乏对涉及(->)
的实例的支持。
无论如何,dimap
是一个类型类方法(用于Profunctor
类(,因此它比您想要的更通用(同样,fmap
也比查找map
的人实际想要的更一般(。专门用于函数的Profunctor
实例,它仍然比您想要的更通用,因为它允许将其输入和输出参数任意转换为任何类型,因此它专门用于函数中的类型签名是:
dimap :: (a -> b) -> (c -> d) -> (b -> c) -> (a -> d)
它显然可以进一步专门化为您想要的函数pen
:
dimap :: (a -> b) -> (b -> a) -> (b -> b) -> (a -> a)
它不在base
中,但包含在lens
包或独立的profunctors
包中:
> import Data.Profunctor -- from "profunctors"
> dimap read show (x -> x + 1) "5"
"6"
类型为a -> b
的Haskell函数显然不能是"双射"的,但如果使用lens
包,则Iso
表示双射函数。
您可以从函数及其逆函数中定义Iso
,方法是:
> import Control.Lens
> showRead = iso show read
要使用此Iso
作为包装器/展开器应用函数,可以使用Lens函数under
:
> under showRead (+1) "5"
"6"
我想值得注意的是,showRead
可能不是一个好的Iso
(即不完全守法(,因为show
和read
不是完美的逆。(也就是说,一些show
实例产生的值不能是read
返回以再现该值。(
Haskell的好处之一是,可以很容易地查找不知道名称的函数,只需根据它们的类型即可。把(a -> b) -> (b -> a) -> (b -> b) -> a -> a
放在Hoogle中试试。在这种情况下,你会得到4个点击,但它们都在第三方库中。其中两个与您的函数完全相同,两个几乎相同,但更严格。
此外,请注意,您的函数的类型签名没有它可能的那么通用,并且所有结果都有一个更通用的类型签名:(a -> b) -> (c -> d) -> (b -> c) -> a -> d
。使用不太通用的类型签名有点危险,因为它会导致编译器发现错误。例如,pen conv rev app = rev . conv
是错误的,因为它跳过了app
。使用您的类型签名,这会编译得很好,但会有运行时错误。对于更通用的类型签名,它将是在编译时捕获的类型错误。