Groovy - Understand Closure in More Detail and Distinguish Each Lexical Scope in Closure

in utopian-io •  6 years ago  (edited)

Groovy.png

Repository

https://github.com/JetBrains/intellij-community
https://github.com/gradle/gradle
https://github.com/apache/groovy

What Will I Learn?

  • You will learn more about Closure
  • Deal with functional programming in Groovy
  • All about lexical scope upon Closure in Groovy

Requirements


I will not discuss the installation and creation steps anymore in this tutorial. For more details, please visit my previous one.


Difficulty

  • Beginner


In my previous tutorial, I've already discussed about Groovy DSL can be more flexible and powerful to build such a custom syntax, so anyone can easily interpret the codes even to who doesn't understand the programming at all.

But, I think DSL comes prematurely to the beginners. Because to get closer, they have to understand Closure extensively in advance. And now, this tutorial will explain it in detail. Initially, I will use the term of block to simply refer to the block of statements or algorithm to solve a particular case, i.e. function, method or closure. Because in Groovy, they have the same major characteristic as performing a particular process depends on the parameter(s) (as input), has the type which is a type of the returned value (as output) and always produce any output, at least null or even you somehow define it which does not intend to return any value at all.

The main goal of a block is to prevent redundant definitions. So, if Java SE has provided a solution raising a number to the power of the second one, Math.pow(double, double), then we don't need to create the same new definition. Just use the existing one.

Now, they can be distinguished from each other very easily as follows

Function

The base form of a block that only depends on parameter(s) and the other same level members. The term of function in the world of programming is general and comprehensive meaning, sometimes in a particular language it has a specific meaning. Java was originally intended for object-oriented programming, although it has finally adopted functional programming. So, I think the right definition for a function in the context of Java is the static one which is defined using the static keyword.

class A {
    private final NON_STATIC_RESOURCE = 'will never be touched'

    static void sayHello(def yourName) {    // (1)
        new Inner()                         // (2)
        new Callable() {                    // (3)
            @Override
            void to(String name) { }
        }
        println "Hello ${ yourName }!"      // (4)
    }

    class Inner { }

    interface Callable {
        void to(String name)
    }
}

def result = A.sayHello('World')
println result                              // (5)

Untitled 14.png

  1. A function which is defined using static keyword.
  2. An error occurs, because this function attempts to access non-static members. This is the meaning of my statement about "a block that only depends on parameter(s) and the other same level members".
  3. But, why this one can be accessed?
    Because as a member inner-interface is always static implicitly.
    Because of this rule, we can perform functional programming easily as follows,
    class A {
        ...
    
        static def greeting(String salaam) {
            new Callable() {
                @Override
                void to(String name) {
                    println "$salaam $name!"
                }
            }
        }
    }
    
    def sayHello = A.greeting('Hello')
    def sayHi = A.greeting('Hi')
    sayHello.to 'World'
    sayHello.to 'Groovy'
    sayHi.to 'Murez'
    
    Untitled 15.png

  4. As I explained, a function depends on parameter(s) and the other static members only. If you try to do the following,
    println "Hello ${ NON_STATIC_RESOURCE }!"
    an error will occur.
  5. The result is null, why?
    Again, this is the meaning of my statement about "a block always produce any output, at least null or even you somehow define it which does not intend to return any value at all".
    Here the sayHello method with a void type is executed, then a null value will be given.

Method

It is the same as a function but can access non-static members. This is why a method is always represented as a behavior on an object.

In the JVM-based context, a function will never access a field (or sometimes as an attribute or property) which is a non-static member. So, it is ridiculous to create an object first and then execute a function. This is why we only have to do A.sayHello('World') in order to execute a function as in the previous example.

While in the object-oriented programming context, a method in any class always depends on the fields to perform such a particular process. So in order to execute a method, we must create the object first.

class B {
    private String salaam

    void say(String name) {
        println "$salaam $name!"        // (1)
    }
}

def withHello = new B(salaam: 'Hello')
def withHi = new B(salaam: 'Hi')
withHello.say 'World'                   // (2)
withHi.say 'Murez'

Untitled 16.png

  1. Now a method can access any non-static member, of course by eliminating the static keyword.
  2. The say method must be invoked by first creating an object of enclosing class.

Eventually why do you have to define a method while there are no dependencies with any property? Then you have to redefine it into a function.

Sometimes, if referring to any name is ambiguous, we can use this keyword explicitly that corresponds to the enclosing class where it is used.

class Outer {
    class Inner {
        private String desc

        void set(String desc) {
            this.desc = desc    // (1)
        }
    }

    void get() { this.desc }    // (2)
}
  1. The name of desc is ambiguous between field or argument. So, this used here corresponds to the Inner class and then this.desc is a property.
  2. The this used here corresponds to the Outer class while the field of desc never exists in the Outer class finally this statement will cause an error

Of course this cannot be used in a static context, because this only refers to any non-static member.

Closure

This is the most powerful one, in addition to parameter(s) it also depends on the undefined members which can be easily resolved using delegation, as long as the delegated object defines the missing members correctly.

