mrh*_*nia 5 haskell functor typeclass
我注意到我有很多函数可以为我的值添加某种标签。例如,考虑这两种数据类型:
data Named a = Named String a
data Colored a = White a | Black a
Run Code Online (Sandbox Code Playgroud)
以及一些使用它们的函数:
name :: Foo -> Named Foo
color :: Named Foo -> Colored (Named Foo)
Run Code Online (Sandbox Code Playgroud)
在某些时候,我开始拥有带有许多类似嵌套“标签”的函数,所以我想知道是否可以将其概括为更易于管理。所有这些都可能很好地与 PureScript 的行多态性配合使用,但我们在这里谈论的是 Haskell。无论如何,这就是我想出的:
class Tag f where
separate :: f a -> (forall b. b -> f b, a)
Run Code Online (Sandbox Code Playgroud)
法律应该是这样的:
fx = let (f, x) = separate fx in f x
Run Code Online (Sandbox Code Playgroud)
或者不进行类型检查但更优雅的版本:
uncurry ($) . separate = id
Run Code Online (Sandbox Code Playgroud)
Tag也可以成为一个子类,Functor前提是
fmap g fx = let (f, x) = separate fx in f (g x)
Run Code Online (Sandbox Code Playgroud)
例如数据类型的实例如下:
instance Tag Named where
separate (Named name x) = (Named name, x)
instance Tag Colored where
separate (White x) = (White, x)
separate (Black x) = (Black, x)
Run Code Online (Sandbox Code Playgroud)
...以及其他一些一般情况:
instance Tag Identity where
separate (Identity x) = (Identity, x)
instance (Tag f, Tag g) => Tag (Compose f g) where
separate (Compose fgx) =
let (f, gx) = separate fgx in
let (g, x) = separate gx in
(Compose . f . g, x)
Run Code Online (Sandbox Code Playgroud)
使整个类型类真正有用的是这个函数:
reorder :: (Tag f, Tag g) => f (g a) -> g (f a)
reorder fgx =
let (f, gx) = separate fgx in
let (g, x) = separate gx in
g (f x)
Run Code Online (Sandbox Code Playgroud)
它看起来像一些明显的习语,所以它必须已经为社区所知。当您不知道要搜索的事物的名称并且 Hoogle 没有任何结果时,Google 的帮助不是很大。
所以我在这里,寻找一个名字,甚至一些图书馆,看看我还能用那个东西做些什么。
user2407038 的评论确实触及了它的核心,因为您最终想要表达的概念可以归结为与某个对函子同构的函子——毕竟,对是附加了其他东西的值。从这个角度来看,还有一些可能有趣的额外事情需要注意。
为了方便起见,我将假设您的Tags 是Functors (您提到的相关条件通过参数性成立),并通过替换forall b. b -> f b为同构来简化类型f ()。那么我们可能有:
separate :: Tag f => f a -> (f (), a)
Run Code Online (Sandbox Code Playgroud)
与法律:
slot . separate = id
Run Code Online (Sandbox Code Playgroud)
其中左逆元为:
slot :: Functor f => (f (), a) -> f a
slot (sh, a) = fmap (const a) sh
Run Code Online (Sandbox Code Playgroud)
通过添加应该是满射的要求slot(就您的用例而言,这是完全合理的),slot升级为完全逆,从而为我们提供了f a和之间的同构(f (), a)。
这些功能至少存在于生态系统中的一个地方:adjunctions包:
splitL :: Adjunction f u => f a -> (a, f ())
unsplitL :: Functor f => a -> f () -> f a
Run Code Online (Sandbox Code Playgroud)
Hask/Hask 左伴随同构于配对函子,同时也与 Hask/Hask 右伴随同构Representable,即与函数函子同构。dfeuer 的怀疑在某种程度上是合理的,尽管附加词并没有给我们提供Distributiveor的左伴随对应项Representable。这是他们的草图,名字是虚构的:
class Traversable t => Lone t where
codistribute :: Functor f => t (f a) -> f (t a)
surround :: Functor f => (a -> f b) -> t a -> f (t b)
class Lone f => Detachable f where
type Corep f
cotabulate :: (Corep f, a) -> f a
coindex :: f a -> (Corep f, a)
Run Code Online (Sandbox Code Playgroud)
关于连接的几点说明Traversable:
codistribute和分别surround只是sequenceA和traverse,除非将Applicative约束放宽到Functor(如果总是只有一个值,Applicative则没有必要)。
出于同样的原因,cotabulateand coindex(或slot和separate,或unsplitL和splitL)可以被视为可遍历函子的形状和内容分解的表现(其中我们不需要内容的列表/向量,因为同样,总是存在精确的一个值)。
可以想象,成对的各种实例都可以通过 进行翻译Detachable。对于Comonad,甚至不需要,因为就Lone足够了(另请参见extractL和duplicateL来自附加词)。
surround是 van Laarhoven 透镜(a Lens (t a) (t b) a b,用透镜术语来说),就像traverse是 van Laarhoven 遍历 ( Traversal (t a) (t b) a b) 一样。
最后但并非最不重要的codistribute一点是对您的reorder.