79 lines
7.4 KiB
TeX
79 lines
7.4 KiB
TeX
%!TEX root = ../../main.tex
|
|
|
|
\chapter{Vorteile in der Praxis}
|
|
Generell inferiert \ac{Java-TX} immer den generellsten Typ oder Prinzipaltyp. Für eine formale Definition des Prinzipaltyps in \ac{Java-TX} wird an dieser Stelle auf \cite{plumicke_java-tx_2022}[Abschnitt 5] verwiesen. Für Funktionstypen gilt dabei, dass der Definitionsbereich, also die Parameter maximal (möglichst generell) und der Wertebereich, also der Rückgabewert minimal (möglichst speziell) sein muss \cite{plumicke_java-tx_2022}[Abschnitt 2]. Durch den Typinferenzalgorithmus kann daher daher ein generellerer Typ inferiert werden, als ein Programmierer angegeben hätte. Dadurch wird der Definitionsbereich der Funktion größer und die Wiederverwendbarkeit des Codes steigt potenziell. Zur Verdeutlichung ist in \autoref{lst:prinzipal_type_example} ein reales Beispiel aus dem Code des \JTXC{} gegeben\footnote{Die Imports der Klasse wurden bei den Beispielen in diesem Abschnitt bewusst weggelassen, um den Quellcode kompakt zu halten. Es gilt aber natürlich, dass alle verwendeten Typen importiert werden müssen (vgl. \autoref{sec:java_tx_typeinference})}. Die Aufgabe der Klasse ist in diesem Fall nicht wichtig, daher wurde der Quellcode in diesem Beispiel auf die relevante Methode reduziert. Wie man sehen kann, erwartet die Methode als Parameter eine Liste des Typs \texttt{GenericRefType}, über welche dann mittels For-Each Schleife iteriert wird, wobei die einzelnen Elemente genutzt werden, um eine neue Liste zu erstellen. Die Typisierung des Parameters ist dabei keineswegs ungewöhnlich und wäre vermutlich von vielen Programmieren so oder so ähnlich gewählt worden. Aber handelt es sich bei der Signatur hierbei um die optimale/generellste Typisierung?
|
|
|
|
\begin{lstlisting}[language=Java, caption={Beispielklasse aus dem \JTXC{}}, label={lst:prinzipal_type_example}]
|
|
public class FunNClass extends ClassOrInterface {
|
|
private static GenericDeclarationList createGenerics(List<GenericRefType> funNParams) {
|
|
List<GenericTypeVar> generics = new ArrayList<>();
|
|
for (GenericRefType param : funNParams) {
|
|
generics.add(new GenericTypeVar(param.getParsedName(),
|
|
new ArrayList<>(), new NullToken(), new NullToken()));
|
|
}
|
|
return new GenericDeclarationList(generics, new NullToken());
|
|
}
|
|
...
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Sehen wir und dazu das Beispiel in \autoref{lst:prinzipal_type_example_javatx} an.
|
|
|
|
\begin{lstlisting}[language=java, caption={\autoref{lst:prinzipal_type_example} ohne Typinformationen}, label={lst:prinzipal_type_example_javatx}]
|
|
public class FunNClass extends ClassOrInterface {
|
|
private static createGenerics(funNParams) {
|
|
var generics = new ArrayList<>();
|
|
for (param : funNParams) {
|
|
generics.add(new GenericTypeVar(param.getParsedName(),
|
|
new ArrayList<>(), new NullToken(), new NullToken()));
|
|
}
|
|
return new GenericDeclarationList(generics, new NullToken());
|
|
}
|
|
...
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Hier wurden alle inferierbaren Typen entfernt. Relevant ist hier vor allem der Rückgabetyp und der Typ des Parameters der Methode. Außerdem muss der generische Typ der Liste \lstinline|generics| nicht mehr explizit angegeben werden und im Kopf der For-Each Schleife wurde der Typ der Variablen \texttt{param} entfernt\footnote{Dies wäre auch in Java mit dem \texttt{var} Platzhalter möglich}. In \autoref{lst:prinzipal_type_example_javatx_infered} sind die vom \JTXC{} inferierten Typen für diese Klasse zu sehen. Interessant ist vor allem der Typ des Parameters \lstinline|funNParams| der Methode \lstinline|createGenerics|. Dieser wurde im Vergleich zu \autoref{lst:prinzipal_type_example} von \texttt{List<GenericRefType>} zu \lstinline|Iterable<? extends GenericRefType>|. Wie man in \autoref{fig:java_21_collections} sehen kann, ist \lstinline|Iterable| ein Interface, dass von \lstinline|List| erweitert wird und somit ein Supertyp von List.
|
|
|
|
\begin{figure}[h!]
|
|
\centering
|
|
\begin{tikzpicture}
|
|
\tikzstyle{rect} = [rectangle, draw, text width=13cm, align=center, minimum height=1.5cm, inner sep=3pt]
|
|
\tikzstyle{implements} = [->, dashed, >=open triangle 60]
|
|
|
|
\node[rect] (iterable) {java.lang.Iterable};
|
|
\node[rect, below=of iterable] (collection) {java.util.Collection};
|
|
\node[rect, below=of collection] (sequencedcollection) {java.util.SequencedCollection};
|
|
\node[rect, below=of sequencedcollection] (list) {java.util.List};
|
|
|
|
\draw[implements] (collection) -- (iterable);
|
|
\draw[implements] (sequencedcollection) -- (collection);
|
|
\draw[implements] (list) -- (sequencedcollection);
|
|
|
|
\end{tikzpicture}
|
|
\caption{Vererbungshierarchie von java.util.List ab Java 21 \cite{noauthor_java_nodate-2}}
|
|
\label{fig:java_21_collections}
|
|
\end{figure}
|
|
|
|
Die Wildcard \lstinline|? extends| ermöglicht Kovarianz und somit auch Subtypen von \lstinline|GenericRefType| als generischen Typparameter. Mit dem neuen Typ ist der Definitionsbereich der Methode somit offensichtlich größer als zuvor und der Typ damit genereller. Aber ist dieser Typ in diesem Kontext auch korrekt?
|
|
\newpage
|
|
\begin{lstlisting}[language=java, caption={Inferierte Typen für \autoref{lst:prinzipal_type_example_javatx}}, label={lst:prinzipal_type_example_javatx_infered}]
|
|
public class FunNClass extends ClassOrInterface {
|
|
private static GenericDeclarationList createGenerics(Iterable<? extends GenericRefType>funNParams) {
|
|
var generics = new ArrayList<GenericTypeVar>();
|
|
for (GenericRefType param : funNParams) {
|
|
generics.add(new GenericTypeVar(param.getParsedName(),
|
|
new ArrayList<>(), new NullToken(), new NullToken()));
|
|
}
|
|
return new GenericDeclarationList(generics, new NullToken());
|
|
}
|
|
...
|
|
\end{lstlisting}
|
|
|
|
Zunächst einmal wird in der Funktion die Liste lediglich durchlaufen. Es werden also keine speziellen Methoden des Interface \texttt{List} verwendet. Die For-Each Schleife ist auf alle Typen anwendbar, die das Iterable Interface implementieren.
|
|
Iterable ist also der allgemeinste Typ, auf den eine For-Each Schleife angewandt werden kann \cite{noauthor_for-each_nodate}.
|
|
Da innerhalb der Methode nur lesend auf \lstinline|funNParams| zugegriffen wird, ist auch die Kovarianz des Typs unproblematisch und korrekt \cite{naftalin_java_2007}[S.19 ff]. Der Typ ist also ein korrekter Typ für den Parameter \lstinline|funNParams|. Die meisten Java Programmierer hätten diesen Typen vermutlich nicht gefunden. Dies zeigt die Stärke des Typinferenzalgorithmus von \ac{Java-TX}. Dass der Rückgabewert bereits der speziellste Typ ist, ist leicht zu sehen, da in der letzten Zeile explizit ein Objekt des Typs \lstinline|GenericDeclarationList| zurückgegeben wird.
|
|
Alle weiteren Typen, die inferiert wurden, entsprechen den Typen in \autoref{lst:prinzipal_type_example} und sind somit ebenfalls korrekt.
|
|
|
|
Grundsätzlich wird der Code durch das entfernen von Typen kompakter. Es lässt sich allerdings darüber streiten, ob der Code durch das weglassen der Typinformationen auch lesbarer wird. Es kann sicherlich auch von Vorteil sein die Parameter- und Rückgabetypen einer Methode direkt zu sehen. Hier wäre das entwickelte Eclipse Plugin ein guter Kompromiss, welches den gewünschten Typ direkt in den Quellcode einfügen kann \cite{stadelmeier_java_2015}. Dies ist vor allem auch deshalb sinnvoll weil es mehrere korrekte Typen geben kann \cite{plumicke_java-tx_2022}[Abschnitt 3.1]. In diesem Fall könnte der Entwickler den gewünschten Typen auswählen.
|