Java中的Functors

6

我正在尝试在Java中定义类,使其类似于Haskell的functors。这里,functor被定义为:

/**
 * Programming languages allow only (just simply enough) endofunctor, that are functors from and to the same category.
 * In this case, the category is the one of the datatypes (in here Type, in order to make it more clear)
 */
public interface EndoFunctor<X extends Type> extends Type {

    /**
     * The basic implementation for any element fx
     * @param map   Transformation function for the type parameter
     * @param fx    Element of the current class
     * @param <Y>   Target type
     * @return      transformed element through map
     */
    <Y extends Type> EndoFunctor<Y> fmap(Function<X,Y> map, EndoFunctor<X> fx);

}

如果我想实现一个类型的身份函子函子,我需要编写类似以下的代码:

public class Id<X extends Type> implements EndoFunctor<X> {
    protected X witness;
    Id(X witness) { this.witness = witness; }
    @Override
    public <Y extends Type> Id<Y> fmap(Function<X, Y> map, Id<X> fx) {
        return new Id<>(map.apply(fx.witness));
    }
}

这段代码的问题在于Id<X>与类型EndoFunctor<X>不匹配。我该如何确定EndoFunctor接口中的fmap,使得任何类型K<T>实现了EndoFunctor<T>且给定一个映射函数T->U时,返回值为K<U>,而不需要进行任何类型转换(也就是说,由于我知道我的对象是一个Id<T>,所以fmap的结果“必须是”Id<U>,因此我将类型EndoFunctor<U>的结果向下转换为这种类型)。

你为什么不使用 EndoFunctor<Y> fmap(Function<X,Y> map) 呢?这样,每个实例都将使用其实例变量。例如:return new Id<>(map.apply(this.witness)) - afsantos
好的,我认为这个观察并不能回答我的问题。你的映射(fmap2(x))可以定义为fmap(x,this),所以这并没有真正解决类型问题。 - jackb
顺便说一下,我这样定义函数是为了使它更像一个函子的定义,即F:(a->b)->(Fa -> Fb),其中F在这种情况下是EndoFunctor。 - jackb
1
在你的 Id 实现中,我觉得你限制了参数范围只能是 Id 的实例。我试图理解是否有可能让 Id 接受任何其他 Functor 类型的参数。 - afsantos
1
你可能想考虑尝试使用Scala,因为它的类型系统可以实现这一点。例如:trait Functor[F[_]] { def fmap[A,B](g: A => B)(f: F[A]): F[B] } - user2297560
好知道。唯一的问题是,当这些函数从Java中调用时(我有一些已经用Java编写的代码),这样的类型转换不会被保留。谢谢。 - jackb
3个回答

8

你可以使用 CRTP

interface EndoFunctor<X extends Type, T extends EndoFunctor<X, T>> extends Type {
    <Y extends Type> EndoFunctor<Y, ?> fmap(Function<X,Y> map, T fx);    
}

class Id<X extends Type> implements EndoFunctor<X, Id<X>> {
    protected X witness;
    Id(X witness) { this.witness = witness; }

    @Override
    public <Y extends Type> Id<Y> fmap(Function<X, Y> map, Id<X> fx) {
        return new Id<>(map.apply(fx.witness));
    }
}

这种方法解决了我代码后面出现的其他问题(我不会让你感到无聊)。谢谢。 - jackb

8
我该如何确定EndoFunctor接口中的fmap,以便如果任何类型K实现了EndoFunctor并给出一个映射函数T->U,则返回K作为值,而无需进行任何类型转换(也就是说,由于我知道我的对象是一个Id,因此fmap的结果“必须”是一个Id,因此我将类型为EndoFunctor的结果向下转换为这种类型)?
你不能做到这一点。这被称为高阶多态,在Java中不支持(很少有语言支持)。Jorn Vernee的回答让你在Java中最接近它,但该接口允许你写入
class NotId<X extends Type> implements EndoFunctor<X, Id<X>> {

    @Override
    public <Y extends Type> ADifferentEndoFunctorAgain<Y> fmap(Function<X, Y> map, Id<X> fx) { ... }
}

如果想要编写一个通用的代码,涉及到EndoFunctor,而不是像Id那样只针对特定的EndoFunctor,那么这种方法是行不通的。


实际上,我希望对类型进行一些提升能够解决这个问题。你的答案在形式上是正确的,而之前的答案则帮助我编写代码。可惜我不能同时接受两个答案。 - jackb

2
问题不在于 Id<X> 无法匹配 EndoFunctor<X>,而是当您尝试覆盖 fmap 时,您使参数类型更加具体,因此方法签名不再与 EndoFunctor 中的 fmap 方法签名匹配。
这意味着当前形式下的 Id<X> 并没有完全实现 EndoFunctor<X> 接口。在实现接口时,必须能够与您的类进行交互,而无需知道它是另一个接口。
要么按照注释中的建议删除此方法参数并使用实例变量,要么修改 Id<X> 中的签名为 public <Y extends Type> Id<Y> fmap(Function<X, Y> map, EndoFunctor<X> fx),以使其与接口兼容。

你的建议与上述描述的问题类似:这样做只允许返回一种特定的类型。 - jackb

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