August 25, 2017
On Thursday, 24 August 2017 at 23:50:21 UTC, data pulverizer wrote:
>
> ```
> double density(D: UnivariateDistribution!Discrete, U = getVariateType!D, T = GetDistributionParameterType!D)(D d, U x)
> if(!is(D == Poisson!T))
> {
>     assert(false, "density function unimplemented for this distribution: " ~ D.stringof);
> }
>
> double density(D: UnivariateDistribution!Continuous, U = getVariateType!D, T = GetDistributionParameterType!D)(D d, U x)
> if(!is(D == Gamma!T) && !is(D == Gaussian!T) && !is(D == Uniform!T) && !is(D == Exponential!T))
> {
>     assert(false, "density function unimplemented for this distribution: " ~ D.stringof);
> }
>

What you seem concerned about here is how to produce a meaningful error message for distribution that you do not have implementations for. A slightly more elegant solution would be to pack the structs into an AliasSeq and then use something like !allSatisfies to test them all. I'm sure there's a more elegant solution, but that's the first thing I thought of.

>
> immutable class(T...){...}
>
> that this class can only create immutable objects without having to write immutable everywhere and or a UDA, but making every member immutable accomplishes the same thing.
>

What you're looking for is an immutable constructor:

class C
{
    this() immutable;
}

August 25, 2017
On Friday, 25 August 2017 at 00:35:24 UTC, jmh530 wrote:
> What you seem concerned about here is how to produce a meaningful error message for distribution that you do not have implementations for. A slightly more elegant solution would be to pack the structs into an AliasSeq and then use something like !allSatisfies to test them all. I'm sure there's a more elegant solution, but that's the first thing I thought of.
>

Andrei suggested allSatisfies that as an alternative approach to a Union keyword similar to Julia, at the time I was still stuck on how cool having a Union keyword like Julia's in D would be.

>>
>> immutable class(T...){...}
>
> What you're looking for is an immutable constructor:
>
> class C
> {
>     this() immutable;
> }

Aha, thanks!

August 25, 2017
On Friday, 25 August 2017 at 01:04:31 UTC, data pulverizer wrote:
> [snip]

With respect to your point about immutability, you might be interested in the parameterize function in dstats.distrib. I hadn't noticed that was there, but I think it accomplishes, to a limited extent, the behavior of what you want. It returns a delegate with the values of the distribution fixed in there.

Along the same lines, I think below is how I would set it up, rather than the mixin approach I discussed above. While it does not currently work with the parametrize funciton currently, I believe that with some simple adjustments it could.


import std.stdio : writeln;

struct Normal
{
    import dstats : normalCDF, normalCDFR, normalPDF, invNormalCDF;

    alias cdf = normalCDF;
    alias cdfr = normalCDFR;
    alias pdf = normalPDF;
    alias density = pdf;
    alias icdf = invNormalCDF;
}

struct LogNormal
{
    import dstats : logNormalCDF, logNormalCDFR, logNormalPDF;

    alias cdf = logNormalCDF;
    alias cdfr = logNormalCDFR;
    alias pdf = logNormalPDF;
    alias density = pdf;
    //no icdf for LogNormal in dstats
}

private void hasMemberCheck(alias T, string x)()
{
    import std.traits : hasMember;
    static assert(hasMember!(T, x), T.stringof ~ " must have " ~ x ~" member to
                                        call " ~ x ~ " function");
}

auto cdf(alias T, U...)(U u)
{
    hasMemberCheck!(T, "cdf");

    return T.cdf(u);
}

auto pdf(alias T, U...)(U u)
{
    hasMemberCheck!(T, "pdf");

    return T.pdf(u);
}

auto pmf(alias T, U...)(U u)
{
    hasMemberCheck!(T, "pmf");

    return T.pmf(u);
}

auto cdfr(alias T, U...)(U u)
{
    hasMemberCheck!(T, "cdfr");

    return T.cdfr(u);
}

auto density(alias T, U...)(U u)
{
    hasMemberCheck!(T, "density");

    return T.density(u);
}

auto icdf(alias T, U...)(U u)
{
    hasMemberCheck!(T, "icdf");

    return T.icdf(u);
}

void main()
{
    writeln(Normal.cdf(0.5, 0.0, 1.0));
    writeln(cdf!Normal(0.5, 0.0, 1.0));
    writeln(icdf!Normal(cdf!Normal(0.5, 0.0, 1.0), 0.0, 1.0));

    writeln(LogNormal.cdf(0.5, 0.0, 1.0));
    writeln(cdf!LogNormal(0.5, 0.0, 1.0));
    //writeln(icdf!LogNormal(cdf!LogNormal(0.5, 0.0, 1.0), 0.0, 1.0));
    //error
}
August 25, 2017
On Friday, 25 August 2017 at 14:30:03 UTC, jmh530 wrote:
> On Friday, 25 August 2017 at 01:04:31 UTC, data pulverizer wrote:
>> [snip]
>
> With respect to your point about immutability, you might be interested in the parameterize function in dstats.distrib. I hadn't noticed that was there, but I think it accomplishes, to a limited extent, the behavior of what you want. It returns a delegate with the values of the distribution fixed in there.
>
> Along the same lines, I think below is how I would set it up, rather than the mixin approach I discussed above. While it does not currently work with the parametrize funciton currently, I believe that with some simple adjustments it could.
>
>
> import std.stdio : writeln;
>
> struct Normal
> { ...

Your wrapping strategy looks sensible though I would probably generate them all using string mixins.


August 25, 2017
On Friday, 25 August 2017 at 16:01:27 UTC, data pulverizer wrote:
>
> Your wrapping strategy looks sensible though I would probably generate them all using string mixins.

That might require less maintenance going forward.
August 25, 2017
On Friday, 25 August 2017 at 16:01:27 UTC, data pulverizer wrote:
>
> Your wrapping strategy looks sensible though I would probably generate them all using string mixins.

See below. I haven't implemented the random variables yet, but otherwise it seems to be working well. There is some trickiness with deprecated stuff that I had to hard code, but other than that it's pretty generic. Also, I think it is ignoring my check to only include public/export stuff. Not sure why that is.

module distribAlt;

private template isMemberOf(alias T, string x)
{
    import std.traits : hasMember;

    enum bool isMemberOf = hasMember!(T, x);
}

private void hasMemberCheck(alias T, string x)()
{
    static assert(isMemberOf!(T, x), T.stringof ~ " must have " ~ x ~" member to
                                        call " ~ x ~ " function");
}

private string genStructInternals(string funcName, string structName)()
{
    import dstats.distrib;
    import std.array : appender;
    import std.algorithm.searching : endsWith;

    enum spaces = "    ";

    auto aliasBuf = appender!string();
    auto importBuf = appender!string();

    enum string invName = "inv" ~ structName;

    enum bool anyPDForPMF = false;

    importBuf.put(spaces);
    importBuf.put("import dstats.distrib : ");

    foreach(member; __traits(allMembers, dstats.distrib))
    {
        static if (__traits(getProtection, member) == "public" ||
                   __traits(getProtection, member) == "export")
        {
            import std.algorithm.searching : startsWith, findSplitAfter;
            import std.string : toLower;

            static if (startsWith(member, funcName))
            {
                enum string memberAfter = findSplitAfter(member, funcName)[1];
                enum string lowerMemberAfter = toLower(memberAfter);

                importBuf.put(member ~ ", ");

                aliasBuf.put(spaces);
                aliasBuf.put("alias " ~ lowerMemberAfter ~ " = "
                                                                    ~ member ~ ";");
                aliasBuf.put("\n");

                static if ((lowerMemberAfter == "pdf") ||
                           (lowerMemberAfter == "pmf"))
                {
                    aliasBuf.put(spaces);

                    aliasBuf.put("alias density = " ~ lowerMemberAfter ~ ";");
                    aliasBuf.put("\n");
                }
            }
            else static if (startsWith(member, invName))
            {
                enum string memberAfter = findSplitAfter(member, invName)[1];

                importBuf.put(member ~ ", ");

                aliasBuf.put(spaces);
                aliasBuf.put("alias i" ~ toLower(memberAfter) ~ " = "
                                                                    ~ member ~ ";");
                aliasBuf.put("\n");
            }
        }
    }

    if (endsWith(importBuf.data, ", "))
    {
        string importOut = importBuf.data[0 .. ($ - (", ".length))] ~";\n";

        if (endsWith(aliasBuf.data, "\n"))
            return importOut ~ aliasBuf.data[0 .. ($ - ("\n").length)];
        else
            assert(0, "No relevant functions in dstats.distrib");
    }
    else
    {
        assert(0, "No relevant functions in dstats.distrib");
    }
}

private string toLowerFirst(string name)()
{
    import std.string : toLower;
    import std.conv : to;

    string firstLetter = name[0].toLower.to!string;
    return firstLetter ~ name[1 .. $];
}

private string toUpperFirst(string name)()
{
    import std.string : toUpper;
    import std.conv : to;

    string firstLetter = name[0].toUpper.to!string;
    return firstLetter ~ name[1 .. $];
}

private template GenDistStruct(string name)
{
    const char[] GenDistStruct =
        "///"~ "\n" ~
        "struct " ~ toUpperFirst!(name) ~ "\n" ~
        "{\n" ~
        genStructInternals!(name, toUpperFirst!(name)) ~ "\n" ~
        "}";
}

string GenDistStructs()
{
    import dstats.distrib;
    import std.array : appender;
    import std.algorithm.searching : startsWith, endsWith, canFind,
                                                findSplitBefore, findSplitAfter;

    string[__traits(allMembers, dstats.distrib).length] createdStructs;
    size_t i;
    auto structsBuf = appender!string();

    foreach(member; __traits(allMembers, dstats.distrib))
    {
        static if (__traits(getProtection, member) == "public" ||
                   __traits(getProtection, member) == "export")
        {
            static if ((member.endsWith("PDF") ||
                            member.endsWith("PMF") ||
                            member.endsWith("CDF") ||
                            member.endsWith("CDFR")))
            {
                static if (member.endsWith("PDF"))
                    enum string memberBefore =
                                              findSplitBefore(member, "PDF")[0];
                else static if (member.endsWith("PMF"))
                    enum string memberBefore =
                                              findSplitBefore(member, "PMF")[0];
                else static if (member.endsWith("CDF"))
                    enum string memberBefore =
                                              findSplitBefore(member, "CDF")[0];
                else static if (member.endsWith("CDFR"))
                    enum string memberBefore =
                                             findSplitBefore(member, "CDFR")[0];

                static if (member.startsWith("inv"))
                    enum string newMember =
                          toLowerFirst!(findSplitAfter(memberBefore, "inv")[1]);
                else
                    enum string newMember = memberBefore;

                static if (member != "chiSqrCDF" &&
                           member != "chiSqrCDFR" &&
                           member != "invChiSqrCDFR" &&
                           member != "invChiSqCDFR") //Deprecated: Easiest way I found to fix it
                {
                    if (i == 0 ||
                               !(createdStructs[0 .. i].canFind(newMember)))
                    {
                        structsBuf.put(GenDistStruct!newMember);
                        structsBuf.put("\n");
                        createdStructs[i] = newMember;
                        i++;
                    }
                }
            }
        }
    }
    return structsBuf.data;
}

mixin(GenDistStructs());

private template GenDistFunc(string name)
{
    const char[] GenDistFunc =
        "auto " ~ name ~ "(alias T, U...)(U u)\n" ~
        "{\n" ~
        `   hasMemberCheck!(T, "` ~ name ~ `");` ~ "\n" ~
        "   return T." ~ name ~ "(u);\n" ~
        "}";
}

mixin(GenDistFunc!("pdf"));
mixin(GenDistFunc!("pmf"));
mixin(GenDistFunc!("cdf"));
mixin(GenDistFunc!("cdfr"));
mixin(GenDistFunc!("icdf"));
mixin(GenDistFunc!("density"));

void main()
{
    import std.stdio : writeln;

    writeln(Normal.cdf(0.5, 0.0, 1.0));
    writeln(cdf!Normal(0.5, 0.0, 1.0));
    writeln(icdf!Normal(cdf!Normal(0.5, 0.0, 1.0), 0.0, 1.0));

    writeln(LogNormal.cdf(0.5, 0.0, 1.0));
    writeln(cdf!LogNormal(0.5, 0.0, 1.0));
}
August 26, 2017
On Friday, 25 August 2017 at 20:54:05 UTC, jmh530 wrote:
> See below. I haven't implemented the random variables yet, but otherwise it seems to be working well. There is some trickiness with deprecated stuff that I had to hard code, but other than that it's pretty generic. Also, I think it is ignoring my check to only include public/export stuff. Not sure why that is.
>
> module distribAlt;
> ...

Wow, I didn't realise that you'd go off and work on it. You need to start a package for it! I'll bookmark this one as a little reminder of mixin techniques.


August 26, 2017
On Saturday, 26 August 2017 at 02:14:59 UTC, data pulverizer wrote:
> On Friday, 25 August 2017 at 20:54:05 UTC, jmh530 wrote:
>> See below. I haven't implemented the random variables yet, but otherwise it seems to be working well. There is some trickiness with deprecated stuff that I had to hard code, but other than that it's pretty generic. Also, I think it is ignoring my check to only include public/export stuff. Not sure why that is.
>>
>> module distribAlt;
>> ...
>
> Wow, I didn't realise that you'd go off and work on it. You need to start a package for it! I'll bookmark this one as a little reminder of mixin techniques.

Something I had wanted for a long time and once your article got my juices flowing. I had a hard time stopping once you got me started!

I'm going to try to add support for the random number generators and then create a PR for dstats.
August 28, 2017
On Thursday, 24 August 2017 at 23:50:21 UTC, data pulverizer wrote:
> I find OOP-polymorphic types ultimately unsatisfying, but I don't know of anyway to write, compile and load a D script with new types and methods on the fly into the same session.

That is why binding membership and polymorphism together is a historical wrong turn. CLOS had it right but the world followed the Simula/Smalltalk path because of a nice metaphor (objects sending messages to each other).

My openmethods library allows you to add methods "from outside" and also supports dynamic loading: you can add new methods to existing classes and new classes to hierarchies that have methods. See the blog post that just came up.



August 28, 2017
On Thursday, 24 August 2017 at 23:50:21 UTC, data pulverizer wrote:
> I find OOP-polymorphic types ultimately unsatisfying, but I don't know of anyway to write, compile and load a D script with new types and methods on the fly into the same session.

That is why binding membership and polymorphism together is a historical wrong turn. CLOS had it right but the world followed the Simula/Smalltalk path because of a nice metaphor (objects sending messages to each other).

My openmethods library allows you to add methods "from outside" and also supports dynamic loading: you can add new methods to existing classes and new classes to hierarchies that have methods. See the blog post that just came up.