【问题标题】:Mapping the value inside a newtype映射新类型中的值
【发布时间】:2017-09-21 22:40:09
【问题描述】:

假设定义了以下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 <$$>)

也许这样的抽象已经存在,但我找不到。

【问题讨论】:

  • 对于新类型的特殊情况,你应该只使用Data.Coerce,它给你coerce :: (a -&gt; a) -&gt; A a -&gt; A acoerce :: (A a -&gt; A a) -&gt; B a -&gt; B acoerce (a -&gt; a) -&gt; B a -&gt; B a
  • 我认为safe coercions 是自动包装/展开的方式。
  • @user2407038 确实如此,但不一定必须在使用站点。对于新类型,instance Functor N where fmap = coerce 是相当合理的。
  • 我看不出你提出的类型类会有什么帮助。 B a -&gt; B a 类型与 h (k a) -&gt; h (k b) 不统一。
  • &lt;$$&gt; 让人想起 newtype / newtype-generics 包中的 over。但是over 需要外部 newtype 构造函数作为附加参数。 hackage.haskell.org/package/newtype-generics-0.5.1/docs/…

标签: haskell newtype


【解决方案1】:

如果你喜欢 danidiaz 的回答,你可能会喜欢这个更“现代”的over 版本:

mover :: (Coercible o n, Coercible o' n')
      => (o -> n)
      -> (o' -> n')
      -> (o -> o') -> n -> n'
mover _pack _pack' = coerce

这会跳过所有实例以支持更明确的传递。

【讨论】:

  • 不错。 _pack 在哪里定义?另外我希望mover 已经在某个库中定义了......
  • 这些只是通配符。你可以写mover _ _ = coerce
【解决方案2】:

一种解决方案是使用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

【讨论】:

  • 我更有可能联系Control.Lens.Iso.coercedControl.Lens.Setter.over。它们不需要 NewtypeGeneric 实例。
  • 其实还可以更简单。看看我要写的答案。
【解决方案3】:

实际上,使用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])

【讨论】:

  • 这在我的情况下不起作用,因为fmap :: (a -&gt; b) -&gt; f a -&gt; f b。我要重用的函数类型为A a -&gt; A b,因此我需要??? :: (A a -&gt; A b) -&gt; B a -&gt; B a 之类的东西。请注意,我不是对 a 类型的元素(在您的示例中)进行操作,而是对 Team a 类型的元素进行操作。对于我正在处理的问题A a = Map a Integer,所以这个例子不起作用。另一个答案中的函数over 似乎符合要求。
  • @Damian Nadales A a -&gt; A b 实际上是fmap g A 类型的部分应用,其中g 是类型(a -&gt; b) 所以:t fmap g 会给你(f a -&gt; f b) 其中@987654337 @ 代表函子A。然后你只需要应用另一个fmap,但这次是在B 类型上,比如fmap (fmap g) 或` fmap 。 fmap $ g` 这实际上是上面代码中 League 类型的 Functor 实例中发生的事情。如果您更喜欢 League 的 Functor 实例(您的 B 类型)直接在 (A a -&gt; A b) 而不是 (a -&gt; b) 上操作,那么将 League 的 Functor 实例设为 fmap f (League x) = League (f x)
猜你喜欢
  • 2020-07-21
  • 2020-05-16
  • 2017-08-08
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 2016-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多