Skip to content

Commit 9b93e64

Browse files
committed
Improvements
1 parent 82d8443 commit 9b93e64

File tree

1 file changed

+9
-9
lines changed

1 file changed

+9
-9
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ First of all, let's explore how basic refinements work in LiquidJava. Open [Exam
7373

7474
As demonstrated previously, we can also refine method parameters and return values. Open [ExampleMethodRefinements.java](./src/main/java/com/tutorial/part1/ExampleMethodRefinements.java). Here, the method `divide` is refined to ensure that the parameter `b` is never zero. Try changing the value of the second argument in the `divide` method call to zero and observe the error reported by LiquidJava. Now change it back to a non-zero value.
7575

76-
But wait, we can also refine the return value of the method! Let's introduce a bug. Change the implementation of the `divide` method, and return `a - b` instead of `a / b`. No error! Let's fix this. On top of the method, add the following refinement to specify that the return value should be exactly equal to the division of `a` by `b`:
76+
But wait, we can also refine the return value of the method! Let's introduce a bug. Change the implementation of the `divide` method to return `a - b` instead of `a / b`. No error! Let's fix this. Above of the method signature, add the following refinement to specify that the return value should be exactly equal to the division of `a` by `b`:
7777

7878
```java
7979
@Refinement("_ == a / b")
@@ -83,11 +83,11 @@ Now we get an error! LiquidJava is telling us that the implementation of the met
8383

8484
#### Exercise
8585

86-
Now, open [ExampleBankRefinements.java](./src/main/java/com/tutorial/part1/exercise/ExampleBankRefinements.java). The `main` method simulates a wrong usage of the `deposit` and `withdraw` methods of a bank account, since it tries to withdraw more money than the current balance. Let's make use of LiquidJava refinements to ensure the correctness of these methods. Replace the `"true"` refinements with the appropriate ones to ensure the correct behavior of both methods. For example, we want to ensure that the `balance` and `amount` parameters of both methods are equal or greater than zero and greater than zero, respectively. Also, we want to ensure the correct implementation of both methods — they must return the updated balance after the deposit or withdrawal operations. This also tells the typechecker what the expected output is, allowing it to verify the correctness of the following operations.
86+
Now, open [ExampleBankRefinements.java](./src/main/java/com/tutorial/part1/exercise/ExampleBankRefinements.java). The `main` method simulates a wrong usage of the `deposit` and `withdraw` methods of a bank account, since it tries to withdraw more money than the current balance. Let's make use of LiquidJava refinements to ensure the correct usage of these methods. Replace the `"true"` refinements with the appropriate ones to ensure the correct behavior of both methods. For example, we want to ensure that the `balance` and `amount` parameters of both methods are equal or greater than zero and greater than zero, respectively. Also, we want to ensure the correct implementation of both methods — they must return the updated balance after the deposit or withdrawal operations. This also tells the typechecker what the expected output is, allowing it to verify the correctness of the following operations.
8787

88-
Now, with the correct refinements in place, LiquidJava will report an error in the `withdraw` method call in the `main` method, since it tries to withdraw more money than it was deposited. If we instead try to withdraw 10 or less, no error will be reported.
88+
With the correct refinements in place, LiquidJava will report an error in the `withdraw` method call in the `main` method, since it tries to withdraw more money than it was deposited. If we instead try to withdraw `10` or less, no error will be reported.
8989

90-
However, notice that we are repeating the same refinement twice in the `balance` parameter of both methods. For this, we can use a refinement alias to define commonly used refinements and avoid repetition. To do this, add the following lines of code to the top of the class:
90+
However, notice that we are repeating the same refinement twice in the `balance` parameter of both methods. For this, we can use a refinement aliases to define commonly used refinements and avoid repetition. Add the following lines of code above the class definition:
9191

9292
```java
9393
import liquidjava.specification.RefinementAlias;
@@ -101,7 +101,7 @@ With this alias, we can replace all occurrences of `@Refinement("_ >= 0")` with
101101

102102
Now, let's explore how to use **state refinements** to specify and verify properties about the state of an object. Open [ExampleStateRefinements.java](./src/main/java/com/tutorial/part2/ExampleStateRefinements.java). Here, we specify that this object can only be in two states: `a` or `b`. Then, in the constructor, we specify that the initial state is `a`, through the `@StateRefinement` annotation. This annotation allows us to specify in which state the object should be before the method is called (`from`), and in which state it will be after the method execution (`to`). In the constructor, since it's the first method to be called, we can only specify the `to` state.
103103

104-
This object has two methods, also called `a` and `b`. From the state refinements, we can see that the method `a` can only be called when the object is in state `a` transiting to state `b`. Similarly, the method `b` can only be called when the object is in state `b`, transiting to state `a`. This means that we cannot call the same method twice in a row, since it would violate the protocol established by the state refinements. Try uncommenting line 22 to see the error reported by LiquidJava.
104+
This object has two methods, also called `a` and `b`. From the state refinements, we can see that the method `a` can only be called when the object is in state `a` transiting to state `b`. Similarly, the method `b` can only be called when the object is in state `b`, transiting to state `a`. This means that we cannot call the same method twice in a row, since it would violate the protocol established by the state refinements. Try uncommenting line 22 to observe the error.
105105

106106
#### Exercise
107107

@@ -111,18 +111,18 @@ Open [File.java](./src/main/java/com/tutorial/part2/exercise/File.java). Your ta
111111

