Re "Strings are wrong", the influences on my thinking went like this.(1) As an undergraduate, I did exercises with the Euler compiler������ (written in Burroughs Algol) and the XPL compiler (written in XPL).������ XPL had immutable strings, rather like AWK or Java, and a PL/I-ish������ set of operations on them.�� Burroughs Algol didn't have strings,������ but did have pointers, that you could use to stream through arrays.������ It turned out to be notably easier to do tokenising in Algol than������ in XPL.(2) A friend of mine, while a masters student, made some pocket money������ by writing a "batch editor" (same application domain as sed, but������ different command set) for IBM mainframes under MVS, which he did������ using PL/I.�� PL/I fans used to point to its string operations as������ a good collection, but he had to fight PL/I every step of the way������ to get the string operations he needed.�� When I met UNIX V7 on a������ PDP-11, it took me 15 pages of C to accomplish what he needed������ more than 50 pages of PL/I to do.�� And my editor had undo...������ The trick was being able to make just the right text abstractions������ that I needed.(3) A company I worked at had a program that generated assembly code������ for several different machines from a common specification.�� The������ original version manipulated instructions as *trees*.�� A new hire������ was asked to write a new generator because the old one was������ getting harder to maintain and we wanted it faster.�� His program������ represented instructions as *strings*.�� It turned out to be������ slower and harder to maintain than the original.(4) I encountered Eugene Myers' AVL DAG data structure (which quite������ efficiently lets you maintain many versions of a large text) and������ the Rope data structure (basically a lazy concatenation tree)������ that accompanies the Boehm garbage collector.�� Both of them get������ a lot of efficiency out of NOT doing concatenations.(5) Learning Java, I found that Java had taken a bullet to the head������ which Smalltalk had dodged, and which had been very clearly������ explained in the coloured books.�� Consider the selectors������ #printOn: and #printString.�� There are two ways to implement������ them: the smart (Smalltalk) and the spectacularly stupid (Java).������ Smart way:�� #printOn: is basic, #printString is derived.������ Stupid way: #printString is basic and #printOn: is derived.������ Why is this smart?�� If you are generating several MB of text������ *in order to write it to an external sink*, then #printOn:������ does the job very efficiently.�� But #printString not only������ turns over large amounts of memory, giving the GC more work������ than it really needs, it is fatally easy to fall into������ quadratic time behaviour.(5) There's the insufficiently famous epigram by Alan Perlis:������ "The string is a stark data structure,�������� and everywhere it is passed there is much duplication of process.�������� It is a perfect vehicle for hiding information."������ One way to understand this is that it's quite easy to put������ information *into* a string, but it can be quite hard to get������ it out again.�� I have even run into examples where the������ getting-it-out-reliably part was accidentally impossible.������ What the heck, let's take Dates as an example (not of impossibility).������ What does '180612' mean?�� What does dateString copyFrom: 3 to: 4������ give you?�� But aData monthNumber is hard to misunderstand.(6) Then there's the whole scripting attack (Little Bobby Tables)������ thing.The rule of thumb that I use is that if you are just accepting adatum, maybe stashing it somewhere, and eventually giving it back,then a string is fine.�� For example, considering the huge varietyin the way people's names may be structured, strings are prettymuch the ideal way to represent them.�� You *shouldn't* be extracting"the middle initial" because some people don't *have* one and somepeople have more than one.�� (I am sick of my name being rejected asinvalid by American-designed programs that insist that a surnamecannot have an apostrophe or two capital letters.�� No Irish needapply.)�� But if you want to process structured information, youwant to get the data out of string form and into some kind of treeas soon as you can.�� In fact, you don't want a string form in thefirst place.�� I have seen people manipulating XML with stringoperations, and it's not a pretty sight.And when we're talking about something like (REAL) object-orientedprogramming in a language like Pharo, we really should be thinkingin terms of objects other than strings. For example, if we takeVMS as an example, [.FOO]BAR.TXT;1 and <.FOO>BAR.TXT.1 are thesame *filename* while they are different *strings*, and resolvingthe filename relative to DSK:[ICK.ACK] isn't terribly similar tostring concatenation.�� (Alphabetic case?�� Let's not go there.)This is precisely why there are classes like FileLocator and Path.On 6 March 2018 at 16:33, Esteban A. Maringolo <emaringolo@gmail.com> wrote:Hi Richard,
Certainly a BitStream is beyond what I initially thought.
But it could be of some use to play with it, is it available somewhere
with a friendly open source license?
I might give it a try using it for my generator (tests are already
passing). Your example is almost complete, but if I understand it
you're adding the size of the checksum instead of the checksum itself.
Nonetheless I think its readability is superior, and I guess that it
is better perfomance and memorywise.
Also it could be useful for Base58Encoder I'm expermenting with.
Encoding is "simple" but requires the use of a really large integer,
I'm stuck at the decoding part now.
After that there is a Base32 encoder (to implement a Bech32 encoder as well).
So I might use it in my road of experimentation with these matters.
Unless I diverge from this and abandon it as it normally happens. :/
Regarding this part:
> This reminds me of a lesson I learned many years
> ago: STRINGS ARE WRONG.�� (Thank you, designers of
> Burroughs Extended Algol!)�� When trees aren't the
> answer, streams often are.
Can you provide more context to this? I wouldn't mind if it is in a
separate thread or a blog post of your own.
Thanks in advance.
Esteban A. Maringolo
2018-03-05 21:55 GMT-03:00 Richard O'Keefe <raoknz@gmail.com>:
> I note that the specification in question does not
> deal with arbitrary bit strings but with "entropies"
> that are 128 to 256 bits long and a multiple of 32 bits.
> 4 to 8 bits are copied from the front to the end.
> (So selecting *this* bit field can be done by taking
> the first byte of a ByteArray.)�� This makes the sequence
> 132 to 264 bits.�� This is then chopped into 11 bit
> subsequences.�� These are not arbitrary subsequences and
> they are not taken in arbitrary order.�� They are a stream.
>
> My own Smalltalk library include BitInputStream and
> BitOutputStream, wrapping byte streams.�� So we could
> do something like
>
>�� �� ent := aByteArray size * 8.�� "BIP-39 ENT"
>�� �� cs�� := ent // 32.�� �� �� �� �� �� "BIP-39 CS"
>�� �� foo := ByteArray new: (ent + cs) // 8.
>�� �� o�� ��:= BitOutputStream on: foo writeStream.
>�� �� i�� ��:= BitInputStream on: aByteArray readStream.
>�� �� 1 to: ent do: [:x | o nextPut: i next].
>�� �� i reset.
>�� �� o nextUnsigned: cs put: (i nextUnsigned: cs).
>�� �� i close.
>�� �� o close.
>�� �� i := BitInputStream on: foo readStream.
>�� �� ans := (1 to: (ent + cs) // 11) collect: [:x |
>�� �� �� ��WordList at: 1 + (i nextUnsigned: 11)].
>
> Stare at this for a bit, and you realise that you don't
> actually need the working byte array foo.
> ByteArray
>�� ��methods for: 'bitcoin'
>�� �� ��mnemonic
>�� �� �� ��|ent csn i t|
>�� �� �� ��(ent between: 128 and: 256)
>�� �� �� �� ��ifFalse: [self error: 'wrong size for BIP-39'].
>�� �� �� ��cs�� := ent // 32.
>�� �� �� ��n�� ��:= (ent + cs) // 11.
>�� �� �� ��i�� ��:= BitInputStream on: (ReadStream on: self).
>�� �� �� ��t�� ��:= i nextUnsigned: cs.
>�� �� �� ��i reset.
>�� �� �� ��^(1 to: n) collect: [:index |
>�� �� �� �� ��WordList at: 1 + (index = n
>�� �� �� �� �� ��ifTrue:�� [((i nextUnsigned: 11 - cs) bitShift: cs) bitOr: t]
>�� �� �� �� �� ��ifFalse: [i nextUnsigned: 11])]
>
> My BitInputStream and BitOutputStream classes are,
> um, not really mature.�� They aren't *completely*
> naive, but they could be a lot better, and in
> particular, BitInputStream>>nextUnsigned: and
> BitOutputStream>>nextUnsigned:put: are definitely
> suboptimal.�� I put this out there just to suggest
> that there is a completely different way of thinking
> about the problem.�� (Actually, this isn't *entirely*
> unlike using Erlang bit syntax.)
>
> Bit*Streams are useful enough to justify primitive
> support.�� (Which my classes don't have yet.�� I did
> say they are not mature...)
>
> This reminds me of a lesson I learned many years
> ago: STRINGS ARE WRONG.�� (Thank you, designers of
> Burroughs Extended Algol!)�� When trees aren't the
> answer, streams often are.
>
>
>
>
> On 6 March 2018 at 07:21, Esteban A. Maringolo <emaringolo@gmail.com> wrote:
>>
>> 2018-03-05 14:02 GMT-03:00 Stephane Ducasse <stepharo.self@gmail.com>:
>> > On Sun, Mar 4, 2018 at 9:43 PM, Esteban A. Maringolo
>> > <emaringolo@gmail.com> wrote:
>> >> 2018-03-04 17:15 GMT-03:00 Sven Van Caekenberghe <sven@stfx.eu>:
>> >>> Bits are actually numbered from right to left (seen from how they are
>> >>> printed).
>> >>
>> >> I understand bit operations, used it extensively with IP address eons
>> >> ago.
>> >>
>> >> But if a spec says: "Take the first n bits from the hash", it means
>> >> the first significant bits.
>> >> so in 2r100101111 the first 3 bits are "100" and not "111".
>> >
>> > naive question: why?
>>
>> Because it says so.
>> "A checksum is generated by taking the first ENT / 32 bits of its
>> SHA256 hash. This checksum is appended to the end of the initial
>> entropy.
>> Next, these concatenated bits are split into groups of 11 bits, each
>> encoding a number from 0-2047, serving as an index into a wordlist.
>> Finally, we convert these numbers into words and use the joined words
>> as a mnemonic sentence." [1].
>>
>> > To me it looks like a lousy specification.
>>
>> It might be, I can't really tell.
>>
>> But such manipulation could be useful if you are knitting different
>> parts of a binary packet whose boundaries are not at byte level, but
>> bit instead. So you can take "these 5 bits, concatenate with this
>> other 7, add 13 zero bits, then 1 followed by the payload". I'm
>> assuming a non real case here though, my use case was fulfilled
>> already.
>>
>> Regards!
>>
>> --
>> Esteban A. Maringolo
>>
>> [1]
>> https://github.com/bitcoin/bips/blob/master/bip-0039.mediawi ki#generating-the-mnemonic
>>
>