mixin template's alias parameter ... ignored ?

someone someone at somewhere.com
Tue Jul 13 14:09:47 UTC 2021


On Tuesday, 13 July 2021 at 05:37:49 UTC, ag0aep6g wrote:
> On 13.07.21 03:03, someone wrote:
>> On Monday, 12 July 2021 at 23:28:29 UTC, ag0aep6g wrote:
> [...]
>>> I'm not sure where we stand with `in`
>> 
>> You mean *we* = D developers ?
>
> Yes. Let me rephrase and elaborate: I'm not sure what the 
> current status of `in` is. It used to mean `const scope`. But 
> DIP1000 changes the effects of `scope` and there was some 
> discussion about its relation to `in`.
>
> Checking the spec, it says that `in` simply means `const` 
> unless you use `-preview=in`. The preview switch makes it 
> `const scope` again, but that's not all. There's also something 
> about passing by reference.
>
> https://dlang.org/spec/function.html#in-params

ACK. So for the time being I'll be reverting all my input 
parameters to const (unless ref or out of course) and when the 
whole in DIP matter resolves (one way or the other) I'll revert 
them (or not) accordingly. Parameters declared in read more 
naturally (and akin to out) than const but is form not function 
what I need to get right right now.

>> For a UDT like mine I think it has a lot of sense because when 
>> I think of a string and I want to chop/count/whatever on it my 
>> mind works one-based not zero-based. Say "abc" needs b my mind 
>> works a lot easier mid("abc", 2, 1) than mid("abc", 1, 1) and 
>> besides I am *not* returning a range or a reference slice to a 
>> range or whatever I am returning a whole new string 
>> construction. If I would be returning a range I will follow 
>> common sense since I don't know what will be done thereafter 
>> of course.
>
> I think you're setting yourself up for off-by-one bugs by going 
> against the grain like that. Your functions are one-based. The 
> rest of the D world, including the standard library, is 
> zero-based. You're bound to forget to account for the 
> difference.

And I think you have a good point. I'll reconsider.

> But it's your code, and you can do whatever you want, of 
> course. Just looked like it might be a mistake.

All in all the whole module was updated accordingly and it seems 
it is working as expected (further testing needed) but, in the 
meantime, I learned a lot of things following the advice given by 
you, Ali, and others in this forum:

