【问题标题】:Functor/Applicative-like typeclass for composing (a->a) functions instead of (a->b)?用于组合 (a->a) 函数而不是 (a->b) 的仿函数/应用程序类型类?
【发布时间】:2014-01-13 16:35:48
【问题描述】:

我有一个看起来像这样的类型:

newtype Canonical Int = Canonical Int

还有一个函数

canonicalize :: Int  -> Canonical  Int
canonicalize =  Canonical . (`mod` 10)  -- or whatever

(Canonical 类型可能并不重要,它只是用来区分“原始”值和“规范化”值。)

我想创建一些机制,以便规范化函数应用程序的结果。

例如:(编辑:修正虚假定义)

cmap :: (b->Int) -> (Canonical b) -> (Canonical Int)
cmap f (Canonical x) = canonicalize $ f x

cmap2 :: (b->c->Int) -> (Canonical b) -> (Canonical c) -> (Canonical Int)
cmap2 f (Canonical x) (Canonical y) = canonicalize $ f x y

这在表面上类似于 Functor 和 Applicative,但并不完全相似,因为它太专业了:除非“b”是 Int,否则我实际上无法组合函数(按照 Functor/Applicative 的同态法则的要求)。

我的目标是使用现有的库函数/组合器,而不是编写我自己的变体,例如 cmapcmap2。那可能吗?是否有不同的类型类或不同的方式来构建规范类型,以实现我的目标?

我尝试过其他结构,例如

newtype Canonical a = Canonical { value :: a, canonicalizer :: a -> a }

但这遇到了同样的不可组合性问题,因为我无法将一个规范化器转换为另一个(我只想使用结果类型的规范化器,它始终为Int(或Integral a

而且我不能像这样强制“仅专业化”,这不是有效的 Haskell:

instance (Functor Int) (Canonical Int) 

(以及类似的变体)

我也试过

newtype (Integral a) => Canonical a = Canonical a -- -XDatatypeContexts
instance (Integral a) => Functor Canonical where
  fmap f (Canonical x) = canonicalize  $ f x

但是 GHC 说 DatatypeContexts 已被弃用,这是一个坏主意,而且更严重的是, 我明白了:

 `Could not deduce (Integral a1) arising from a use of 'C'   
 from the context (Integral a)
 bound by the instance declaration
 [...] fmap :: (a1 -> b) -> (C a1 -> C b)

我认为约束Integral a 实际上不能用于将fmap 约束为(Integral -> Integral) 我希望的方式,这很明显(因为fmap 有两个类型变量): -(

当然这也不是有效的 Haskell

instance (Integer a) => Functor Canonical where

是否有类似的类型类我可以使用,或者我尝试使用类型类来实现“隐式规范化函数调用的结果”的功能是错误的?

【问题讨论】:

  • 让我们退后一步。您确定类型签名cmap2 :: (b->c->Int) -> (Canonical Int) -> (Canonical Int) 描述了您想要的吗?因为它看起来很没有意义。它也与Functor 无关。下面是一个有效函子签名的示例:(Int -> Int) -> Canonical Int -> Canonical Int
  • 我的错误,我在将更复杂的类型简化为在 SO 上提出的问题时犯了错误。我已经(试图)改进定义,进行了编辑。

标签: haskell


【解决方案1】:

我认为您想要实现的目标可以在 mono-traversable 包中找到,在本例中是 MonoFunctor 类型类。

【讨论】:

  • 啊,mono-traversable 的神奇之处在于它使用了“类型族”。这样,具体类型Text 可以被视为Foo Char。有趣的是,在omap 中,Char 然后用作Element Text,签名是(Element m -> Element m) -> m -> m,它不完全是(a -> a) -> m a -> m a。在某种意义上,m 被视为UnElement (Element m),以使类型签名匹配fmap
  • 需要单向遍历到 fpcomplete ;-)。 haddocks.fpcomplete.com/fp/7.4.2/20130922-179
  • 在不稳定的软件包集中可用。在我们创建 9 月的稳定快照时,mono-traversable 还不够稳定。
  • 啊,昨天我在 FPComplete IDE 中找不到访问设置的汉堡菜单。切换到不稳定的效果很好! fpcomplete.com/school/using-fphc/…
【解决方案2】:

您的cmap 实际上可以通过fmap 实现。一开始有点奇怪,但(->) 本身只是一个数据类型,实际上等价于Reader。我们可以fmap over 返回结果。

cmap :: (a -> Int) -> a -> (Canonical Int)
cmap = fmap Can

您可以使用相同的模式创建其他 cmap 变体

cmap2 :: (a -> b -> Int) -> a -> b -> (Canonical Int)
cmap2 = fmap (fmap Can)

cmap3 :: (a -> b -> c -> Int) -> a -> b -> c -> (Canonical Int)
cmap3 = fmap (fmap (fmap Can))

现在在代码中看到这通常有点奇怪,在(->) 上看到不太通用的fmap 形式更为常见

instance Functor (r ->) where
  fmap = (.)

cmap :: (a -> Int) -> a -> (Canonical Int)
cmap = (.) Can
cmap f = Can . f

cmap2 :: (a -> b -> Int) -> a -> b -> (Canonical Int)
cmap2   = (.) ((.) Can)
cmap2   = (.) (Can .)
cmap2 f = (Can .) . f

cmap3 :: (a -> b -> c -> Int) -> a -> b -> c -> (Canonical Int)
cmap3   = (.) ((.) ((.) Can))
cmap3   = (.) ((.) (Can .))
cmap3   = (.) ((Can .) .)
cmap3 f = ((Can .) .) . f

显然,这在高阶组合器中变得有点荒谬。 Pointfree 样式可能不是最佳选择。

【讨论】:

  • 这看起来令人鼓舞,但请注意,我并不是简单地尝试使用 Canonical 构造函数标记一个值。我也在尝试添加一个转换:canonicalize = Canonical . (`mod` 10)
【解决方案3】:

从你的类型签名来看,这有点像 Identity monad

newtype Identity a = Identity a

instance Monad Identity where
         return x = Identity x
         f >>= (Identity x)  = Identity (f x)

那么在你的例子中 canonicalize = returncmap f x = f >>= (return x)

希望对你有帮助

编辑

我想到的另一个结构是自同构(我学过数学)

如果你有

data Automorphisms a = AMorph (a -> a)

那么你可以有一个 Monoid

instance Monoid Automorphism where
         mempty = id
         mappend = (.)
         mconcat = foldr1 (.)

(我希望 foldr1 是正确的)

【讨论】:

  • 我同意它看起来有点像Identity,但我认为如果我尝试将canonicalize 放入left-hand 侧,同样的类型检查问题也适用的定义。 IdentityCanonical 是参数多态的,但 canonicalize 充其量是临时多态的。
  • 你能解释一下吗?我想我现在智商很高(临时多态让我头晕目眩)——也许明天我会摸索它
  • 我的意思是Identity适用于任何类型a->a,但canonicalize实现为Int -> Int
  • 您的AutomorphismsEndo 来自Data.Monoid
  • @misterbee:对所有事情都使用Identity 并仅在必要时进行规范化怎么样?然后,您可以在大多数情况下使用 Haskell 的所有多态性,并且仅在绝对必要时才需要规范化的 Int。如果您另外定义了Identity IntCanonical Int 之间的同构,那么这种方法可能会非常轻量级。
猜你喜欢
  • 1970-01-01
  • 2017-04-19
  • 1970-01-01
  • 2020-12-25
  • 2015-09-07
  • 2016-04-27
  • 2021-11-10
  • 2012-02-13
  • 2020-03-03
相关资源
最近更新 更多