Skip to content

Commit

Permalink
Merge pull request #10 from ba-st/dynamic_binding
Browse files Browse the repository at this point in the history
Dynamic Bindings
  • Loading branch information
gcotelli authored Mar 19, 2017
2 parents a111af2 + fd17906 commit 4d40afe
Show file tree
Hide file tree
Showing 111 changed files with 999 additions and 78 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ This library is aimed at providing a simpler way to enforce and check assertions

This library provides basic arithmetic abstractions like Percentages. See the [related documentation.](docs/Math.md)

### Bindings and Optionals

This library provides support to express optional values and required values, that can be unknown at the beginning of an execution. See the [related documentation.](docs/BindingsAndOptionals.md)

## Contributing

If you want to help check the [contribution guidelines.](CONTRIBUTING.md)
Expand Down
71 changes: 71 additions & 0 deletions docs/BindingsAndOptionals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Bindings

A binding is useful for describing situations when there's a need for a required value that can be missing at the beginning. As this is a required value, the contract is to ask for it, and in case it is still missing we will raise an exception.

```smalltalk
| definedBinding undefinedBinding |
definedBinding := Binding to: 1.
definedBinding content. "1"
undefinedBinding := Binding undefinedExplainedBy: 'Please set the default count.'.
undefinedBinding content "Raises and exception"
```

## Optionals

An optional is useful for describing situations when we have an object that can either be present or not. In some ways it's similar to the Maybe monad, but it does not pretend to be a monad.

So let's say we have a user interface where we want to show the details of some file the user has to upload. At the beginning there is no file so we can start with an unused optional:

```smalltalk
fileOptional := Optional unused.
```

and we can have the following rendering code:

```smalltalk
fileOptional withContentDo: [:file | self renderDetailsOf: file]
```

The first time we render our page we don't have a file, and so we won't render anything. Now we can let the user upload a file and change the optional:

```smalltalk
fileOptional := Optional containing: self uploadFile
```

and now the rendering code will take care of rendering the file details.

This is the simplest use, now suppose we want to take some action in case the file is not yet uploaded. We can change the rendering code to:

```smalltalk
fileOptional
withContentDo: [ :file | self renderDetailsOf: file ]
ifUnused: [ self renderUploadInstructions ]
```

### Combinations

We can easily combine two optionals:

```smalltalk
fileOptional
with: fileExtensionOptional
return: [:fileName : fileExtension |
'<1s>.<2s>' expandMacrosWith: fileName with: fileExtension ]
```
This will produce a new optional that will have the concatenation as its content, or an unused one in case some part is missing.

If we have a list of optionals it's possible to combine them to get a new optional as a result. So suppose we have a list of possible numbers and we want to get the sum only if all are available. We can do that by sending the following message:

```smalltalk
Optional
withAll: numberOptionals
return: [:addends | addends sum ]
```

So we will get a new optional that contains the sum in case all the possible numbers are available, or an unused optional in case some number is not available. In that case we are performing the computation over a collection of the values. It can be expressed for each step, with an injection:

```smalltalk
Optional
forEvery: numberOptionals
injectInto: [:sum :addend | sum + addend ]
```

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
tests
testTo

| binding |

binding := Binding to: 1.

self assert: binding content equals: 1

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
tests
testUndefinedExplainedBy

| binding explanation messageObtained |

explanation := 'Parameter not yet configured'.
binding := Binding undefinedExplainedBy: explanation.

self
should: [ binding content ]
raise: AssertionFailed
withExceptionDo: [ :error | messageObtained := error messageText ].

self assert: messageObtained equals: explanation
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
tests
testUndefinedExplainedByAllRaising

| binding explanations messageObtained |

explanations := {'Parameter not yet configured' . 'Parameter seems wrong'}.
binding := Binding undefinedExplainedByAll: explanations raising: InstanceCreationFailed.

self
should: [ binding content ]
raise: InstanceCreationFailed
withExceptionDo: [ :error | messageObtained := error messageText ].

self
assert: messageObtained
equals: 'Parameter not yet configured. Parameter seems wrong'
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
tests
testUndefinedExplainedByRaising

| binding explanation messageObtained |

explanation := 'Parameter not yet configured'.
binding := Binding undefinedExplainedBy: explanation raising: InstanceCreationFailed.

self
should: [ binding content ]
raise: InstanceCreationFailed
withExceptionDo: [ :error | messageObtained := error messageText ].

