如何避免编写此类Haskell样板代码 [英] How do I avoid writing this type of Haskell boilerplate code

查看:84
本文介绍了如何避免编写此类Haskell样板代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常遇到这种情况,使它很烦人.

I run into this situation often enough for it to be annoying.

比方说,我有一个求和类型,可以容纳x的实例或一堆与x不相关的其他事物-

Let's say I have a sum type which can hold an instance of x or a bunch of other things unrelated to x -

data Foo x = X x | Y Int | Z String | ...(other constructors not involving x)

要声明一个Functor实例,我必须这样做-

To declare a Functor instance I have to do this -

instance Functor Foo where
    fmap f (X x) = X (f x)
    fmap _ (Y y) = Y y
    fmap _ (Z z) = Z z
    ... And so on

而我想做的是-

instance Functor Foo where
    fmap f (X x) = X (f x)
    fmap _ a = a

即我只关心X构造函数,所有其他构造函数都只是通过".但这当然不会编译,因为左侧的a与等式右侧的a是不同的类型.

i.e. I only care about the X constructor, all other constructors are simply "passed through". But of course this wouldn't compile because a on the left hand side is a different type from the a on the right hand side of the equation.

有没有办法避免为其他构造函数编写此样板?

Is there a way I can avoid writing this boilerplate for the other constructors?

推荐答案

我假设我们想为一般情况提供一种解决方案,在这种情况下,更改类型的参数不一定位于DeriveFunctor的正确位置.

I assume that we'd like to have a solution for the general case where the changing type parameter is not necessarily in the right position for DeriveFunctor.

我们可以区分两种情况.

We can distinguish two cases.

在简单情况下,out数据类型不是递归的.在这里,棱镜是一个合适的解决方案:

In the simple case out data type is not recursive. Here, prisms are a fitting solution:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data Foo x y = X x | Y y | Z String

makePrisms ''Foo

mapOverX :: (x -> x') -> Foo x y -> Foo x' y
mapOverX = over _X

如果我们的数据是递归的,那么事情会变得更加复杂.现在makePrisms不会创建变型棱镜.我们可以通过将其分解为明确的固定点来摆脱定义中的递归.这样,我们的棱镜就可以保持类型变化:

If our data is recursive, then things get more complicated. Now makePrisms doesn't create type-changing prisms. We can get rid of the recursion in the definition by factoring it out to an explicit fixpoint. This way our prisms remain type-changing:

import Control.Lens

newtype Fix f = Fix {out :: f (Fix f)}

-- k marks the recursive positions
-- so the original type would be "data Foo x y = ... | Two (Foo x y) (Foo x y)"
data FooF x y k = X x | Y y | Z String | Two k k deriving (Functor)

type Foo x y = Fix (FooF x y)

makePrisms ''FooF

mapOverX :: (x -> x') -> Foo x y -> Foo x' y
mapOverX f = 
   Fix .               -- rewrap 
   over _X f .         -- map f over X if possible
   fmap (mapOverX f) . -- map over recursively
   out                 -- unwrap

或者我们可以排除自下而上的转换:

Or we can factor out the bottom-up transformation:

cata :: (Functor f) => (f a -> a) -> Fix f -> a
cata f = go where go = f . fmap go . out

mapOverX :: (x -> x') -> Foo x y -> Foo x' y
mapOverX f = cata (Fix . over _X f)

有大量关于使用函子的固定点进行通用编程的文献,还有许多库,例如.您可能需要搜索递归方案"以获取更多参考.

There's a sizeable literature on using fixpoints of functors for generic programming, and also a number of libraries, for example this or this. You might want to search for "recursion schemes" for further references.

这篇关于如何避免编写此类Haskell样板代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