```d
/// implementation-bugs [-] using foreach (with this structure) 
always misses the last grapheme‐cluster … possible phobos bug # 
20483 @ unittest's last line

/// implementation‐tasks [+] reconsider making this whole UDT 
zero‐based as suggested by ag0aep6g—has a good point
/// implementation‐tasks [+] reconsider excessive cast usage as 
suggested by Ali: bypassing compiler checks could be potentially 
harmful … cast and integer promotion @ 
http://ddili.org/ders/d.en/cast.html
/// implementation‐tasks [-] for the time being input parameters 
are declared const instead of in; eventually they'll be back to 
in when the related DIP was setted once and for all; but, 
definetely—not scope const

/// implementation‐tasks‐possible [-] pad[L|R]
/// implementation‐tasks‐possible [-] replicate/repeat
/// implementation‐tasks‐possible [-] replace(string, string)
/// implementation‐tasks‐possible [-] translate(string, string) … 
same‐size strings matching one‐to‐one

/// usage: array slicing can be used for usual things like: 
left() right() substr() etc … mainly when grapheme‐clusters are 
not expected at all
/// usage: array slicing needs a zero‐based first range argument 
and a second one one‐based (or one‐past‐beyond; which it is 
somehow … counter‐intuitive

module fw.types.UniCode;

import std.algorithm : map, joiner;
import std.array : array;
import std.conv : to;
import std.range : walkLength, take, tail, drop, dropBack; /// 
repeat, padLeft, padRight
import std.stdio;
import std.uni : Grapheme, byGrapheme;

/// within this file: gudtUGC



shared static this() { }  /// the following will be executed 
only‐once per‐app:
static this() { }         /// the following will be executed 
only‐once per‐thread:
static ~this() { }        /// the following will be executed 
only‐once per‐thread:
shared static ~this() { } /// the following will be executed 
only‐once per‐app:



alias stringUGC = Grapheme;
alias stringUGC08 = gudtUGC!(stringUTF08);
alias stringUGC16 = gudtUGC!(stringUTF16);
alias stringUGC32 = gudtUGC!(stringUTF32);
alias stringUTF08 = string;  /// same as immutable(char )[];
alias stringUTF16 = wstring; /// same as immutable(wchar)[];
alias stringUTF32 = dstring; /// same as immutable(dchar)[];

/// mixin templateUGC!(stringUTF08, r"gudtUGC08"d);
/// mixin templateUGC!(stringUTF16, r"gudtUGC16"d);
/// mixin templateUGC!(stringUTF32, r"gudtUGC32"d);
/// template templateUGC (typeStringUTF, alias lstrStructureID) { 
/// if these were possible there will be no need for stringUGC## 
aliases in main()

public struct gudtUGC(typeStringUTF) { /// UniCode 
grapheme‐cluster‐aware string manipulation (implemented for 
one‐based operations)

    /// provides: public property size_t count

    /// provides: public size_t decode(typeStringUTF strSequence)
    /// provides: public typeStringUTF encode()

    /// provides: public gudtUGC!(typeStringUTF) take(size_t 
intStart, size_t intCount = 1)
    /// provides: public gudtUGC!(typeStringUTF) takeL(size_t 
intCount)
    /// provides: public gudtUGC!(typeStringUTF) takeR(size_t 
intCount)
    /// provides: public gudtUGC!(typeStringUTF) chopL(size_t 
intCount)
    /// provides: public gudtUGC!(typeStringUTF) chopR(size_t 
intCount)
    /// provides: public gudtUGC!(typeStringUTF) padL(size_t 
intCount, typeStringUTF strPadding = r" ")
    /// provides: public gudtUGC!(typeStringUTF) padR(size_t 
intCount, typeStringUTF strPadding = r" ")

    /// provides: public typeStringUTF takeasUTF(size_t intStart, 
size_t intCount = 1)
    /// provides: public typeStringUTF takeLasUTF(size_t intCount)
    /// provides: public typeStringUTF takeRasUTF(size_t intCount)
    /// provides: public typeStringUTF chopLasUTF(size_t intCount)
    /// provides: public typeStringUTF chopRasUTF(size_t intCount)
    /// provides: public typeStringUTF padL(size_t intCount, 
typeStringUTF strPadding = r" ")
    /// provides: public typeStringUTF padR(size_t intCount, 
typeStringUTF strPadding = r" ")

    /// usage; eg: stringUGC32("äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 
日本語 = japanese"d).take(35, 3).take(1,2).take(1,1).encode(); /// 日
    /// usage; eg: stringUGC32("äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 
日本語 = japanese"d).take(35).encode(); /// 日
    /// usage; eg: stringUGC32("äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 
日本語 = japanese"d).takeasUTF(35); /// 日

    void popFront() { ++pintSequenceCurrent; }
    bool empty() { return pintSequenceCurrent == 
pintSequenceCount; }
    typeStringUTF front() { return takeasUTF(pintSequenceCurrent); 
}

    private stringUGC[] pugcSequence;
    private size_t pintSequenceCount = cast(size_t) 0;
    private size_t pintSequenceCurrent = cast(size_t) 0;

    @property public size_t count() { return pintSequenceCount; }

    this(
       const typeStringUTF lstrSequence
       ) {

       /// (1) given UTF‐encoded sequence

       decode(lstrSequence);

    }

    @safe public size_t decode( /// UniCode (UTF‐encoded → 
grapheme‐cluster) sequence
       const typeStringUTF lstrSequence
       ) {

       /// (1) given UTF‐encoded sequence

       size_t lintSequenceCount = cast(size_t) 0;

       if (lstrSequence is null) {

          pugcSequence = null;
          pintSequenceCount = cast(size_t) 0;
          pintSequenceCurrent = cast(size_t) 0;

       } else {

          pugcSequence = lstrSequence.byGrapheme.array;
          pintSequenceCount = pugcSequence.walkLength;
          pintSequenceCurrent = cast(size_t) 1;

          lintSequenceCount = pintSequenceCount;

       }

       return lintSequenceCount;

    }

    @safe public typeStringUTF encode() { /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence

       typeStringUTF lstrSequence = null;

       if (pintSequenceCount >= cast(size_t) 1) {

          lstrSequence = pugcSequence
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             ;

       }

       return lstrSequence;

    }

    @safe public gudtUGC!(typeStringUTF) take( /// UniCode 
(grapheme‐cluster → grapheme‐cluster) sequence
       const size_t lintStart,
       const size_t lintCount = cast(size_t) 1
       ) {

       /// (1) given start position >= 1
       /// (2) given count >= 1

       gudtUGC!(typeStringUTF) lugcSequence;

       if (lintStart >= cast(size_t) 1 && lintCount >= 
cast(size_t) 1) {

          /// eg#1: takeasUTF(1,3) → range#1=start-1=1-1=0 and 
range#2=range#1+count=0+3=3 → 0..3
          /// eg#1: takeasUTF(6,3) → range#2=start-1=6-1=5 and 
range#2=range#1+count=5+3=8 → 5..8

          /// eg#2: takeasUTF(01,1) → range#1=start-1=01-1=00 and 
range#2=range#1+count=00+1=01 → 00..01
          /// eg#2: takeasUTF(50,1) → range#2=start-1=50-1=49 and 
range#2=range#1+count=49+1=50 → 49..50

          size_t lintRange1 = lintStart - cast(size_t) 1;
          size_t lintRange2 = lintRange1 + lintCount;

          if (lintRange2 <= pintSequenceCount) {

             lugcSequence = 
gudtUGC!(typeStringUTF)(pugcSequence[lintRange1..lintRange2]
                .map!((ref g) => g[])
                .joiner
                .to!(typeStringUTF)
                );

          }

       }

       return lugcSequence;

    }

    @safe public gudtUGC!(typeStringUTF) takeL( /// UniCode 
(grapheme‐cluster → grapheme‐cluster) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       gudtUGC!(typeStringUTF) lugcSequence;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence
             .take(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             );

       }

       return lugcSequence;

    }

    @safe public gudtUGC!(typeStringUTF) takeR( /// UniCode 
(grapheme‐cluster → grapheme‐cluster) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       gudtUGC!(typeStringUTF) lugcSequence;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence
             .tail(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             );

       }

       return lugcSequence;

    }

    @safe public gudtUGC!(typeStringUTF) chopL( /// UniCode 
(grapheme‐cluster → grapheme‐cluster) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       gudtUGC!(typeStringUTF) lugcSequence;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence
             .drop(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             );

       }

       return lugcSequence;

    }

    @safe public gudtUGC!(typeStringUTF) chopR( /// UniCode 
(grapheme‐cluster → grapheme‐cluster) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       gudtUGC!(typeStringUTF) lugcSequence;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lugcSequence = gudtUGC!(typeStringUTF)(pugcSequence
             .dropBack(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             );

       }

       return lugcSequence;

    }

    @safe public typeStringUTF takeasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintStart,
       const size_t lintCount = cast(size_t) 1
       ) {

       /// (1) given start position >= 1
       /// (2) given count >= 1

       typeStringUTF lstrSequence = null;

       if (lintStart >= cast(size_t) 1 && lintCount >= 
cast(size_t) 1) {

          /// eg#1: takeasUTF(1,3) → range#1=start-1=1-1=0 and 
range#2=range#1+count=0+3=3 → 0..3
          /// eg#1: takeasUTF(6,3) → range#2=start-1=6-1=5 and 
range#2=range#1+count=5+3=8 → 5..8

          /// eg#2: takeasUTF(01,1) → range#1=start-1=01-1=00 and 
range#2=range#1+count=00+1=01 → 00..01
          /// eg#2: takeasUTF(50,1) → range#2=start-1=50-1=49 and 
range#2=range#1+count=49+1=50 → 49..50

          size_t lintRange1 = lintStart - cast(size_t) 1;
          size_t lintRange2 = lintRange1 + lintCount;

          if (lintRange2 <= pintSequenceCount) {

             lstrSequence = pugcSequence[lintRange1..lintRange2]
                .map!((ref g) => g[])
                .joiner
                .to!(typeStringUTF)
                ;

          }

       }

       return lstrSequence;

    }

    @safe public typeStringUTF takeLasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       typeStringUTF lstrSequence = null;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lstrSequence = pugcSequence
             .take(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             ;

       }

       return lstrSequence;

    }

    @safe public typeStringUTF takeRasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       typeStringUTF lstrSequence = null;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lstrSequence = pugcSequence
             .tail(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             ;

       }

       return lstrSequence;

    }

    @safe public typeStringUTF chopLasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       typeStringUTF lstrSequence = null;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lstrSequence = pugcSequence
             .drop(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             ;

       }

       return lstrSequence;

    }

    @safe public typeStringUTF chopRasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintCount
       ) {

       /// (1) given count >= 1

       typeStringUTF lstrSequence = null;

       if (lintCount >= cast(size_t) 1 && lintCount <= 
pintSequenceCount) {

          lstrSequence = pugcSequence
             .dropBack(lintCount)
             .map!((ref g) => g[])
             .joiner
             .to!(typeStringUTF)
             ;

       }

       return lstrSequence;

    }

    @safe public typeStringUTF padLasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintCount,
       const typeStringUTF lstrPadding = cast(typeStringUTF) r" "
       ) {

       /// (1) given count >= 1
       /// [2] given padding (default is a single blank space)

       typeStringUTF lstrSequence = null;

       if (lintCount >= cast(size_t) 1 && lintCount > 
pintSequenceCount) {

          lstrSequence = null; /// pending

       }

       return lstrSequence;

    }

    @safe public typeStringUTF padRasUTF( /// UniCode 
(grapheme‐cluster → UTF‐encoded) sequence
       const size_t lintCount,
       const typeStringUTF lstrPadding = cast(typeStringUTF) r" "
       ) {

       /// (1) given count >= 1
       /// [2] given padding (default is a single blank space)

       typeStringUTF lstrSequence = null;

       if (lintCount >= cast(size_t) 1 && lintCount > 
pintSequenceCount) {

          lstrSequence = null; /// pending

       }

       return lstrSequence;

    }

}

unittest {

    version (useUTF08) {
    stringUTF08 lstrSequence1 = 
r"12345678901234567890123456789012345678901234567890"c;
    stringUTF08 lstrSequence2 = 
r"1234567890АВГДЕЗИЙКЛABCDEFGHIJabcdefghijQRSTUVWXYZ"c;
    stringUTF08 lstrSequence3 = "äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 
日本語 = japanese 😎"c;
    }

    version (useUTF16) {
    stringUTF16 lstrSequence1 = 
r"12345678901234567890123456789012345678901234567890"w;
    stringUTF16 lstrSequence2 = 
r"1234567890АВГДЕЗИЙКЛABCDEFGHIJabcdefghijQRSTUVWXYZ"w;
    stringUTF16 lstrSequence3 = "äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 
日本語 = japanese 😎"w;
    }

    version (useUTF32) {
    stringUTF32 lstrSequence1 = 
r"12345678901234567890123456789012345678901234567890"d;
    stringUTF32 lstrSequence2 = 
r"1234567890АВГДЕЗИЙКЛABCDEFGHIJabcdefghijQRSTUVWXYZ"d;
    stringUTF32 lstrSequence3 = "äëåčñœß … russian = русский 🇷🇺 ≠ 🇯🇵 
日本語 = japanese 😎"d;
    }

    size_t lintSequence1sizeUTF = lstrSequence1.length;
    size_t lintSequence2sizeUTF = lstrSequence2.length;
    size_t lintSequence3sizeUTF = lstrSequence3.length;

    size_t lintSequence1sizeUGA = lstrSequence1.walkLength;
    size_t lintSequence2sizeUGA = lstrSequence2.walkLength;
    size_t lintSequence3sizeUGA = lstrSequence3.walkLength;

    size_t lintSequence1sizeUGC = 
lstrSequence1.byGrapheme.walkLength;
    size_t lintSequence2sizeUGC = 
lstrSequence2.byGrapheme.walkLength;
    size_t lintSequence3sizeUGC = 
lstrSequence3.byGrapheme.walkLength;

    assert(lintSequence1sizeUGC == cast(size_t) 50);
    assert(lintSequence2sizeUGC == cast(size_t) 50);
    assert(lintSequence3sizeUGC == cast(size_t) 50);

    assert(lintSequence1sizeUGA == cast(size_t) 50);
    assert(lintSequence2sizeUGA == cast(size_t) 50);
    assert(lintSequence3sizeUGA == cast(size_t) 52);

    version (useUTF08) {
    assert(lintSequence1sizeUTF == cast(size_t) 50);
    assert(lintSequence2sizeUTF == cast(size_t) 60);
    assert(lintSequence3sizeUTF == cast(size_t) 91);
    }

    version (useUTF16) {
    assert(lintSequence1sizeUTF == cast(size_t) 50);
    assert(lintSequence2sizeUTF == cast(size_t) 50);
    assert(lintSequence3sizeUTF == cast(size_t) 57);
    }

    version (useUTF32) {
    assert(lintSequence1sizeUTF == cast(size_t) 50);
    assert(lintSequence2sizeUTF == cast(size_t) 50);
    assert(lintSequence3sizeUTF == cast(size_t) 52);
    }

    /// the following should be the same regardless of the 
encoding being used and is the whole point of this UDT being made:

    version (useUTF08) { alias stringUTF = stringUTF08; 
stringUGC08 lugcSequence3 = stringUGC08(lstrSequence3); }
    version (useUTF16) { alias stringUTF = stringUTF16; 
stringUGC16 lugcSequence3 = stringUGC16(lstrSequence3); }
    version (useUTF32) { alias stringUTF = stringUTF32; 
stringUGC32 lugcSequence3 = stringUGC32(lstrSequence3); }

    assert(lugcSequence3.encode() == lstrSequence3);

    assert(lugcSequence3.take(35, 3).take(1,2).take(1,1).encode() 
== cast(stringUTF) r"日");

    assert(lugcSequence3.take(21).encode() == cast(stringUTF) 
r"р");
    assert(lugcSequence3.take(27).encode() == cast(stringUTF) 
r"й");
    assert(lugcSequence3.take(35).encode() == cast(stringUTF) 
r"日");
    assert(lugcSequence3.take(37).encode() == cast(stringUTF) 
r"語");
    assert(lugcSequence3.take(21, 7).encode() == cast(stringUTF) 
r"русский");
    assert(lugcSequence3.take(35, 3).encode() == cast(stringUTF) 
r"日本語");

    assert(lugcSequence3.takeasUTF(21) == cast(stringUTF) r"р");
    assert(lugcSequence3.takeasUTF(27) == cast(stringUTF) r"й");
    assert(lugcSequence3.takeasUTF(35) == cast(stringUTF) r"日");
    assert(lugcSequence3.takeasUTF(37) == cast(stringUTF) r"語");
    assert(lugcSequence3.takeasUTF(21, 7) == cast(stringUTF) 
r"русский");
    assert(lugcSequence3.takeasUTF(35, 3) == cast(stringUTF) 
r"日本語");

    assert(lugcSequence3.takeL(1).encode() == cast(stringUTF) 
r"ä");
    assert(lugcSequence3.takeR(1).encode() == cast(stringUTF) 
r"😎");
    assert(lugcSequence3.takeL(7).encode() == cast(stringUTF) 
r"äëåčñœß");
    assert(lugcSequence3.takeR(16).encode() == cast(stringUTF) 
r"日本語 = japanese 😎");

    assert(lugcSequence3.takeLasUTF(1) == cast(stringUTF) r"ä");
    assert(lugcSequence3.takeRasUTF(1) == cast(stringUTF) r"😎");
    assert(lugcSequence3.takeLasUTF(7) == cast(stringUTF) 
r"äëåčñœß");
    assert(lugcSequence3.takeRasUTF(16) == cast(stringUTF) r"日本語 = 
japanese 😎");

    assert(lugcSequence3.chopL(10).encode() == cast(stringUTF) 
r"russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎");
    assert(lugcSequence3.chopR(21).encode() == cast(stringUTF) 
r"äëåčñœß … russian = русский 🇷🇺");

    assert(lugcSequence3.chopLasUTF(10) == cast(stringUTF) 
r"russian = русский 🇷🇺 ≠ 🇯🇵 日本語 = japanese 😎");
    assert(lugcSequence3.chopRasUTF(21) == cast(stringUTF) 
r"äëåčñœß … russian = русский 🇷🇺");

    version (useUTF08) { stringUTF08 lstrSequence3reencoded; }
    version (useUTF16) { stringUTF16 lstrSequence3reencoded; }
    version (useUTF32) { stringUTF32 lstrSequence3reencoded; }

    for (
       size_t lintSequenceUGC = cast(size_t) 1;
       lintSequenceUGC <= lintSequence3sizeUGC;
       ++lintSequenceUGC
       ) {

       lstrSequence3reencoded ~= 
lugcSequence3.takeasUTF(lintSequenceUGC);

    }

    assert(lstrSequence3reencoded == lstrSequence3);

    lstrSequence3reencoded = null;

    version (useUTF08) { foreach (stringUTF08 lstrSequence3UGC; 
lugcSequence3) { lstrSequence3reencoded ~= lstrSequence3UGC; } }
    version (useUTF16) { foreach (stringUTF16 lstrSequence3UGC; 
lugcSequence3) { lstrSequence3reencoded ~= lstrSequence3UGC; } }
    version (useUTF32) { foreach (stringUTF32 lstrSequence3UGC; 
lugcSequence3) { lstrSequence3reencoded ~= lstrSequence3UGC; } }

    //assert(lstrSequence3reencoded == lstrSequence3); /// ooops … 
always missing last grapheme‐cluster: possible bug # 20483

}
```


More information about the Digitalmars-d-learn mailing list