Dan Ingalls is of course a big NAME in the
world of Smalltalk, but the stated reason
for changing the behaviour of #beginsWith:
and #endsWith: makes no sense.
We have many ways to define a partial order
on strings.
x <= y iff y beginsWith: x
x <= y iff y endsWith: x
x <= y iff y includesSubCollection: x
x <= y iff y includesSubSequence: x
These things are supposed to obey laws:
if a beginsWith: b , c
then a beginsWith: b
if a endsWith: b , c
then a endsWith: c
if a includesSubCollection: b , c
then a includesSubCollection: b
and a includesSubCollection: c
if a includesSubSequence: b , c
then a includesSubSequence: b
and a includesSubSequence: c.
We also expect the usual rules of equality
to hold.�� So
(1) a beginsWith: a
(2) a = '' , a
(3) THEREFORE a beginsWith: ''
(1) a endsWith: a
(2) a = a , ''
(3) THEREFORE a endsWith: ''
(1) a includesSubCollection: a
(2) a = '' , a
(3) THEREFORE a includesSubCollect: ''
Reasoning about strings (as values) gets
enormously more complicated if the operations
do not follow simple sensible rules, and
having '' be the least string under these
orderings and having '' be a prefix and a
suffix of any string is essential if the
rules are going to be simple and coherent.
'' is to strings (and more generally
empty sequences are to sequences) pretty
much what 0 is to integers.���� Denying that
'abc' beginsWith: '' is *structurally*
just like denying that 0 <= 123.
Now as it happens I *can* see a use for
versions of #beginsWith: and #endsWith:
that diverge from the ones we have, but
*this* is not where they need to diverge.
a beginsWithGraphemesOf: b
iff a asGraphemes = b asGraphemes , c asGraphemes
for some c, where s asGraphemes returns a
sequence of strings each of which is a maximally
long grapheme cluster, such that concatenating
s asGraphemes recovers s.�� That is,
#beginsWithGraphemesOf: and
#endsWithGraphemesOf: would respect the
Unicode Text Segmentation boundaries.
But s beginsWithGraphemesOf: ''
would still need to be true.
The thing is, in practice you often DON'T
KNOW whether a potential affix is empty or
not.�� Here are some of my test cases.
�� �� testTestData
�� �� �� "Ensure that the sample string has no duplicates."
�� �� �� [(Set withAll: string) size = stringSize] assert.
�� �� testBeginsWith
�� �� �� "Test that every prefix of the sample IS a prefix of it."
�� �� �� 0 to: stringSize do: [:n |
�� �� �� �� [string beginsWith: (string copyFrom: 1 to: n)] assert].
�� �� testEndsWith
�� �� �� "Test that every suffix of the sample IS a suffix of it."
�� �� �� 0 to: stringSize do: [:n |
�� �� �� �� [string endsWith: (string copyFrom: stringSize - n + 1 to: stringSize)] assert].
�� �� testIndexOfSubCollectionAtBeginning
�� �� �� "Test that every prefix of 'abcd' is found at the beginning."
�� �� �� 0 to: stringSize do: [:n | |s i t|
�� �� �� �� s := string copyFrom: 1 to: n.
�� �� �� �� i := string indexOfSubCollection: s startingAt: 1.
�� �� �� �� [1 = i] assert.
�� �� �� �� t := string copyFrom: i to: i - 1 + n.
�� �� �� �� [t = s] assert].
�� �� testIndexOfSubCollectionAtEnd
�� �� �� "Test that every proper suffix of the sample is found at the end."
�� �� �� 1 to: stringSize do: [:n | |s i t|
�� �� �� �� s := string copyFrom: stringSize - n + 1 to: stringSize.
�� �� �� �� i := string indexOfSubCollection: s startingAt: 1.
�� �� �� �� [stringSize + 1 - n = i] assert.
�� �� �� �� t := string copyFrom: i to: i - 1 + n.
�� �� �� �� [t = s] assert].
�� �� testLastIndexOfSubCollectionAtBeginning
�� �� �� "Test that every proper prefix of the sample is found at the beginning."
�� �� �� 1 to: stringSize do: [:n | |s i t|
�� �� �� �� s := string copyFrom: 1 to: n.
�� �� �� �� i := string lastIndexOfSubCollection: s startingAt: stringSize.
�� �� �� �� [1 = i] assert.
�� �� �� �� t := string copyFrom: i to: i - 1 + n.
�� �� �� �� [t = s] assert].
�� �� testLastIndexOfSubCollectionAtEnd
�� �� �� "Test that every suffix of the sample is found at the end."
�� �� �� 0 to: stringSize do: [:n | |s i t|
�� �� �� �� s := string copyFrom: stringSize - n + 1 to: stringSize.
�� �� �� �� i := string lastIndexOfSubCollection: s startingAt: stringSize.
�� �� �� �� [stringSize + 1 - n = i] assert.
�� �� �� �� t := string copyFrom: i to: i - 1 + n.
�� �� �� �� [t = s] assert].
�� �� testOccurrencesOfEmptyCollection
�� �� �� "Test that the empty string occurs at the beginning,
�� �� �� ��at the end, and in between every pair of adjacent characters."
�� �� �� [(string occurrencesOfSubCollection: '') = (stringSize + 1)] assert.
�� �� testOccurrencesOfUniqueParts
�� �� �� "Test that unique parts occur as many times as they should."
�� �� �� |repeated|
�� �� �� repeated := string , string , string.
�� �� �� 1 to: stringSize do: [:start |
�� �� �� �� start to: stringSize do: [:finish | |s n|
�� �� �� �� �� s := string copyFrom: start to: finish.
�� �� �� �� �� n := string occurrencesOfSubCollection: s.
�� �� �� �� �� [n = 1] assert.
�� �� �� �� �� n := repeated occurrencesOfSubCollection: s.
�� �� �� �� �� [n = 3] assert]].