October 26, 2015
On 26-Oct-2015 12:16, Jacob Carlborg wrote:
> On 2015-10-25 19:23, Dmitry Olshansky wrote:
>
>> I humbly believe that D may just add special re-write rule to the switch
>> statement in order to allow user-defined switchable types. This goes
>> along nicely with the trend - e.g. foreach statement works with anything
>> having static range interfaces or opApply.
>
> Do you think that could handle all different type of patterns? For
> example extractor patterns:
>
> match x {
>      case Foo(bar) => println(bar)
> }
>
> The above is Scala.
>

Scala does it with a bit of re-writes and partially hard-wired logic (mostly to optimize). I don't see a problem with it and D would be better off with something similar.

Current switch is both too lax (accepts variables) and too rigid (only integers, string and soon(?) pointers).

-- 
Dmitry Olshansky
October 26, 2015
On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote:
> On Sunday, 25 October 2015 at 06:22:51 UTC, TheFlyingFiddle wrote:
> You could also emulate constant matching using default parameters (albeit with the restriction that they must be after any non-default/constant parameters), since the defaults form part of the function's type. I tried making something like this earlier this summer and it'd check that a given value was first equal to the default parameter and match if so, or match if there was no default parameter but the types matched.
>
> e.g.
> //template ma(tch/g)ic
>
> unittest
> {
>     Algebraic!(string, int, double, MyStruct) v = 5;
>     string s = v.match!(
>         (string s = "") => "Empty string!",
>         (string s) => s,
>         (int i = 7) => "Lucky number 7",
>         (int i = 0) => "Nil",
>         (int i) => i.to!string,
>         (double d) => d.to!string,
>         (MyStruct m = MyStruct(15)) => "Special MyStruct value",
>         (MyStruct m) => m.name, //
>         () => "ooer");
>     writeln(s);
> }
>
> It's a bit ugly overloading language features like this, but it makes the syntax a little prettier.
This does look nicer indeed.

>>Why not just use a value as an extra argument:
>>v.match!(
>>    7, (int i) => "Lucky number 7"
>>);
I like this you could go further with this to allow any number of constants.
v.match!(
    5, 7,   i => "Was: " ~ i.to!string,
    (int i)   => "Was this: " ~ i.to!string);

Or for ranges.
v.match!(
    MatchR!(1, 10), i => "Was: " ~ i.to!string, //Matches 1 .. 10
    (int i)          => "Was this: " ~ i.to!string);

> I'd really like to see proper pattern matching as a language-level feature however; for all the emulating it we can do in D, it's not very pretty or friendly and optimising it is harder since the language has no concept of pattern matching.
One could probably get something like this:

int i = 5;
string s = i.match!(
   5, 7, n => "Five or seven",
   MatchR!(10, 100), n => "Was between ten and a hundred",
   (n)     => "Was: " ~ n.to!string);

to fold into something like this:

void match(T...)(int i)
{
   switch(i)
   {
      case 5: case 7: return (T[2])!int(i);
      case 10: .. case 99: return (T[3])!int(i);
      default: return (T[4])!int(i);
   }
}

int i = 5;
string s = match!(/* lambdas and whatnot */), i);

With some template/ctfe and string mixings magic.

In-lining, constant folding etc could probably just reduce it to
the equvalent of:

int i    = 5;
string s = "Five or seven";

(if there is really good constant folding :P)

It might however generate lot's of useless symbols in the resulting code
making code size's larger.