self assert: messageObtained equals: explanation
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"instance" : {
"testBoundObjectWhenBound" : "GabrielOmarCotelli 3/15/2017 15:35",
"testUnbound" : "GabrielOmarCotelli 3/15/2017 15:35"
"testUndefinedExplainedByAllRaising" : "MaximilianoTabacman 3/18/2017 16:32",
"testUndefinedExplainedBy" : "MaximilianoTabacman 3/18/2017 16:28",
"testUndefinedExplainedByRaising" : "MaximilianoTabacman 3/18/2017 16:30",
"testTo" : "MaximilianoTabacman 3/18/2017 16:28"
},
"class" : { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An OptionalTest is a test class for testing the behavior of Optional
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tests-Printing
testPrintingUnusedOptional

self assert: (Optional unusedBecause: 'This feature is disabled.') printString equals: 'This feature is disabled.'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tests-Printing
testPrintingUsedOptional

self assert: (Optional containing: 1) printString equals: '1'
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
tests-Combining
testWhenAllUsedInInjectInto

| firstOptional secondOptional thirdOptional combinedOptional |

firstOptional := Optional containing: 1.
secondOptional := Optional containing: 0.
thirdOptional := Optional containing: 3.

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
injectInto: [ :min :current | min min: current ].

combinedOptional withContentDo: [ :min | self assert: min equals: 0 ] ifUnused: [ self fail ].

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
injectInto: [ :sum :current | sum + current ].

combinedOptional withContentDo: [ :sum | self assert: sum equals: 4 ] ifUnused: [ self fail ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
tests-Combining
testWhenAllUsedInInjectIntoWhenEmpty

| combinedOptional |

combinedOptional := Optional whenAllUsedIn: #() injectInto: [ :min :current | self fail ].

self assert: (combinedOptional withContentDo: [ :x | self fail ] ifUnused: [ 0 ]) isZero
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
tests-Combining
testWhenAllUsedInInjectIntoWhenFirstUnused

| firstOptional secondOptional thirdOptional combinedOptional |

firstOptional := Optional unused.
secondOptional := Optional containing: 1.
thirdOptional := Optional containing: 3.

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
injectInto: [ :min :current | min min: current ].

self assert: (combinedOptional withContentDo: [ :min | self fail ] ifUnused: [ 0 ]) isZero
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
tests-Combining
testWhenAllUsedInInjectIntoWhenSomeUnused

| firstOptional secondOptional thirdOptional combinedOptional |

firstOptional := Optional containing: 1.
secondOptional := Optional unused.
thirdOptional := Optional containing: 3.

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
injectInto: [ :min :current | min min: current ].

self assert: (combinedOptional withContentDo: [ :min | self fail ] ifUnused: [ 0 ]) isZero
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
tests-Combining
testWhenAllUsedInReturn

| firstOptional secondOptional thirdOptional combinedOptional result |

firstOptional := Optional containing: 0.
secondOptional := Optional containing: 2.
thirdOptional := Optional containing: 3.

combinedOptional := firstOptional
whenAllUsedIn:
{secondOptional.
thirdOptional}
return: [ :addends | addends sum ].

result := 0.
combinedOptional withContentDo: [ :sum | result := sum ] ifUnused: [ self fail ].
self assert: result equals: 5.

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
return: [ :addends | addends sum ].

result := 0.
combinedOptional withContentDo: [ :sum | result := sum ] ifUnused: [ self fail ].
self assert: result equals: 5
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
tests-Combining
testWhenAllUsedInReturnWhenEmpty

| combinedOptional |

combinedOptional := Optional whenAllUsedIn: #() return: [ :addends | addends sum ].

combinedOptional withContentDo: [ :sum | self fail ] ifUnused: [ :explanations | self assert: explanations isEmpty ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
tests-Combining
testWhenAllUsedInReturnWhenFirstIsUnused

| firstOptional secondOptional thirdOptional combinedOptional |

firstOptional := Optional unusedBecause: 'This is expected.'.
secondOptional := Optional unusedBecause: 'This is also expected.'.
thirdOptional := Optional containing: 3.

combinedOptional := firstOptional
whenAllUsedIn:
{secondOptional.
thirdOptional}
return: [ :addends | addends sum ].

combinedOptional withContentDo: [ :sum | self fail ] ifUnused: [ :explanations | self assert: explanations equals: #('This is expected.' 'This is also expected.') ].

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
return: [ :addends | addends sum ].

combinedOptional withContentDo: [ :sum | self fail ] ifUnused: [ :explanations | self assert: explanations equals: #('This is expected.' 'This is also expected.') ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
tests-Combining
testWhenAllUsedInReturnWhenSomeAreUnused

| firstOptional secondOptional thirdOptional combinedOptional |

firstOptional := Optional containing: 0.
secondOptional := Optional unused.
thirdOptional := Optional containing: 3.

combinedOptional := firstOptional
whenAllUsedIn:
{secondOptional.
thirdOptional}
return: [ :addends | addends sum ].

combinedOptional withContentDo: [ :sum | self fail ] ifUnused: [ :explanations | self assert: explanations isEmpty ].

combinedOptional := Optional
whenAllUsedIn:
{firstOptional.
secondOptional.
thirdOptional}
return: [ :addends | addends sum ].

combinedOptional withContentDo: [ :sum | self fail ] ifUnused: [ :explanations | self assert: explanations isEmpty ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
tests-Accessing
testWithContentDo

| optional expectedContent content |

expectedContent := 2.
content := 1.

optional := Optional containing: expectedContent.

optional withContentDo: [ :theContent | content := theContent ].

self assert: content equals: expectedContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
tests-Accessing
testWithContentDoIfUnused

| optional expectedContent content |

expectedContent := 2.

optional := Optional containing: expectedContent.

content := optional withContentDo: [ :theContent | theContent ] ifUnused: [ self fail ].

self assert: content equals: expectedContent
Loading

0 comments on commit 4d40afe

Please sign in to comment.