Haskell:新类型的函子实例

2024-02-18

对于水平集,这是一个简单的函子:

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 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 的实例,因为两者都没有 kind* -> *。虽然,从语法上来说,它们feel与 Box 没有什么不同,因此看起来应该可以对基础值进行操作,而无需所有手动包装和展开。

另外,创建 n 个这样的函数并不令人满意,因为每个新类型都会重复:

fmapWidth  :: (Int -> Int) -> Width  -> Width
fmapHeight :: (Int -> Int) -> Height -> Height

如何将整数上的函数提升为宽度上的函数?


首先要注意的是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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell:新类型的函子实例 的相关文章

随机推荐