简体   繁体   English

Java泛型:如何用Java编码Functor接口?

[英]Java generics: How to encode a Functor interface in Java?

I want to define a Functor class in Java. 我想用Java定义一个Functor类。 This works: 这有效:

//a Function
public interface F<A,R> {
   public R apply(A a);
}

public interface Functor<A> {
   public <B> Functor<B> fmap(F<A,B> f);
}

However the return value of fmap should be not Functor , but the appropriate subclass. 但是fmap的返回值应该不是Functor ,而是相应的子类。 Usually this can be encoded with the CRTP , but here I seem to hit a wall because of the additional parameter A . 通常这可以使用CRTP进行编码,但是由于附加参数A ,我似乎在这里遇到了问题。 Eg the following and similar encodings don't work ("type parameter FInst is not within its bounds"): 例如,以下和类似的编码不起作用(“类型参数FInst不在其范围内”):

public interface Functor<A, FInst extends Functor<A,FInst>> {
    public <B, I extends Functor<B,FInst>> I fmap(F<A,B> f);
}

[Clarification] [澄清]

With "appropriate subclass" I mean the type of the class being called itself. 对于“适当的子类”,我指的是被称为自身的类的类型。 Eg Lists are functors, so I would like to write something like 例如列表是算子,所以我想写类似的东西

public class ListFunctor<A> implements ??? {
  final private List<A> list;
  public ListFunctor(List<A> list) {
     this.list = list;
  } 

  @Override
  <B> ListFunctor<B> fmap(F<A,B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: list) result.add(f.apply(a));
     return new ListFunctor<B>(result); 
  }  
}

I'm aware that I could write this even with the first definition I gave (because covariant return types are allowed), but I want that the return type "ListFunctor" is enforced by the type system (so that I can't return a FooFunctor instead), which means that the Functor interface needs to return the "self-type" (at least it is called so in other languages). 我知道即使使用我给出的第一个定义也可以写这个(因为允许使用协变返回类型),但是我希望返回类型“ListFunctor”由类型系统强制执行 (这样我就不能返回相反,FooFunctor),这意味着Functor接口需要返回“自我类型”(至少在其他语言中被称为)。

[Result] [结果]

So it seems what I want is impossible. 所以看起来我想要的是不可能的。 Here is a related blog-post: http://blog.tmorris.net/higher-order-polymorphism-for-pseudo-java/ 这是一篇相关的博客文章: http//blog.tmorris.net/higher-order-polymorphism-for-pseudo-java/

[Aftermath] [后果]

I stumbled over this age-old question of mine, and realized that this was the starting point of the amazing journey with my library highJ , containing much more than a simple Functor . 我偶然发现了这个古老的问题,并意识到这是我的图书馆highJ的惊人旅程的起点,它不仅仅包含一个简单的Functor I would have never imagine that people would use this crazy stuff for anything serious, but it happened, and that makes me very happy. 我永远不会想象人们会把这些疯狂的东西用于任何严肃的事情,但事情发生了,这让我很开心。

public interface Functor<A, FInst extends Functor<A,FInst>> {
    public <B, I extends Functor<B,FInst>> I fmap(F<A,B> f);
}

This code generates an error because when you define I , you define it to be a subclass of Functor<B,FInst> , but the FInst parameter must be a subclass of Functor<B,FInst> in this case, while it is defined above as being a subclass of Functor<A,FInst> . 此代码生成错误,因为当您定义I ,将其定义为Functor<B,FInst>的子类,但在这种情况下,FInst参数必须是Functor<B,FInst>的子类,而它在上面定义作为Functor<A,FInst>的子类。 Since Functor<A,FInst> and Functor<B,FInst> aren't compatible, you get this error. 由于Functor<A,FInst>Functor<B,FInst>不兼容,因此会出现此错误。

I haven't been able to solve this completely, but I could do at least a half of the job: 我完全无法解决这个问题,但我至少可以完成一半的工作:

import java.util.ArrayList;
import java.util.List;

