Haskell:新类型的Functor实例

6
为了明确起见,这里有一个简单的函数对象:
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的函数扩展为作用于Widths的函数?

5
好的,我会尽力进行翻译。以下是需要翻译的内容:You want a different class. - user2407038
1
这样做没有意义:函数对象是高阶类型。 - Willem Van Onsem
1
第三个选项是来自lens(或类似包)的over:给定一个称为unWidth的字段的镜头,over unWidth可以实现你想要的功能(即它允许你使用单态函子)。 - duplode
3
问题并不在于newtype。说“data Width”或“newtype Box a”,都不会有任何改变(除了惰性求值这个在这里不重要)。 - n. m.
1
可能适用于简单类型的“类似函数对象”的实例。 - chepner
显示剩余2条评论
1个回答

10

首先请注意,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

对我来说,这也不太合理 - 如果您以同一种通用方式映射数字操作,则可以为Num等提供Width实例并直接使用它们。但是此时您几乎没有比简单的更好的类型系统保证

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接