pharo icon indicating copy to clipboard operation
pharo copied to clipboard

SequenceableCollection >> splitOnFirst: is a valuable and missing method

Open Ducasse opened this issue 1 year ago • 5 comments

In microdown we define

Sequenceable >> splitOnFirst: anObject
	"Split the receiver on the first element that match anObject"
	
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 0 >> #(#(1 2 3) #(4 5 6 0 7)) "
	
	| indexOfElement |
	indexOfElement := self indexOf: anObject.
	indexOfElement = 0 ifTrue: [ ^ { self. #() } ].
	^ { self copyFrom: 1 to: indexOfElement -1 . self copyFrom: indexOfElement + 1 to: self size }  

And we believe that this method deserves to be in Pharo because it is really useful. This deserves more tests.

We can imagine also another one

Sequenceable >> splitOnFirstSatisfy: aPrediate
	"Split the receiver on the first element that satisfy aPrediate"
	
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: [:each | each isZero]) >> #(#(1 2 3) #(4 5 6 0 7)) "
	

Ducasse avatar May 26 '24 08:05 Ducasse

the following is better than the first one. It lets clients adjust their semantics.

splitOnFirst: anObject noneValue: aValue
	"Split the receiver on the first element that match anObject"
	
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 0 noneValue: 33 >> #(#(1 2 3) #(4 5 6 0 7)) "
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 99 nonValue: nil >> #(#(1 2 3 04 5 6 0 7) nil "
	
	| element |
	element := self indexOf: anObject.
	element = 0 ifTrue: [ ^ { self. aValue } ].
	^ { self copyFrom: 1 to: element -1 . self copyFrom: element + 1 to: self size }  

Ducasse avatar May 26 '24 08:05 Ducasse

splitOnFirst: anObject
	"Split the receiver on the first element that match anObject, returns two sequenceable."
	
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 0 >> #(#(1 2 3) #(4 5 6 0 7)) "
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 99 >> #(#(1 2 3 04 5 6 0 7) #() "
	
	^ self splitOnFirst: anObject noneValue: #()

Ducasse avatar May 26 '24 08:05 Ducasse

Would it be more idiomatic to use #splitOnFirst:ifNone: like #detect:ifNone:

The slight difference might be that the ifNone: would be aBlock meaning you could either handle the error or choose to return the collection.

macta avatar May 27 '24 20:05 macta

Good idea. I have to think about because with ifNone: how do I return #(#(1 2 3 04 5 6 0 7) #())

#(1 2 3 0 4 5 6 0 7) splitOnFirst: 99 ifNone: [ :orginalCollection ]

splitOnFirst: anObject ifNone: aOneValueBlock
	"Split the receiver on the first element that match anObject"
	
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 0 noneValue: 33 >> #(#(1 2 3) #(4 5 6 0 7)) "
	"#(1 2 3 0 4 5 6 0 7) splitOnFirst: 99 nonValue: nil >> #(#(1 2 3 04 5 6 0 7) nil "
	
	| element |
	element := self indexOf: anObject.
	element = 0 ifTrue: [ ^ aBloc value: { self. aValue } ].
	^ { self copyFrom: 1 to: element -1 . self copyFrom: element + 1 to: self size }  

But this is not the same as ifNone: because ifNone: does not need block with one argument. So I would prefer not to introduce such kind of subtlety.

Ducasse avatar Aug 06 '24 14:08 Ducasse

Because in general split should return two collections.

Ducasse avatar Aug 06 '24 14:08 Ducasse