digitalmars.D - A Value Range Propagation usage example, and more
- bearophile (96/96) Oct 08 2014 This is the first part of a function to convert to base 58 (some
This is the first part of a function to convert to base 58 (some letters are missing, like the upper case "I") used in the Bitcoin protocol: alias Address = ubyte[1 + 4 + RIPEMD160_digest_len]; char[] toBase58(ref Address a) pure nothrow safe { static immutable symbols = "123456789" ~ "ABCDEFGHJKLMNPQRSTUVWXYZ" ~ "abcdefghijkmnopqrstuvwxyz"; static assert(symbols.length == 58); auto result = new typeof(return)(34); foreach_reverse (ref ri; result) { uint c = 0; foreach (ref ai; a) { c = c * 256 + ai; ai = cast(ubyte)(c / symbols.length); c %= symbols.length; } ri = symbols[c]; } ... } The D type system isn't smart enough to see that "ai" is always fitting in an ubyte, so I have had to use a cast(ubyte). But casts are dangerous and their usage should be minimized, and to!ubyte is slow and makes the function not nothrow. So I've rewritten the code like this with a bit of algebraic rewriting: char[] toBase58(ref Address a) pure nothrow safe { static immutable symbols = "123456789" ~ "ABCDEFGHJKLMNPQRSTUVWXYZ" ~ "abcdefghijkmnopqrstuvwxyz"; static assert(symbols.length == 58); auto result = new typeof(return)(34); foreach_reverse (ref ri; result) { uint c = 0; foreach (ref ai; a) { immutable d = (c % symbols.length) * 256 + ai; ai = d / symbols.length; c = d; } ri = symbols[c % symbols.length]; } ... } Now it can be a little slower because the integer division and modulus has different divisors, so perhaps they can't be implemented with a little more than a single division, as before (I have not compared the assembly), but for the purposes of this code the performance difference is not a problem. Now the D type system is able to see that "ai" is always fitting in a ubyte, and there's no need for a cast. The compiler puts a safe implicit cast. This is awesome. - - - - - - - - - - - - - - But of course you often want more :-) This is another case where the current D type system allows you to avoid a cast: void main() { char['z' - 'a' + 1] arr; foreach (immutable i, ref c; arr) c = 'a' + i; } But if you want to use ranges and functional UFCS chains you currently need the cast: void main() { import std.range, std.algorithm, std.array; char[26] arr = 26 .iota .map!(i => cast(char)('a' + i)) .array; } In theory this program has the same compile-time information as the foreach case. In practice foreach is a built-in that enjoys more semantics than a iota+map. Currently iota(26) loses the compile-time information about the range, so you can't do (note: the "max" attribute doesn't exists): void main() { import std.range: iota; auto r = iota(26); enum ubyte m = r.max; } Currently the only way to keep that compile-time information is to use a template argument: void main() { import std.range: tIota; auto r = tIota!26; enum ubyte m = r.max; // OK } But even if you write such tIota range, the map!() will lose the compile-time value range information. And even if you manage to write a map!() able to do it with template arguments, you have template bloat. So there's a desire to manage the compile-time information (like value range information) of (number) literals without causing template bloat and without the need to use explicit template arguments. Bye, bearophile
Oct 08 2014