2011-02-01 50 views
13

我想在Java中定义一个Functor类。这工作:Java泛型:如何在Java中编码Functor接口?

//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); 
} 

然而FMAP的返回值应不Functor,但相应的子类。通常这可以使用CRTP进行编码,但由于附加参数A,这里我似乎碰壁了。例如。下列和类似的编码不工作(“类型参数元一次不在其边界内”):

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

[澄清]

随着“适宜亚类”我指的是类的类型被称为自己。例如。列表是仿函数,所以我想写点东西像

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); 
    } 
} 

我知道,我会(因为协变返回类型允许的),即使我给的第一个定义写这篇文章,但我想要的回报类型“ListFunctor”是由类型系统强制执行(这样我就不能返回FooFunctor),这意味着Functor接口需要返回“自我类型”(至少在其他语言中这样调用) )。

[结果]

这样看来我想什么是不可能的。这里是一个相关的博客文章:http://blog.tmorris.net/higher-order-polymorphism-for-pseudo-java/

+1

可能有错误的形式有超过3个类型参数,尤其是包含其他类型的参数,因为声明变得很混乱,没有人知道发生了什么。 – 2011-02-01 12:23:11

+1

关于“库作家”和使用方面有所不同。即使使用了Java的有限类型推断(例如通过使用静态方法),您也可以经常“隐藏”泛型。 – Landei 2011-02-01 12:36:47

回答

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

因为当定义I,将其定义为的Functor<B,FInst>一个子类此代码生成一个错误,但元一次参数必须是Functor<B,FInst>一个子类在这种情况下,尽管以上定义为一个Functor<A,FInst>的子类。由于Functor<A,FInst>Functor<B,FInst>不兼容,您会收到此错误。

我一直没能彻底解决这个问题,但我可以做至少完成一半:

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); 
    } 
} 

这工作,并适当限制允许返回类型ListFunctor的设定,但它并不仅限于ListFunctor<B>的子类。你可以声明它返回ListFunctor<A>或任何其他ListFunctor,它仍然会编译。但是你不能声明它返回一个FooFunctor或任何其他Functor。

解决问题的其余部分的主要问题是,您不能将FClass仅限于ListFunctor<B>的子类,因为B参数是在方法级声明的,而不是在类级声明的,所以您不能写

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

因为B在这一点上没有任何意义。我无法使用fmap()的第二个参数来处理它,但即使可以,它也会强制你指定返回类型两次 - 一次在类型参数中,一次作为返回类型本身。

0

我认为你想做的事情没有任何意义(类型明智)。

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

如果您的应用程序想要一个返回整数的getter,请不要给它返回Object。

如果你不知道它是否会返回对象或整数,你试图做错误的方式。

如果你知道它将返回整数,然后包装吸气剂,使它转换为整数。

希望这是你在找什么。

编辑: 说明为什么(我认为)这是不能做到的。

当您使用新的对象时,会出现类型设置。 取每种类型并用一个字母替换。 采取任何数量的其他对象,并做同样的事情。

你想让你的函数返回什么字母? 如果答案是你想要混合,那么它太晚了。类型是以新的方式决定的,而且你已经过去了。

+0

类似的东西正在工作(看看java.lang.Enum的定义),我想我想要的是非常合理的(例如,在Haskell或Scala中不会有问题)。问题是我们能够推进Java的瘫痪型系统的界限。 – Landei 2011-02-01 12:00:15

+0

我没有设法使用java构建递归泛型构造,同时保留外部类型。正如有人在这里写了一篇文章,你不能从上面的级别捕获的东西。我坚持我的解释,你试图给你的程序一个能够返回你无法说出任何事情的对象。 Java泛型非常静态。一旦你将一个通用类型设置为一种类型,你就不能回头了。在新的你有泛型之前。新的你有固定的类型。所以一旦你的对象通过新的,它的所有类型都决定了。 – mncl 2011-02-01 15:51:19

+0

除推论之外,这是由对象决定的,已经有了新的陈述。没有任何物体知道组合(干扰)会返回什么类型。 – mncl 2011-02-01 15:52:40

0

建立在谢尔盖的答案上,我想我来了关闭到我想要的。似乎我可以将他的想法和我的失败尝试结合起来:

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] 

剩下的问题是我可以编写例如ListFunctor<StringBuilder> fi = fs.<Integer,ListFunctor<StringBuilder>>没有编译器的抱怨。至少我可以寻找一种方法来隐藏背后的静态方法丑陋的胆量,并强制幕后的关系...

2

从不同的角度来看,似乎Functor不应该被建模为“包装“的数据,但实际上更像是一个类型,它的工作原理上的的数据。透视这一转变允许没有一个单一的投编码的一切,绝对类型安全的(但仍然有很多的样板):

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] 

看来我太专注于一种类型的包装都FromInstance和ToInstance参数(例如ListFunctor中的List),这不是严格必要的。然而,现在不仅有A而且还有B作为类型参数是一个沉重的负担,这可能使这种方法实际上无法使用。

[研究]

我找到了一种方法,使该版本至少一点点有用:这仿函数可用于解除功能。例如。如果你有F<String, Integer>,你可以从它构建一个F<Foo<String>, Foo<Integer>>当你定义了一个FooFunctor如上图所示:

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] 

我认为它仍然不是很有用,但至少冷却器的方式比其他的尝试:-P