112112
To demonstrate the state refinements in a real world scenario, let's learn about **external refinements**. LiquidJava allows us to refine external classes, for example, from the Java standard library. For instance, we can refine the `java.net.Socket` class to ensure its correct usage. Open [SocketRefinements.java](./src/main/java/com/tutorial/part3/SocketRefinements.java). Here, we refine the `Socket` class through state refinements, with the possible states being `unconnected`, `binded`, `connected`, and `closed`. Then, for each method, we specify the allowed state transitions. This way, we can ensure that, for example, the `connect` method can only be called after the `bind` method, and that the `close` method can only be called once.
113113

114-
Now, open [ExampleSocketUsage.java](./src/main/java/com/tutorial/part3/ExampleSocketUsage.java). Here, we see a simple usage of the `Socket` class. If you comment the line 9 containing with the `bind` method call, LiquidJava will report an error in the `connect` method call, since it violates the state refinement specified for the `Socket` class! Notice that when using the `Socket` class, we don't need to add any annotations, since they are already specified in the external refinement class.
114+
Now, open [ExampleSocketUsage.java](./src/main/java/com/tutorial/part3/ExampleSocketUsage.java). Here, we see a simple usage of the `Socket` class. If you comment out the line 9 containing with the `bind` method call, LiquidJava will report an error in the `connect` method call, since it violates the state refinement specified for the `Socket` class! Notice that when using the `Socket` class, we don't need to deal with any LiquidJava annotations, since they are already specified in the external refinement interface.
115115

116116

117117
#### Exercise
118118

119-
Now, let's refine another external class. Open [MathRefinements.java](./src/main/java/com/tutorial/part3/exercise/MathRefinements.java). Your task is to replace the `"true"` refinements with the appropriate ones to refine the `java.lang.Math` class. We want to ensure that the `abs` method returns a non-negative value, the `max` method returns the bigger of the two arguments, and the `min` method returns the smaller of the two arguments. To check your implementation, uncomment the code in [MathRefinementsTest.java](./src/main/java/com/tutorial/part3/exercise/MathRefinementsTest.java), which should have no errors.
119+
Now, let's refine another external class. Open [MathRefinements.java](./src/main/java/com/tutorial/part3/exercise/MathRefinements.java). Your task is to replace the `"true"` refinements with the appropriate ones to refine the `java.lang.Math` class. We want to ensure that the `abs` method returns a non-negative value, the `max` method returns the larger of the two arguments, and the `min` method returns the smaller one. To check your implementation, uncomment the code in [MathRefinementsTest.java](./src/main/java/com/tutorial/part3/exercise/MathRefinementsTest.java), which should have no errors after you complete the refinements.
120120

121121
### 4. Ghost Variables
122122

123-
Finally, LiquidJava also offers a way to model objects using **ghost variables** through the `@Ghost` annotation, which are used to track additional information about the program state when states aren't enough. These can be, for instance, counters (integers) or flags (booleans), to model more complex protocols. To demonstrate this, open [ArrayListRefinements.java](./src/main/java/com/tutorial/part4/ArrayListRefinements.java). Here, we define the refinements for the `java.util.ArrayList` class, using a ghost variable `size` to keep track of the number of elements in the list. By combining the `size` ghost variable with state refinements, we can ensure that the `get` method can only be called with an index that is non-negative and less than the current size of the list, preventing out-of-bounds access!
123+
Finally, LiquidJava also offers a way to model objects using **ghost variables** through the `@Ghost` annotation, which are used to track additional information about the program's state when states aren't enough. These can be, for instance, counters (integers) or flags (booleans), to model more complex protocols. To demonstrate this, open [ArrayListRefinements.java](./src/main/java/com/tutorial/part4/ArrayListRefinements.java). Here, we define the refinements for the `java.util.ArrayList` class, using a ghost variable `size` to keep track of the number of elements in the list. Using the `size` ghost variable in state refinements, we can prevent out-of-bounds access.
124124

125-
In the constructor, we say that after it is called, the ghost variable `size` will be equal to `0`. This is optional since its default value is zero, but it helps us understand this example. Then, in the `add` method, we specify that it can be called in any state (since we don't specify a `from` state), and that after it is called, the `size` ghost variable will be incremented by one — the new size will be equal to the old size plus one (`old` is a special keyword that refers to the previous state of the object, so calling `size(old(this))` gets the value of `size` before the method was called). Finally, in the `get` method, we specify that the index parameter must be non-negative and less than the current size of the list.
125+
In the constructor, we specify that after it is called, the ghost variable `size` will be equal to `0`. This is optional since its default value is already zero, but it helps us understand this example. Then, in the `add` method, we specify that it can be called in any state (since we don't specify a `from` state), and that after it is called, the `size` ghost variable will be incremented by one — the new size will be equal to the old size plus one (`old` is a special keyword that refers to the previous state of the object, so calling `size(old(this))` gets the value of `size` before the method was called). Finally, in the `get` method, we specify that the index parameter must be non-negative and less than the current size of the list, therefore preventing out-of-bounds errors.
126126

127127
In [ExampleArrayListUsage.java](./src/main/java/com/tutorial/part4/ExampleArrayListUsage.java), we can see a simple usage of the refined `ArrayList` class. If you uncomment line 11, LiquidJava will report an error, since we are trying to access an index that is out of bounds!
128128

0 commit comments

Comments
 (0)