# Encapsulation (& Laziness) ## (A Practical Example) Hey guys! I am Sohang Chopra, a programming language enthusiast. I have dabbled in a variety of languages such as [C/C++](https://en.wikipedia.org/wiki/C%2B%2B), [Java](https://en.wikipedia.org/wiki/Java_(programming_language)), [Python](https://en.wikipedia.org/wiki/Python_(programming_language)), [JavaScript](https://en.wikipedia.org/wiki/JavaScript) and also some niche languages such as [Haskell](https://en.wikipedia.org/wiki/Haskell_(programming_language)) and [Racket](https://en.wikipedia.org/wiki/Racket_(programming_language)) (a [Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)) dialect). Today, I am going to talk about **Encapsulation**, a fundamental concept for creating better, modular code. Unlike the boring introduction in school / college textbooks, I am going to *show, not tell* as they say in writing circles. I'll be using **Java** here, but the principle itself can be applied in any programming language of your choice. **Note**: The final version of the code (Matrix Transpose) is [here](#final-code), if you are impatient. ### Transpose (The Normal Way) ```java import java.util.Scanner; public class Transpose { // The main algorithm private static int[][] transpose(int rows, int cols, int[][] matrix) { int[][] ans = new int[cols][rows]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { ans[j][i] = ans[i][j]; } } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Enter rows "); int rows = scanner.nextInt(); System.out.println("Enter cols "); int cols = scanner.nextInt(); int[][] matrix = new int[rows][cols]; // Input Matrix for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { System.out.printf("Enter matrix[%d][%d] ", i, j); matrix[i][j] = scanner.nextInt(); } } // Function Call int[][] transposed = transpose(rows, cols, matrix); // Output Matrix for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { System.out.print(transposed[i][j] + " "); } System.out.println(); } } } ``` Nothing earth shattering here, just the basic Matrix Transpose function which you should remember from your Programming class at college. If you have forgotten, here's a quick refresher: ``` // A: 2 x 3 Matrix A = [[1, 2, 3], [4, 5, 6]] // A' = transpose(A): 3 x 2 Matrix A' = [[1, 4], [2, 5], [3, 6]] ``` In general, the transpose of a `m x n` matrix `A` is a `n x m` matrix `A'` in which matrix A is "flipped" by ninety degrees. ### Transpose (The Class Version) So, here we have a perfectly simple code for calculating Matrix Transpose. So what shall we do with it? I know! Let's make a longer version by using a `Matrix` class! Yay! ```java public class Transpose { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Enter rows "); int rows = scanner.nextInt(); System.out.println("Enter cols "); int cols = scanner.nextInt(); Matrix matrix = new Matrix(rows, cols); // Input Matrix for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { System.out.printf("Enter matrix[%d][%d] ", i, j); matrix.set(i, j, scanner.nextInt()); } } // Function Call Matrix transposed = matrix.transpose(); // Output Matrix for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { System.out.print(transposed.get(i, j) + " "); } System.out.println(); } } } ``` This is just the same code as before but instead of using a 2d array directly, we have used an instance of the class `Matrix`. Now let's define the `Matrix` class used in the code: ```java class Matrix { private int rows, cols; private int[][] matrix; public Matrix(int matrixRows, int matrixCols) { rows = matrixRows; cols = matrixCols; matrix = new int[rows][cols]; } public int get(int r, int c) { return matrix[r][c]; } public int set(int r, int c, int item) { matrix[r][c] = item; } public Matrix transpose() { Matrix ans = new Matrix(cols, rows); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { ans.set(i, j, get(i, j)); } } return ans; } } ``` This is a simple class, with `get` and `set` accessor methods for getting and setting matrix values, and `transpose` static method for transposing a matrix. ### Transpose (The Lazy Version) The `transpose` function defined above has complexity `O(rows * cols)`. Can we make it faster? Well, if you think about it really hard, you'll realize that the transpose function doesn't *actually* have to create a new matrix - instead, we can create a lazy version by simply returning a read only `view` to a transposed matrix (similar to a`VIEW` in `SQL`). Let's define a `TransposeMatrixView` class for the purpose: ```java class TransposeMatrixView { private Matrix matrix; public TransposeMatrixView(Matrix m) { matrix = m; } public int get(int r, int c) { return matrix.get(c, r); } } ``` `TransposeMatrixView` class contains a field `matrix` of type `Matrix`. It has a `get` method that simply calls `get` method of `Matrix` class with its arguments reversed: `matrix.get(c, r)`. Note that we didn't define a `set` method because this is a read only view of the original matrix. Now we can change the definition of `Matrix::transpose` as follows: ```java public TransposeMatrixView transpose() { return new TransposeMatrixView(this); } ``` Notice that there are no loops anywhere in this new version, because we never actually calculate the transpose - instead we return an object of class `TransposeMatrixView` that also has a `get` method to access the elements. That means that we converted a `O(rows * cols)` function to an `O(1)` function (`O(1)` means it takes constant time to compute, irrespective of input matrix size). Isn't that amazing! The only change in the code calling `transpose` in `main` is the function's new return type: ```java TransposeMatrixView transposed = matrix.transpose(); ``` In Java 10, we can make this code a little less wordy by declaring `transposed` using `var` keyword: ```java var transposed = matrix.transpose(); ``` The `var` keyword in Java 10 allows you to declare and initialize a variable without having to specify its type - the type is inferred by the Java compiler. So what does all this have to do with encapsulation? Well, encapsulation means hiding unnecessary data and packaging it in a single entity with methods to work on the data. Here, the actual matrix data is hidden from the code using it - to access the data in `main`, we have to use the accessor methods `get` and `set` (or just `get` in the case of `TransposeMatrixView` class). The user code calling `transpose` method doesn't care how it is implemented - we can just call `get` on the transposed matrix. Another thing to learn from this example is the idea of **Laziness**. It is a powerful idea that is not used often enough - that when a function is called, it doesn't need to calculate its result immediately, it can just *fake it* and calculate the result when it is needed. If this idea makes you interested, you should check out [Haskell](https://en.wikipedia.org/wiki/Haskell_(programming_language)) - a *pure functional programming language* that has laziness built into it. (Yes, I know that's a lot of fancy words but they are not that complicated once you understand the basic ideas.) Be warned though, it requires a completely different mental model than traditional Object Oriented languages like Java, and will probably feel like learning programming from scratch all over again. But the end result is definitely worth it. I wrote this article because I feel that textbooks usually just define these concepts and then move on. So I decided to write an article showing why encapsulation is a good idea. Please tell me how you felt about this article. ### Final Code ```java // Java 10 (using var keyword) // Matrix Transpose (Lazy) import java.util.Scanner; public class Transpose { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Enter rows "); int rows = scanner.nextInt(); System.out.println("Enter cols "); int cols = scanner.nextInt(); Matrix matrix = new Matrix(rows, cols); // Input Matrix for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { System.out.printf("Enter matrix[%d][%d] ", i, j); matrix.set(i, j, scanner.nextInt()); } } // Function Call var transposed = matrix.transpose(); // Output Matrix for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { System.out.print(transposed.get(i, j) + " "); } System.out.println(); } } } class Matrix { private int rows, cols; private int[][] matrix; public Matrix(int matrixRows, int matrixCols) { rows = matrixRows; cols = matrixCols; matrix = new int[rows][cols]; } public int get(int r, int c) { return matrix[r][c]; } public int set(int r, int c, int item) { matrix[r][c] = item; } public TransposeMatrixView transpose() { return new TransposeMatrixView(this); } } class TransposeMatrixView { private Matrix matrix; public TransposeMatrixView(Matrix m) { matrix = m; } public int get(int r, int c) { return matrix.get(c, r); } } ```