On Wed, Mar 21, 2018 at 4:09 PM, Eliot Miranda <eliot.miranda@gmail.com> wrote:


On Wed, Mar 21, 2018 at 2:48 PM, Levente Uzonyi <leves@caesar.elte.hu> wrote:
On Tue, 20 Mar 2018, Chris Cunningham wrote:

Hi.  Reopening this thread - probably tomorrow will implement 'better' solution (with documentation).

On Sat, Feb 10, 2018 at 12:03 PM, Tobias Pape <Das.Linux@gmx.de> wrote:

      > On 10.02.2018, at 20:36, Tony Garnock-Jones <tonyg@leastfixedpoint.com> wrote:
      >
      > On 02/10/2018 07:02 PM, Levente Uzonyi wrote:
      >> So,
      >>    a perform: {#or:. #xor:. #and:} atRandom with: b
      >> would just work for that imaginary interpreter if b were a Boolean.
      >
      > Yes, that "interpreter" works just fine today, if b is a Boolean. It's
      > the case where b is a Boolean-producing expression - such as in a "lazy"
      > interpreter - that doesn't work without Fabio's proposed change.
      >
      > I went and looked at the ANSI standard (draft), btw [1].
      >
      > There, #xor: is specified as taking only a boolean.
      >
      > So this would be an extension, potentially affecting portability, for
      > what that's worth these days.
      >
      > I think the performance objection has been well-refuted, and I see the
      > consistency benefit as being real, but probably pretty limited, and I
      > kind of don't like the potential portability implications.

      I presume the main "feeling" here is parallelity:

      #& takes an evaluated boolean   #and: takes an unevaluated block
      #| takes an evaluated boolean   #or: takes an unevaluated block

      #xor: takes an evaluated boolean but looks like #and: and #or:,
      So it seems to belong to the right side, and then we think again about
      the left side, come up with symbols, only to find that #~= and #~~ are already there.

      So, just lets go all the way and document #xor: better, not making take it a block,
      maybe pointing out that #~= ist typically better fitted…

      Best regards
              -Tobias


So, I have written some tests for speed and 'ensuring the arguments are booleans', with another proposed solution.

First, speed:

#xor: base
#~= is 124% slower (or 2-1/4 as much time as existing xor: method)
#~~ is 75% faster
#cbcXor: is 32% slower

Note: for real speed, use ~~ , not ~= !

Why cbcXor: ?  It is the only one that makes sure the arguments are boolean - fails otherwise.

Here is a simpler and faster alternative:

True >> #xor: aBoolean

        aBoolean ifTrue: [ ^false ] ifFalse: [ ^true ]

False >> #xor: aBoolean

        aBoolean ifTrue: [ ^true ] ifFalse: [ ^false ]

and is this noticeably slower?

 True >> #xor: aBoolean

        ^aBoolean ifTrue: [ false ] ifFalse: [ true ]

False >> #xor: aBoolean

        ^aBoolean ifTrue: [ true ] ifFalse: [ false ]

but I would much prefer to see either

Boolean>>xor: aBooleanOrBlock

    ^ aBooleanOrBlock value not

or

True >> #xor: aBooleanOrBlock

        ^aBooleanOrBlock value ifTrue: [ false ] ifFalse: [ true ]

False >> #xor: aBooleanOrBlock

        ^aBooleanOrBlock value ifTrue: [ true ] ifFalse: [ false ]

The lack of symmetry with and: and or: is, IMO, bad.

I started out with this mind-set, but Tobias' arguments crystallized the discussion for me:
1. Adhere to standards (booleans only)
2. Or, maybe #xor: behave like and: and or:.
I also found another discussion - unlike and: and or:, for xor: you will ALWAYS have to evaluate the block - and that doubles (or significantly more) the amount of time needed to run the method.  It is still small, of course.

With this last slight variation, our rule would be "as long as the receiver is a boolean and the arguments' value is a boolean, then we apply the rule".
I could live with that.

-cbc


Levente



Tests to run:

First, install

True>>cbcXor: boolean
^boolean isFalse
True>>isTrue
^true
True>>isFalse
^false
False>>cbcXor: boolean
^boolean isTrue
False>>isFalse
^true
False>>isTrue
^false

"Setup"
pairs := #( true false true true false false false true ).
invalidPairs := { true. 1. true. #[ 1 3 0 9 ]. true. 'abc'. false. 1. false. #[ 1 3 0 9 ]. false. 'abc'. 'abc'. true. #[ 1 3 0 9 ]. false. }.
methods := {
[:f :s| ]. 
[:f :s| f xor: s]. 
[:f :s| f cbcXor: s]. 
[:f :s| f ~= s]. 
[:f :s| f ~~ s].
}.
"Validity Test"
validCheck := methods collect: [:m|

m sourceString. 
#( true false false true ) = (pairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]])
ifTrue: ['Valid'] ifFalse: ['ERRORS'].
pairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]. 
}].
"all methods are valid"
"Testing that non-booleans actually result in errors, and not false positive/negatives"
invalidCheck := methods collect: [:m|

m sourceString. 
((invalidPairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]) select: [:r| r = #error]) size = 8
ifTrue: ['Valid'] ifFalse: ['ERRORS'].
invalidPairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]. 
}].
"Only #cbcXor: correctly fails all of these.  Some interesting results..."
"Timing test.  Need to run 10,000,000 to get reasonable distinctions on my machine."
timing := methods collect: [:m|
{ m sourceString.  [[10000000 timesRepeat: [pairs pairsDo: m]] timeToRun] on: Error do: [#invalid]. }
].
"And showing percentage slower"
base := timing first second.
bench := timing second second - base.
timing allButFirst collect: [:res| { res first. this := res second - base. ((this - bench) * 100 / bench) rounded. }].

Thanks,
cbc







--
_,,,^..^,,,_
best, Eliot