2011-02-01 49 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