In Java, any interface that has only one abstract method can be applied as a lambda expression which is an anonymous method, while closure is a lambda expression plus delegate capability.

Closure is an abstract class, but we can create it in the same way as easy as lambda expression. This is the simplest closure, { } that returns a null. Delegation can be done by simply invoking the setDelegate method as follows,

class Any {
    private myName

    String getName() { myName }         // (1)
}

def say = {
    println "$it ${ name }!"            // (2)
}

say.delegate = new Any(myName: 'World') // (3)
say 'Hello'                             // (4)
say.delegate = new Any(myName: 'Murez')
say 'Hi'

Untitled 17.png

  1. This class defines a member which is a method named getName.

  2. Create a closure that will print an argument as it and then join with the other undefined member. If we immediately execute this closure, then an error will occur because the name is completely undefined.

  3. We delegate an object to this closure by passing it a new instance of the Any class and then give a string to the constructor. This technique is called Named argument constructor one of the features provided by Groovy and we'll not discuss it in this tutorial.

  4. There is no error. Because we have delegated an object that has defined a name() method.

    But instead of defining the getName() method, should it define the name() one?

    Here myName is a property of Any and its getter is getName. Then in Groovy, the getter can be invoked in two ways, as .getName() or just .name.

    Even so about the setter, such as a property of the delegate at the closure which can be invoked as .setDelegate(object) or .delegate = object as well.

Closure has a generic which is an abstract type of a value that will be returned by executing a closure. It can be executed like a function in general, but actually the call method is implicitly invoked. Sometimes we have to call him explicitly to avoid ambiguity, like the following example

class C {
    static def isPositive = { it > 0 }

    static def isPositive(def number) {
        throw new UnsupportedOperationException()
    }

    static def test(def value) {
        isPositive(value)           // (1)
    }
}

println C.test(1)

  1. We will get an exceptions with statement like this. But by changing it to isPositive.call(value), then it is considered as a closure rather than a function.

Lexical Scope this and owner

If a function can't refer to any class member (except the static ones) or we can define it as does not have a lexical scope, then a method has a lexical scope of this which references to an enclosing class. Then closures other than having this also have a lexical scope of owner and delegate.

If this corresponds to an enclosing class, then owner corresponds to the enclosing of either a class or a closure where it is defined. While delegate as we know, it corresponds to the delegated object, so we don't need to discuss about it in more detail.

How could it be?
Yes, Exactly! Because a closure can be defined in another closure while a function or method cannot.

class Outer {
    class Inner {
        def takeOwner() {
            ({ getOwner() })                                // (1)
        }

        Closure 'Take a Nested closure'() {
            ({                                              // (2)
                ({ owner })()                               // (3)
            })
        }
    }

    void test() {
        def inner = new Inner()                             // (4)
        assert inner.takeOwner()() == inner                 // (5)
        Closure nested = inner.'Take a Nested closure'()    // (6)
        assert nested() == nested                           // (7)
        assert ({ owner })() == this                        // (8)
        println 'Successful!'                               // (9)
    }
}

new Outer().test()

Untitled 18.png

  1. The method takeOwner returns a closure which returns an object of the owner.
    This equivalent to:
    def takeOwner() {
        return { getOwner() }
    }
    
    So, if you pass a closure as a return value without first storing it to a variable then you have to surround it with parentheses.
  2. The outer closure.
  3. The inner closure and immediately execute it. Here we can clearly see that a closure can be defined within another one like the top-level container, such as class or interface.
    This equivalent to:
    Closure 'Take a Nested closure'() {
        return {
            def innerClosure = { owner }
            return innerClosure.call()
        }
    }
    
  4. The instance of Inner class
  5. As we can see, the stand-alone closure or in other words a closure that is not inside another one, the owner's characteristic will be the same as this, which is returning an instance of enclosing class, i.e. Inner.
    This equivalent to:
    def closure = inner.takeOwner()
    assert closure() == inner
    
  6. Execute the string named method and an instance of the outer closure.
  7. Execute the outer closure which will immediately execute the inner one which finally returns an object of the owner. Because owner is executed in the context of an inner closure then it returns an instance of enclosing closure which is the outer one.
  8. This closure stands alone in the Outer class context, it will automatically become this and return an instance of Outer class.
  9. Because all assertions in each condition are correct, "Successful!" will be printed.

Finally

I will express my gratitude in a different way,
Untitled 19.png


Curriculum

Proof of Work Done

https://github.com/murez-nst/JVM-Based/tree/master/Groovy/Lombok

Reference
Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thank you for your contribution.

  • The name of the classes isn't perceptible. Class A or B doesn't look good.

Looking forward to your upcoming tutorials.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thank you for your review, @portugalcoin!

So far this week you've reviewed 8 contributions. Keep up the good work!

Hey @murez-nst
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Hi @murez-nst! We are @steem-ua, a new Steem dApp, computing UserAuthority for all accounts on Steem. We are currently in test modus upvoting quality Utopian-io contributions! Nice work!

Congratulations @murez-nst! You received a personal award!

1 Year on Steemit

Click here to view your Board of Honor

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @murez-nst! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!