interface F<A,R> {
   public R apply(A a);
}

interface Functor<A, FClass extends Functor<?, FClass>> {
   public <B> FClass fmap(F<A,B> f);
}

public class ListFunctor<A> implements Functor<A, ListFunctor<?>> {
  final private List<A> list;
  public ListFunctor(List<A> list) {
     this.list = list;
  }

  @Override
  public <B> ListFunctor<B> fmap(F<A,B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: list) result.add(f.apply(a));
     return new ListFunctor<B>(result);
  }
}

This works, and it properly limits the set of allowed return types to ListFunctor, but it doesn't limit it to subclasses of ListFunctor<B> only. 这是有效的,并且它正确地将允许的返回类型集限制为ListFunctor,但它不仅限于ListFunctor<B>子类。 You could declare it as returning ListFunctor<A> or any other ListFunctor, and it would still compile. 您可以将其声明为返回ListFunctor<A>或任何其他ListFunctor,它仍然可以编译。 But you can't declare it as returning a FooFunctor or any other Functor. 但是您不能将其声明为返回FooFunctor或任何其他Functor。

The main problem with solving the rest of the problem is that you can't limit FClass to subclasses of ListFunctor<B> only, as the B parameter is declared at the method level, not at the class level, so you can't write 解决其余问题的主要问题是你不能只将FClass限制为ListFunctor<B>子类,因为B参数是在方法级别而不是在类级别声明的,所以你不能写

public class ListFunctor<A> implements Functor<A, ListFunctor<B>> {

because B doesn't mean anything at that point. 因为B在那一点上没有任何意义。 I couldn't get it working with the second parameter to the fmap() either, but even if I could, it would just force you to specify the return type twice - once in the type parameter and once more as the return type itself. 我无法使用fmap()的第二个参数,但即使我可以,它只会强制您指定两次返回类型 - 一次在type参数中,再一次作为返回类型本身。

Looking from a different angle, it seems Functor shouldn't be modeled as a "Wrapper" around the data, but actually more like a type-class, which works on the data. 从不同的角度来看,似乎Functor不应该被建模为围绕数据的“包装器”,而实际上更像是一个类型类,它可以处理数据。 This shift of perspective allows to encode everything without a single cast, and absolutely type-safe (but still with a lot of boilerplate): 这种透视转换允许在没有单个转换的情况下编码所有内容,并且绝对类型安全(但仍然有很多样板):

public interface Functor<A, B, FromInstance, ToInstance> {
    public ToInstance fmap(FromInstance instance, F<A,B> f);
}

public class ListFunctor<A,B> implements Functor<A, B, List<A>, List<B>> {

    @Override
    public List<B> fmap(List<A> instance, F<A, B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: instance) result.add(f.apply(a));
     return result;
    }
}

List<String> stringList = Arrays.asList("one","two","three");
ListFunctor<String,Integer> functor = new ListFunctor<String,Integer>();
List<Integer> intList = functor.fmap(stringList, stringLengthF);
System.out.println(intList);
//--> [3, 3, 5]

It seems I was too focused on packing both FromInstance and ToInstance in one type parameter (eg List in ListFunctor), which isn't strictly necessary. 我似乎过于专注于在一个类型参数(例如ListFunctor中的List)中打包FromInstance和ToInstance,这不是绝对必要的。 However, it's a heavy burden to have now not only A but also B as type parameter, which may make this approach practically unusable. 然而,现在不仅A而且B作为类型参数是一个沉重的负担,这可能使这种方法几乎无法使用。

[Research] [研究]

I found a way to make this version at least a little bit useful: This functor can be used to lift a function. 我发现了一个方法,使该版本至少一点点有用的:这仿函数可用于解除功能。 Eg if you have F<String, Integer> , you can construct a F<Foo<String>, Foo<Integer>> from it when you have a FooFunctor defined as shown above: 例如,如果你有F<String, Integer> ,当你有如上所示定义的FooFunctor时,可以从它构造一个F<Foo<String>, Foo<Integer>>

