Skip to content

Commit

Permalink
ref(rx): rename proxy range iterator (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
plastikfan committed May 4, 2024
1 parent ff0f1ea commit 517fc82
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 13 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,10 @@ The following is an example of how to use the Range operator for scalar types:

___NumericRangeIterator___ is defined for all numeric types and is therefore able to use the native operators for calculation operations.

For struct types, the above is identical, but instead using ___ProxyRangeIterator___:
For struct types, the above is identical, but instead using ___RangeIteratorByProxy___:

```go
obs := rx.RangePF(&rx.ProxyRangeIterator[widget, int]{
obs := rx.RangePF(&rx.RangeIteratorByProxy[widget, int]{
StartAt: widget{id: 5},
By: widget{id: 1},
Whilst: rx.LessThanPF(widget{id: 8}),
Expand Down Expand Up @@ -230,12 +230,32 @@ func (w widget) Inc(index *widget, by widget) *widget {

This may look strange, but is necessary since the type ___T___ can not be defined with pointer receivers with respect to the ___Numeric___ constraint. The reason for this is to keep in line with the original rxgo functionality of being able to compose an observable with literal scalar values and we can't take the address of literal scalars that would be required in order to be able to define ___Inc___ as:

> func (w *widget) Inc(by widget) *widget
```go
func (w *widget) Inc(by widget) *widget
```

So ___Numeric___ receivers on ___T___ being of the non pointer variety is a strict invariant.

The aspect to focus on in ___widget.Inc___ is that ___index___ is incremented with the ___by___ value, not ___w.id___. Effectively, widget is passed a pointer to its original self as index, but w is the copy of index in which we're are running. For this to work properly, the original widget (index) must be incremented, not the copy (w), which would have no effect, resulting in an infinite loop owing to the exit condition never being met.

#### 馃摠 Envelope

The above description regarding pointer receivers on T may appear to be burdensome for prospective types. However, there is a mitigation for this in the form of the type ___Envelope[T any, O Numeric]___. This serves 2 purposes:

+ __permit pointer receiver:__ The envelope wraps the type T addressed as a pointer and also contains a __numeric__ member P of type O. This is particularly useful large struct instances, where copying by value could be non trivial and thus inefficient.

+ __satisfy ProxyField constraint:__ The presence of the proxy field P means that Envelope is able to implement all the methods on the ___ProxyField___ constraint, freeing the client from this obligation.

The following is an example of how to use the ___Envelope___ with the iterator ___RangeIteratorByProxy___:

```go
obs := rx.RangePF(&rx.RangeIteratorByProxy[rx.Envelope[nugget, int], int]{
StartAt: rx.Envelope[nugget, int]{P: 5},
By: rx.Envelope[nugget, int]{P: 1},
Whilst: rx.LessThanPF(rx.Envelope[nugget, int]{P: 8}),
})
```

### 馃幁 Map

The ___Map___ functionality poses a new challenge. If we wanted to map a value of type ___T___ to a value other than ___T___, the mapped to value could not be sent through the channel because it is of the wrong type. A work-around would be to use an opaque instance of Item, but then that could easily become very messy as we no longer have consistent types of emitted values which would be difficult to keep track of.
Expand Down
6 changes: 3 additions & 3 deletions rx/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1037,13 +1037,13 @@ var _ = Describe("Factory", func() {
})
})

Context("ProxyRangeIterator", func() {
Context("RangeIteratorByProxy", func() {
When("positive count", func() {
It("馃И should: create observable", func() {
// Test_Range
defer leaktest.Check(GinkgoT())()

obs := rx.RangePF(&rx.ProxyRangeIterator[widget, int]{
obs := rx.RangePF(&rx.RangeIteratorByProxy[widget, int]{
StartAt: widget{id: 5},
By: widget{id: 1},
Whilst: rx.LessThanPF(widget{id: 8}),
Expand All @@ -1069,7 +1069,7 @@ var _ = Describe("Factory", func() {
// Test_Range
defer leaktest.Check(GinkgoT())()

obs := rx.RangePF(&rx.ProxyRangeIterator[rx.Envelope[nugget, int], int]{
obs := rx.RangePF(&rx.RangeIteratorByProxy[rx.Envelope[nugget, int], int]{
StartAt: rx.Envelope[nugget, int]{P: 5},
By: rx.Envelope[nugget, int]{P: 1},
Whilst: rx.LessThanPF(rx.Envelope[nugget, int]{P: 8}),
Expand Down
14 changes: 7 additions & 7 deletions rx/iterable-range.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ func (i *NumericRangeIterator[T]) While(current T) bool {
return i.Whilst(current)
}

// ProxyRangeIterator iterator required for struct types of T, where the
// RangeIteratorByProxy iterator required for struct types of T, where the
// client has nominated a member of T to be the proxy field with
// which numeric operations are performed to generate indexes for iteration.
type ProxyRangeIterator[T ProxyField[T, O], O Numeric] struct {
type RangeIteratorByProxy[T ProxyField[T, O], O Numeric] struct {
StartAt T
By T
Whilst WhilstFunc[T]
Expand All @@ -164,7 +164,7 @@ type ProxyRangeIterator[T ProxyField[T, O], O Numeric] struct {

// Init is invoked prior to iteration and returns an error if not
// defined correctly.
func (i *ProxyRangeIterator[T, O]) Init() error {
func (i *RangeIteratorByProxy[T, O]) Init() error {
if i.Whilst == nil {
return RangeMissingWhilstError
}
Expand All @@ -174,7 +174,7 @@ func (i *ProxyRangeIterator[T, O]) Init() error {

// Start should return the initial index value. If By has
// not been set, a panic occurs
func (i *ProxyRangeIterator[T, O]) Start() (*T, error) {
func (i *RangeIteratorByProxy[T, O]) Start() (*T, error) {
if i.By.Field() == 0 {
panic("bad by value, can't be zero")
}
Expand All @@ -188,12 +188,12 @@ func (i *ProxyRangeIterator[T, O]) Start() (*T, error) {
return &index, nil
}

func (i *ProxyRangeIterator[T, O]) Step() O {
func (i *RangeIteratorByProxy[T, O]) Step() O {
return i.By.Field()
}

// Increment increments index value
func (i *ProxyRangeIterator[T, O]) Increment(index *T) *T {
func (i *RangeIteratorByProxy[T, O]) Increment(index *T) *T {
// This does look a bit strange but its a work around
// for the fact that the instance of T is implemented with
// non-pointer receivers and therefore can't make modifications
Expand All @@ -212,7 +212,7 @@ func (i *ProxyRangeIterator[T, O]) Increment(index *T) *T {

// While defines a condition that must be true for the loop to
// continue iterating.
func (i *ProxyRangeIterator[T, O]) While(current T) bool {
func (i *RangeIteratorByProxy[T, O]) While(current T) bool {
return i.Whilst(current)
}

Expand Down

0 comments on commit 517fc82

Please sign in to comment.