"No inheritance extensions, part 1th" mainly discusses the mechanism for adding new methods to existing classes in Goovy, Scala, and Clojure, which is one of the ways in which the Java next-generation language implements no inheritance extensions. This article explores how Clojure protocols can expand Java extensions in an innovative way and provide an excellent solution for expression problems.
While this article focuses on scalability, it also slightly involves some Clojure features that allow Clojure and Java code to interoperate seamlessly. The two languages are fundamentally different (Java is imperative, object-oriented, while Clojure is functional), but Clojure implements some handy features that enable Clojure to handle the Java architecture with minimal friction.
Review of Clojure Protocol
Protocol is an important part of Clojure ecosystem. The previous article showed how to add a method to an existing class using a protocol. Protocols can also help Clojure to simulate many of the familiar features of object-oriented languages. For example, Clojure can simulate a combination of object-oriented classes-data and methods by using protocols to bind records to functions. In order to understand the interaction between protocols and records, mapping must first be introduced, which is the core data structure that is the basis for recording in Clojure.
Mapping and recording
In Clojure, a map is a set of name-value pairs (concepts common in other languages). For example, the first step in the read-evaluate-print loop (REPL) in Listing 1 is to create a mapping that contains information about the Clojure programming language:
Listing 1. Interacting with Clojure mappings
User=> (Def language {: Name "Clojure":d esigner "Hickey"})
# ' User/language
user=> (get Language:name)
"Clojure"
user=> (: Name language)
"Clojure"
user=> (:d esigner language)
"Hickey"
Clojure uses maps extensively, so it contains special syntactic sugars that simplify interaction with mappings. You can use familiar (get) functions to retrieve values related to a key. However, Clojure will simplify such common operations as much as possible.
This column more highlights: http://www.bianceng.cn/Programming/Java/
In the Java environment, the source code for a language is not a native data structure, and it must be parsed and transformed. In Clojure (and other Lisp variants), the source code represents a native data structure, such as a list, which helps explain the strange syntax in the language. When the LISP interpreter reads the list as a source code, it tries to interpret the first element of the list as some callable element, such as a function. So in Listing 1, the (: name language) expression returns the same result as the (get language:name) expression. Clojure provides this syntactic sugar because retrieving items from a map is a common operation.
In addition, in Clojure, some structures can be placed in function call slots, which extend the ability to invoke these structures like calling functions. Java programs can only invoke methods and built-in language statements. Listing 1 shows that a mapping key, such as (: Name language), can be invoked as a function in Clojure. The mappings themselves are also callable, and you can use this alternative syntax if you think the alternative syntax (LANGUAGE:NAME) is easier to read. Clojure Rich, callable graphs make this language easier to use, reducing repetitive syntax (such as the common get and set in Java programs).
However, mapping does not completely simulate JVM classes. Clojure provides other ways to help you model problems, including data and behavior, and to integrate the underlying JVM more seamlessly. You can create multiple structures that correspond to similar underlying JVM classes and have different integrity, including types and records. You can use (Deftype) to create a type, which is typically used to model the mechanical structure. For example, if you need a data type to hold XML, it is quite possible to use (Deftype myxmlstructure) to represent the data extraction structure embedded in the XML. In Clojure, it is customary to use records to get data, and information logging is the core of the application. To support this usage, Clojure will automatically include a large number of interfaces in the underlying record definition that includes features such as callable. The REPL interaction in Listing 2 demonstrates the underlying class and superclass of the record:
Listing 2. Underlying classes and superclass of records
user=> (defrecord person [name Age postal])
user. Person
user=> (def bob (person.) Bob "60601)")
# ' User/bob
user=> (: name Bob)
' Bob '
user=> (class Bob)
user. Person
user=> (Supers (class Bob))
#{java.io.serializable clojure.lang.Counted java.lang.Object
Clojure.lang.IKeywordLookup clojure.lang.IPersistentMap
clojure.lang.Associative Clojure.lang.IMeta
Clojure.lang.IPersistentCollection java.util.Map
Clojure.lang.IRecord clojure.lang.IObj java.lang.Iterable
clojure.lang.Seqable Clojure.lang.ILookup}
In Listing 2, I created a new record named person that contains fields for name, age, and postal code. I can construct such a new record using the syntax sugar that Clojure calls against the constructor (using the class name plus a period as a function call). The return value is an instance with a namespace. (By default, all REPL interactions occur within the user namespace.) The callable rule still exists, so I can use the syntax candy shown in Listing 1 to access the members of the record.
When the (class) function is called, it returns the namespace and class name created by Clojure (which can interact with Java code). I can also use (supers) to access the person's super class. In the last four lines of Listing 2, Clojure implements several interfaces, including a scalable interface such as Ipersistentmap, which allows you to work with classes and objects using the native mapping syntax of Clojure. An automatically contained set of interfaces is an important difference between a record and a type, and the type does not contain any automatic interface implementations.