public interface F<A,B> {
   public B apply(A a);

   public <FromInstance, ToInstance> F<FromInstance, ToInstance> lift(
      Functor<A,B,FromInstance, ToInstance> functor);
}

public abstract class AbstractF<A,B> implements F<A,B> {

    @Override
    public abstract B apply(A a);

    @Override
    public <FromInstance, ToInstance> F<FromInstance, ToInstance> lift(
          final Functor<A, B, FromInstance, ToInstance> functor) {
        return new AbstractF<FromInstance, ToInstance>() {

            @Override
            public ToInstance apply(FromInstance fromInstance) {
                return functor.fmap(fromInstance, AbstractF.this);
            }

        };
    }
}

public interface Functor<A, B, FromInstance, ToInstance> {
    public ToInstance fmap(FromInstance instance, F<A,B> f);
}

public class ListFunctor<A, B> implements Functor<A, B, List<A>, List<B>> {

    @Override
    public List<B> fmap(List<A> instance, F<A, B> f) {
        List<B> result = new ArrayList<B>();
        for (A a : instance) {
            result.add(f.apply(a));
        }
        return result;
    }
}

//Usage:
F<String, Integer> strLenF = new AbstractF<String, Integer>() {
            public Integer apply(String a) {
                return a.length();
            }
        };

//Whoa, magick!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
F<List<String>,List<Integer>> liftedF = strLenF.lift(new ListFunctor<String, Integer>());

List<String> stringList = Arrays.asList("one", "two", "three");
List<Integer> intList = liftedF.apply(stringList);
System.out.println(intList);
//--> [3, 3, 5]

I think it's still not very useful, but at least way cooler than the other attempts :-P 我认为它仍然不是很有用,但至少比其他尝试更酷:-P

Building on the answer of Sergey, I think I came close to what I wanted. 在谢尔盖的回答基础上,我想我接近了我想要的东西。 Seems like I can combine his idea with my failed attempt: 似乎我可以将他的想法与我失败的尝试结合起来:

public interface Functor<A, Instance extends Functor<?, Instance>> {
    public <B, I extends Functor<B,Instance>> I fmap(F<A,B> f);
}

public class ListFunctor<A> implements Functor<A, ListFunctor<?>> {
  final private List<A> list;
  public ListFunctor(List<A> list) {
     this.list = list;
  }

  @Override
  public <B, I extends Functor<B, ListFunctor<?>>> I fmap(F<A,B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: list) result.add(f.apply(a));
     return (I) new ListFunctor<B>(result);
  }
}

List<String> list = java.util.Arrays.asList("one","two","three");
ListFunctor<String> fs = new ListFunctor<String>(list);
ListFunctor<Integer> fi = fs.<Integer,ListFunctor<Integer>>fmap(stringLengthF);
//--> [3,3,5]

The remaining problem is that I could write eg ListFunctor<StringBuilder> fi = fs.<Integer,ListFunctor<StringBuilder>> without complaints from the compiler. 剩下的问题是我可以编写例如ListFunctor<StringBuilder> fi = fs.<Integer,ListFunctor<StringBuilder>>而无需编译器的抱怨。 At least I can look for a way to hide the ugly guts behind a static method, and to enforce that relation behind the scenes... 至少我可以寻找一种方法来隐藏静态方法背后的丑陋内容,并在幕后加强这种关系......

Does anyone still use Java and ponder this problem? 还有人还在使用Java并思考这个问题吗? You might find this useful... 你可能会觉得这很有用......

I've been pondering this for a looooong time. 我一直在思考这个问题。 I believe I've made something satisfactory. 我相信我已经取得了令人满意的成绩。 What I would really like to is indeeed impossible in Java. 在Java中,我真正想要的是不可能的。

This is ideal: 这是理想的:

interface Functor<T, CONCRETE<A> extends Functor<A, CONCRETE>> {
    CONCRETE<U> fmap(Func<T, U>);
}

