Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make @This work on type parameters #47

Closed
WilliamStone opened this issue Feb 28, 2019 · 7 comments

Comments

Projects
None yet
2 participants
@WilliamStone
Copy link

commented Feb 28, 2019

Manifold version: 0.45-alpha
JDK version: 11.0.2

Example of requested feature:

package utilities.extensions.java.lang.Object;

import manifold.ext.api.Extension;
import manifold.ext.api.This;

import java.util.function.Function;

@Extension
public class MyObjectExt {

    public static <T extends Object, R> R let1(@This Object thiz, Function<T, R> mapper) {
        return mapper.apply((T) thiz);
    }

    // Cannot compile, Type of thiz must be Object. **Please make this work**
    public static <T extends Object, R> R let2(@This T thiz, Function<T, R> mapper) {
        return mapper.apply((T) thiz);
    }

    public static <T extends Object, R> R let3(T thiz, Function<T, R> mapper) {
        return mapper.apply((T) thiz);
    }
    
    public static void test() {
        String str = "abc";
        String x11 = str.let1((String it) -> it.trim());     // This is ok, but I have to specify type of 'it'
        Object x12 = str.let1(it -> it.trim());            // Error, type of 'it' is deduced as Object

        String x21 = str.let2(it -> it.trim());            // Ideal state, type of 'it' should be deduced as String, if method let2 works
        
        String x31 = let3(str, it -> it.trim());        // without extension method, it's type deduction is working
    }
}

When I call method let1 with argument of some type T, it's type info is lost in method body, so type parameter T of mapper cannot get its correct type.
In my eyes extension method is just syntax sugar, so if let3 is working, so should let1.

@rsmckinney

This comment has been minimized.

Copy link
Member

commented Mar 1, 2019

Ah. Not a problem, so long as the upper bound of the type variable is assignable to the extended type, which as you point out is the case in your example. Manifold will support this feature in the next alpha release, probably within a week. Let me know if you need it sooner.

@WilliamStone

This comment has been minimized.

Copy link
Author

commented Mar 1, 2019

Thanks for your quick response! One week is very fast iteration, I can wait.

@WilliamStone WilliamStone reopened this Mar 1, 2019

@rsmckinney

This comment has been minimized.

Copy link
Member

commented Mar 1, 2019

Hi @WilliamStone.

Just looking to clarify and make sure your use case is fully understood.

I want to understand why T is necessary for your use-case. For instance, if we modify let2() like the following, it is unnecessary.

    public static <R> R let2(@This Object thiz, Function<Object, R> mapper) {
        return mapper.apply(thiz);
    }

Were you looking for the mapper parameter's type to be more specific wrt the extended type? Or is Object suitable enough?

I'm assuming you have a more involved actual use-case and I'm wondering if there are other measures you could take, at least for the interim while I work out the implementation for this change. Basically, the more information you can provide, the better. Thanks.

@WilliamStone

This comment has been minimized.

Copy link
Author

commented Mar 2, 2019

Hi @rsmckinney ,

It's about covariance. I don't know terms exactly enough to describe, so I'll just show an example.

package utilities.extensions.java.lang.Object;

import lombok.val;
import manifold.ext.api.Extension;
import manifold.ext.api.This;

import java.util.function.Function;

@Extension
public class MyObjectExt {
    // Currently cannot compile
    public static <T extends Object, R> R let2(@This T thiz, Function<T, R> mapper) {
        return mapper.apply((T) thiz);
    }

    // let2 without @This
    public static <T extends Object, R> R let3(T thiz, Function<T, R> mapper) {
        return mapper.apply((T) thiz);
    }

    // Your modified version of let2
    public static <R> R let2_2(@This Object thiz, Function<Object, R> mapper) {
        return mapper.apply(thiz);
    }

    public static void test2() {
        String str = "abc";
        Function<String, Integer> mapper = String::length;

        str.let2_2(mapper);  // Can't compile, since let2_2 expects Function<Object, R>, not Function<String, R>
        let2_2(str, mapper); // Can't compile even in its non-extension form
        let3(str, mapper);   // Works
        str.let2(mapper)     // Should work if let2 compiles, since let3 works, and let3 is let2 without @This
    }
}

My current use case: I have a method that receives mapper as argument, In method I write method chain like

    public class SomeSubject<T extends SubjectBase> {
        private T subject;

        public <R> void someMethod(Function<T, R> mapper) {

            ...
            subject.xxx().yyy().let(mapper).zzz()....
            ...
        }
    }

, which is convenient. And since I have several such subject classes, I prefer to put let in class Object.
And thanks for caring, I can workaround it by breaking method chain and writing non-extension method call, just like

    var x = subject.xxx().yyy();
    var y = mapper.apply(x);
    y.zzz()....

which is not a big problem. So I can wait and not to disturb your version plan.

@rsmckinney

This comment has been minimized.

Copy link
Member

commented Mar 5, 2019

work in progress

rsmckinney added a commit that referenced this issue Mar 8, 2019

Manifold extension improvements
- Rewrite @self implementation to provide comprehensive 'self' type support
- @self can be applied to:
-- instance method **return type**
-- instance method **parameter type**
-- instance field type
- the compiler establishes the type of a method/field reference at the use-site
- no bridge methods or other shenanigans otherwise present with recursive generics

Note the completion of @self facilitates #47 -- use @self instead of generic methods and recursive generic types.
@rsmckinney

This comment has been minimized.

Copy link
Member

commented Mar 8, 2019

Hi @WilliamStone.

The functionality you're looking for is best expressed in terms of the self type. With the latest alpha release (0.47-alpha), Manifold now fully supports the self type via @Self.

Your example modified to use @Self:

@Extension
public class MyObjectExt {
    public static <R> R let(@This Object thiz, Function<@Self Object, R> mapper) {
        return mapper.apply(thiz);
    }
}

String str = "abc";
String result = str.let(s -> s.trim());

Use the self type to express the type of the invoker (aka the type of this). Use it in super classes and extension methods anywhere in a method signature or field type. It is especially useful as a means to simplify architecture that otherwise involves recursive generic types. Other applications involve extension methods and structural interfaces.

Note the accompanying v0.47-alpha IntelliJ plugin is awaiting JetBrains approval, which can take a couple of days.

@rsmckinney rsmckinney closed this Mar 17, 2019

@WilliamStone

This comment has been minimized.

Copy link
Author

commented Mar 30, 2019

Thanks! I understand that the extension class is for Object not generic type T. I still feels a little inconvenience that if the actuaI type is some class extending Object, I cannot refer to its type by defining a generic type T extending Object, instead I have to regard it as Object itself. No big difference till now, and I'll try to get to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.