假设定义了以下newtype
:
newtype A a = A a
还有一个功能:
f :: A a -> A a
现在假设我定义了另一个newtype
,其中包含A a
:
newtype B a = B (A a)
然后我想定义一个函数fb
,该函数在B a
上运行,但只使用f
:
fb :: B a -> B a
fb (B x) = B (f x)
现在这非常不方便,因为我必须将值解包并将值包装在B a
类型的元素中。如果我只需要定义一个这样的fb
,那就不会那么糟糕了,但是如果有很多这样的话会变得很乏味。
如果有一个带有函数的类型类,我会很高兴:
(<$$>) :: k a -> k b -> h (k a) -> h (k b)
这样fb
可以改写为:
fb = (f <$$>)
也许这样的抽象已经存在,但我找不到它。
答案 0 :(得分:4)
一种解决方案是使用newtype-generics包,尤其是over
函数:
{-# LANGUAGE DeriveGeneric #-}
import Control.Newtype (Newtype, over)
import GHC.Generics
newtype A a = A a
newtype B a = B (A a) deriving (Generic)
instance Newtype (B a)
f :: A a -> A a
f = undefined
fb :: B a -> B a
fb = over B f
请注意,over
需要外部B
构造函数作为参数,而不仅仅是函数f
。
答案 1 :(得分:3)
如果你喜欢danidiaz的回答,你可能会更喜欢这个&#34;现代&#34;版本over
:
mover :: (Coercible o n, Coercible o' n')
=> (o -> n)
-> (o' -> n')
-> (o -> o') -> n -> n'
mover _pack _pack' = coerce
这会跳过所有实例,转而采用更明确的传递方式。
答案 2 :(得分:1)
实际上,使用newtype
的一个主要原因是为包含在现有类型上的新类型派生新的类类实例。
所以很简单。您只需要为Functor类编写一个实例,就是这样。
newtype Team a = Team a deriving (Show , Eq)
instance Functor Team where
fmap f (Team x) = Team (f x)
newtype League a = League (Team a) deriving (Show , Eq)
instance Functor League where
fmap f (League x) = League (fmap f x)
upgradeTeam :: (Int -> Int) -> Team [Int] -> Team [Int]
upgradeTeam f = fmap (map f)
upgradeLeague :: (Int -> Int) -> League [Int] -> League [Int]
upgradeLeague f = fmap (map f)
prependToLeague :: League [Int] -> Int -> League [Int]
prependToLeague x n = fmap (n:) x
*Main> upgradeTeam (+1) (Team [0,1,2,3])
Team [1,2,3,4]
*Main> upgradeLeague (*2) (League (Team [1,2,3,4]))
League (Team [2,4,6,8])
*Main> prependToLeague (League (Team [2,4,6,8])) 42
League (Team [42,2,4,6,8])