Unfortunately, this is make-believe syntax. 不幸的是,这是伪装语法。 This kind of thing is possible in C++ with template-template parameters, but not Java. 在C ++中可以使用模板模板参数进行此类操作,但不能使用Java。

I was tempted to write this simple thing: 我很想写这个简单的事情:

interface Functor<T> {
    Functor<U> fmap(Func<T, U>);
}

This works in some cases, because an implementation can return a covariant return type (for example, List could return a List from this function), but it breaks down when you try passing around generic variables of type "F extends Functor", or a subclass of Functor, etc... 这在某些情况下有效,因为实现可以返回协变返回类型(例如,List可以从此函数返回List),但是当您尝试传递“F extends Functor”类型的泛型变量时,它会崩溃,或者Functor等的子类......

What I ended up doing was introduce a "dummy type variable", like so: 我最终做的是引入一个“虚拟类型变量”,如下所示:

interface Functor<CONCRETE, T> {
    Functor<CONCRETE, U> fmap(Func<T, U>);
}

The "concrete type" should be the type itself, or some dummy type that guarantees the uniqueness of its implementors. “具体类型”应该是类型本身,或者是一些保证其实现者唯一性的虚拟类型。 Here's an example implementation: 这是一个示例实现:

public final class Array<T> implements Functor<Array<?>, T> {
    private final T[] _values;

    @SafeVarargs
    public Array(T... values) {
        _values  = values;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <A, RESULT extends Functor<Array<?>, A>> RESULT fmap(Function<T, A> f) {
        A[] result = (A[]) new Object[_values.length];
        for (int i = 0; i < _values.length; ++i) {
            result[i] = f.apply(_values[i]);
        }

        return (RESULT) new Array<A>(result);
    }
}

The cast to (RESULT) is safe because there can only be one type that matches "Functor, T>", and that's "Array". 转换为(RESULT)是安全的,因为只有一种类型匹配“Functor,T>”,那就是“Array”。 The disadvantage of this, is that generic code may need to pass around this "CONCRETE" type in a bunch of places, and it makes your signatures unwieldy. 这样做的缺点是,通用代码可能需要在一堆地方传递这种“CONCRETE”类型,这会使你的签名变得笨拙。 For instance: 例如:

public class Test {
    public static <CONCRETE, FInt extends Functor<CONCRETE, Integer>, FBool extends Functor<CONCRETE, Boolean>> FBool intToBool(FInt ints) {
        return ints.fmap(x -> x > 5);
    }

    public static void main() {
        Array<Integer> ints = new Array<>();        
        Array<Boolean> bools1 = ints.fmap(x -> x > 5); // this works because Array<> implements fmap covariantly
        Array<Boolean> bools2 = intToBool(ints); // but this also works thanks to our dummy CONCRETE type
    }
}

I think you want to do something that makes no sense (type wise). 我想你想要做一些毫无意义的事情(明智的)。

interface Getter<Type> {
 Type get();
}

If your application wants a getter that returns Integers, don't give it one that returns Objects. 如果您的应用程序想要一个返回Integers的getter,请不要给它一个返回Objects的。

If you don't know if it will return Objects or Integers you are trying to do something the wrong way. 如果您不知道它是否会返回对象或整数,那么您正试图以错误的方式执行某些操作。

If YOU KNOW it will return Integers, then wrap the getter so that it casts to integers. 如果你知道它将返回Integers,那么包装getter以便它转换为整数。

Hope this is what you are looking for . 希望这是你正在寻找的。

EDIT: Explanation of why (I think) this can not be done. 编辑:解释为什么(我认为)这不能做。

Objects have there types set when you use new. 当您使用new时,对象具有类型集。 Take each type and replace it with a letter. 取每种类型并用字母替换。 Take any number of another objects and do the same. 获取任意数量的其他对象并执行相同操作。

What letter do you want your function to return? 你希望你的函数返回什么字母? If the answer is that you want a mix, well then its too late. 如果答案是你想要混合,那么为时已晚。 Types are decided at new, and you are already past new. 类型是新的,你已经过了新的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM