要设置水平,这是一个简单的仿函数:
data Box a = Box a deriving (Show)
instance Functor Box where
fmap f (Box x) = Box (f x)
这使我们可以“在盒子里面”进行操作:
> fmap succ (Box 1)
Box 2
如何使用newtype实现同样的语法便利?假设我有以下内容:
newtype Width = Width { unWidth :: Int } deriving (Show)
newtype Height = Height { unHeight :: Int } deriving (Show)
这有点笨重:
> Width $ succ $ unWidth (Width 100)
Width {unWidth = 101}
这很不错:
> fmap succ (Width 100) -- impossible?
Width {unWidth = 101}
当然,我不能将Width或Height作为Functor的一个实例,因为它们都没有* -> *
。虽然,从语法上来说,它们感觉与Box没有区别,所以看起来应该可以在没有所有手动包装和解包的情况下对基础值进行操作。
另外,由于每个新newtype的重复,创建这样的n个函数并不令人满意:
fmapWidth :: (Int -> Int) -> Width -> Width
fmapHeight :: (Int -> Int) -> Height -> Height
如何将Ints上的函数提升为宽度函数?
答案 0 :(得分:8)
首先请注意newtype
这里没有障碍 - 你可以像data
一样参数化,然后你就有了一个普通的仿函数。像
{-# LANGUAGE DeriveFunctor #-}
newtype WidthF a = Width { unWidth :: a } deriving (Show, Functor)
type Width = WidthF Int
但是,我不认为这是一个好主意。 Width
不应该是一个仿函数;将非数字类型存储在其中是没有意义的。
user2407038建议的一个选项是使其成为“单态仿函数”
import Data.MonoTraversable (MonoFunctor(..))
newtype Width = Width { unWidth :: Int } deriving (Show)
instance MonoFunctor Width where
omap f (Width w) = Width $ f w
这对我来说似乎也不合理 - 如果您以一种通用方式映射数字操作,那么您也可以为Width
提供Num
个实例等,并直接使用这些。但是那时你几乎没有比简单的
type Width = Int
可以在没有任何帮助的情况下轻松修改,另一方面是在没有任何类型系统保护措施的情况下很容易被错误处理。
相反,我认为你想要的可能就是这样:
import Control.Lens
data Box = Box {
width, height :: Int }
widthInPx, heightInPx :: Lens' Box Int
widthInPx f (Box w h) = (`Box`h) <$> f w
heightInPx f (Box w h) = (Box w) <$> f h
然后你可以做
> Box 3 4 & widthInPx %~ (*2)
Box 6 4
> Box 4 2 & heightInPx %~ succ
Box 4 3