> Things like Option (and other ADTs) are lovely, but really need good pattern matching to become worthwhile IMO (e.g. Java Optional<T> has a get() method that throws on empty, which undermines the main reason to use optional -
Another thing that has always bothered me with Optional<T> in Java in addition to this is that the optional value itself might be null. So to write robust code you first have to check against null on the option value :P.

> Scala's Option is really nice on the other hand since you can/should pattern match).
Don't really see a point in an optional type if can access the underlying
value without first checking if it's there.




October 26, 2015
On Monday, 26 October 2015 at 14:13:20 UTC, TheFlyingFiddle wrote:
> On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote:
>> Scala's Option is really nice on the other hand since you can/should pattern match).
> Don't really see a point in an optional type if can access the underlying
> value without first checking if it's there.

The key difference with (exhaustive) pattern matching is that it *is* the check that the value is there. Pattern matching enforces the existence of an on-nothing clause for Optional, on-error for Error, on-Leaf and on-Branch for Bintrees, etc.
And even with nice higher-order functions, plain pattern matching is quite valuable for finely controlled error/resource handling, and I often see it in Rust code as well as Scala (and I've seen it used in Haskell occasionally too). A brief, contrived example use-case:

//External code that disallows monadic int[]
void processThatMustOccur(int[] data);
...
Option!File findFile(string fname);
Result!(int[]) parseInts(File file);

//verbose for clarity
void parseMatches(string path) {
    Option!File ofile = path.findFile();

    //Static guarantee of handling value not present
    ofile match {
        None() => {
            //Handle error for no file found, retry with new path
        }
        //The existence of file is effectively proof that ofile is present
        Some(file) => {
            Option!(int[]) odata = file.parseInts();
            odata match {
                Success(data) => processThatMustOccur(preProcess(data));
                Error(error) =>
                    //Handle error for bad parse, backtrack depends on error
            }
        }
    }

    //Continue after processing data
}

void parseMonadic(string path) {
    path.findFile()
        .bind!parseInts()
        .bind!(map!preProcess)
        .bind!processThatMustOccur()
        .onFailure!backTrack
        //How can we backtrack a variable amount easily?

    //Continue after processing data
}

The error control loss can be mostly avoided by using an appropriate error monad or API design, but there's still the issue of interfacing with non-monadic code.
It essentially provides a guarantee that the equivalent of 'T get();' will handle errors, like having a checked exception version 'T get() throws OnNotPresent;' instead. It also scales up much better than having these checked exceptions on not-present ADT accesses.
October 26, 2015
On Monday, 26 October 2015 at 15:58:38 UTC, Edmund Smith wrote:
> On Monday, 26 October 2015 at 14:13:20 UTC, TheFlyingFiddle wrote:
>> On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote:
>>> Scala's Option is really nice on the other hand since you can/should pattern match).
>> Don't really see a point in an optional type if can access the underlying
>> value without first checking if it's there.
>
> The key difference with (exhaustive) pattern matching is that it *is* the check that the value is there. Pattern matching enforces the existence of an on-nothing clause for Optional, on-error for Error, on-Leaf and on-Branch for Bintrees, etc.
> And even with nice higher-order functions, plain pattern matching is quite valuable for finely controlled error/resource handling, and I often see it in Rust code as well as Scala (and I've seen it used in Haskell occasionally too). A brief, contrived example use-case:

What I meant is that I don't really see the point in optionals that look something like this:

struct Optional(T)
{
   T value;
   bool empty;

   ref T get()
   {
      enforce(!empty, "Value not present!");
      return value;
   }

   //Stuff...
}

Optional!(int[]) doSomething(...);
void process(int[]);

void foo()
{
    Optional!(int[]) result = doSomething(...);
    process(result.get());
}

I mean sure you get a null check in foo instead of in process but this style of writing code does not really give you much of an advantage since you can't really handle the errors much better then you could a Null exception.

If you instead use pattern matching as in your example you have much better context information that can actually help you do something in the case a value is not there.
October 27, 2015
On Monday, 26 October 2015 at 16:42:27 UTC, TheFlyingFiddle wrote:
> If you instead use pattern matching as in your example you have much better context information that can actually help you do something in the case a value is not there.

Probably possible:

Some!T get(T)(Option!T item) {
    Some!T r;
    //Static guarantee of handling value not present
    item match {
        None() => {
            throw new Exception("empty!");
        }
        Some(t) => {
            r=t;
        }
    }

    return r;
}

Then:
Option!File file;
Some!File s = file.get();
October 27, 2015
On Tuesday, 27 October 2015 at 07:55:46 UTC, Kagamin wrote:
> On Monday, 26 October 2015 at 16:42:27 UTC, TheFlyingFiddle wrote:
>> If you instead use pattern matching as in your example you have much better context information that can actually help you do something in the case a value is not there.
>
> Probably possible:
>
> Some!T get(T)(Option!T item) {
>     Some!T r;
>     //Static guarantee of handling value not present
>     item match {
>         None() => {
>             throw new Exception("empty!");
>         }
>         Some(t) => {
>             r=t;
>         }
>     }
>
>     return r;
> }
>
> Then:
> Option!File file;
> Some!File s = file.get();

Sure that would work but i don't see that it's different then an enfore since you don't have access the context where get is invoked so you can't really do anything with it.

Contrived Example:

void foo()
{
   Option!File worldFile = getAFile("world.json");
   auto world  = parseJSON(worldFile.get());
   Option!File mapFile = getAFile(world["map"]);
   auto map    = parseJSON(mapFile.get());
   //Other stuff.
}

Let's say we get an NoObjectException, this tells us that either the world.json file did not exist or the map file does not exist. get does not have access to that context so we wouldn't be able to tell. This next example would fix this.

void foo()
{
   Option!File worldFile = getAFile("world.json");
   enforce(worldFile.hasValue, "Error while loading file: world.json");
   auto world = parseJSON(worldFile.get());
   Option!File mapFile = getAFile(world["map"]);
   enforce(mapFile.hasValue, "Error while loading file: " ~ world["map"]);
   auto map = parseJSON(mapFile.get());
   //Other stuff
}

Now we know which file failed to load. But we bypassed the NoObjectException to do it.

I would prefer this style instead.
void foo()
{
  Option!File worldFile = getAFile("world.json");
  match worldFile {
     Some(value) => {
         auto world          = parseJSON(value);
         Option!File mapFile = getAFile(world["map"]);
         match mapFile {
            Some(mapf) => {
               auto map = parseJSON(mapf);
               //Do something here.
            },
            None => enforce(false, "Failed to load: " ~ world["map"]);
         }
     },
     None => enforce(false, "Failed to load: world.json");
   }
}

The reason that I prefer that is not that I like the syntax really. It's just that if the only way to get a value is to pattern match on it then you are forced to consider the case where the value was not there.


Guess a D version without language support would look something like:
void foo()
{
  auto worldFile = getAFile("world.json");
  worldFile.match!(
     (File worldf) {
        auto world = parseJSON(value);
        auto mapFile = getAFile(world["map"]);
        mapFile.match!(
           (File mapf)
           {
              auto map = parseJSON(mapf);
              //Do stuff;
           },
           (None) => enforce(false, "Failed to load: " ~ world["map"]);
     },
     (None) => enforce(false, "Failed to load: world.json")
  );
}

The example here is very contrived. Here we just throw exceptions if the file could not load and if that is all we do we should just wrap getAFile instead but i hope you get my point.




October 27, 2015
On Tuesday, 27 October 2015 at 15:06:07 UTC, TheFlyingFiddle wrote:
> The reason that I prefer that is not that I like the syntax really. It's just that if the only way to get a value is to pattern match on it then you are forced to consider the case where the value was not there.

If pattern matching is the only way, the get function above will still work: it uses only pattern matching, nothing else.
October 27, 2015
On Tuesday, 27 October 2015 at 15:06:07 UTC, TheFlyingFiddle wrote:
> I would prefer this style instead.
> void foo()
> {
>   Option!File worldFile = getAFile("world.json");
>   match worldFile {
>      Some(value) => {
>          auto world          = parseJSON(value);
>          Option!File mapFile = getAFile(world["map"]);
>          match mapFile {
>             Some(mapf) => {
>                auto map = parseJSON(mapf);
>                //Do something here.
>             },
>             None => enforce(false, "Failed to load: " ~ world["map"]);
>          }
>      },
>      None => enforce(false, "Failed to load: world.json");
>    }
> }

This can arguably already be done cleaner in D.

if (auto worldFile = getAFile("world.json"))
{
    auto world = parseJSON(worldFile);
    if (auto mapFile = getAFile(world["map"))
    {
        //...
    }
    else enforce(false, "Failed to load: " ~ world["map"]);
}
else enforce(false, "Failed to load: world.json");

Or even:

auto worldFile = enforce(getAFile("world.json"), "Failed to load: world.json");
auto world = parseJSON(worldFile);
auto mapFile = enforce(getAFile(world["map"]), "Failed to load: " ~ world["map"]);
//...

From what I've seen in the Rust community, they try to avoid using match as it's very syntactically heavy. They have all kinds of idioms to avoid doing matches on Option, such as the try! macro, unwrap_or, unwrap_or_else, etc.

That being said, pattern matching has been one of my most-wanted D features for years.
October 27, 2015
On Tuesday, 27 October 2015 at 17:48:04 UTC, Meta wrote:
> On Tuesday, 27 October 2015 at 15:06:07 UTC, TheFlyingFiddle wrote:
> This can arguably already be done cleaner in D.
>
> if (auto worldFile = getAFile("world.json"))
> {
>     auto world = parseJSON(worldFile);
>     if (auto mapFile = getAFile(world["map"))
>     {
>         //...
>     }
>     else enforce(false, "Failed to load: " ~ world["map"]);
> }
> else enforce(false, "Failed to load: world.json");
>
> Or even:
>
> auto worldFile = enforce(getAFile("world.json"), "Failed to load: world.json");
> auto world = parseJSON(worldFile);
> auto mapFile = enforce(getAFile(world["map"]), "Failed to load: " ~ world["map"]);
> //...
>
> From what I've seen in the Rust community, they try to avoid using match as it's very syntactically heavy. They have all kinds of idioms to avoid doing matches on Option, such as the try! macro, unwrap_or, unwrap_or_else, etc.
>
> That being said, pattern matching has been one of my most-wanted D features for years.

Yes this is much cleaner. But it does not really force a user to consider the empty case.

I mean this would still compile.
auto worldFile = getAFile("world.json");
auto world     = parseJSON(worldFile);
auto mapFile   = getAFile(world["map"]);
auto map       = parseJSON(mapFile);

What I was after was a way to at compile time ensure that all accesses to the value in the optional type are considered. From my uses of Maybe in haskell i know this get's annoying quick so I dunno if it's a good thing. But atleast you would know that in all places that optionals are accessed a handler for the empty case would be present.
1 2 3
Next ›   Last »