Skip to content

Comments

Add new algorithm: either()#3395

Merged
andralex merged 1 commit intodlang:masterfrom
nordlow:either-and-every
Oct 29, 2015
Merged

Add new algorithm: either()#3395
andralex merged 1 commit intodlang:masterfrom
nordlow:either-and-every

Conversation

@nordlow
Copy link
Contributor

@nordlow nordlow commented Jun 8, 2015

Thanks to D's lazy parameters this new variadic function either simplify pattern matching.

either() is similar to Python/Lisp's or. Same as Phobos's any but returns first parameter value which implicitly convert to true, or last argument otherwise. I guess any could be modified but that would cause breakage on its return type.

I've found real use of either()in various parsing contexts.

I'm very open to changing the namings if you feel they are misguiding.

Note that either currently cannot be nothrow because of issue https://issues.dlang.org/show_bug.cgi?id=12647

@nordlow nordlow changed the title Add new algorithms either(), every() and tryEvery() Add new algorithms: either(), every() and tryEvery() Jun 8, 2015
@DmitryOlshansky
Copy link
Member

2 things to note:

  • lazy had awful codegen last time I've seen it
  • tryEvery is quite non-obvious in how it expects each lazy parameter to affect the same haystack it then restores on failure. A simpler approach is to just make each parameter a callable that accepts ref S whole.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These array appends would be highly problematic for the performance of a parser...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I return fixed length bool array instead.

@DmitryOlshansky
Copy link
Member

@nordlow Please do not merge with master, do:

 # if upstream is dlang/phobos  remote
git pull --rebase upstream master

@nordlow
Copy link
Contributor Author

nordlow commented Jun 12, 2015

I just realized that it's better for tryEvery to return either a

  • a bool true upon matching of all arguments, false otherwise,
  • a size_t counter that somehow indicates how many arguments that could be matched or
  • a Voldemort struct with count member and implicit bool conversion rules that enables the nice idiom
if (const hit = haystack.tryEvery(needles...) 
{
    // do something with `hits.count`
}

I prefer the last alternative as it will harmonize with the interface of the pending

#3288

What do you guys think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, two things:

  1. Could you please try whether it's possible to accept/return by reference? I.e. either(a[0], a[1]) = 1; should work. But I don't think auto ref cooperates nicely with lazy.
  2. A predicate would help, e.g. choose either non-empty string: auto s = either!(s => !s.empty)(param, "default");. In fact I think such use would be most useful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to ensure here that CommonType!Ts is not void.

@MetaLang
Copy link
Member

I'd like to propose a different implementation for either. I think an iterative approach is preferable for efficiency reasons.

CommonType!Ts either(Ts...)(lazy Ts a) 
if (a.length >= 1 && !is(CommonType!Ts == void))
{
    foreach (ai; a)
        if (ai) 
            return ai;

    return a[$ - 1];
}

@CyberShadow
Copy link
Member

So either/every are any/all except they return non-bool? Does the distinction make them useful enough to add considering the overlap in function? Can we change any/all instead?

Yes, either/every operate on a tuple, and any/all on a range. But seeing as the functions require that they have a common type, you can use any/all with only, right?

tryEvery seems too specialized for Phobos.

@DmitryOlshansky
Copy link
Member

tryEvery seems too specialized for Phobos.

+1 I'd also point out non-obvious conventions in its usage.

@nordlow nordlow force-pushed the either-and-every branch from 8c5c1ec to 12bb64e Compare June 25, 2015 10:42
@nordlow
Copy link
Contributor Author

nordlow commented Jun 25, 2015

Made either and every iterative instead.

@nordlow nordlow changed the title Add new algorithms: either(), every() and tryEvery() Add new algorithms: either(), every() and tryAll() Jun 25, 2015
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will return a[$ - 1] even if it's falsy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So which lvalue should be returned if all parameters bool-convert to false? We can't use CommonType!Ts.init because that's an rvalue. Any ideas on this, @andralex?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the only sensible thing to do would be to throw an exception or assert, but that seems too heavy for such a small function. Maybe have the user provide a default value? It doesn't really make sense, but I don't see much of an alternative. What do Python and Lisp do when all values evaluate to false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python:

assert((0 or False) is False)
assert((False or 0) is 0)

assert((False and 0) is False)
assert((0 and False) is 0)

assert((0 or None) is None)
assert((None or 0) is 0)

Emacs Lisp:

(and 0 nil) => nil
(and nil 0) => nil

(or 0 nil) => 0
(or nil 0) => 0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the important thing here is to have the same behaviour for l-values as for r-values. If so I see three alternatives here:

  1. Mimic Python's behaviour (in order to get same behaviour for l-value and r-value returns);
    • either returns last argument upon no true-conversion
    • every returns first argument upon no true-conversion
  2. If default value is supplied return it upon failure for r-value overloads of both either and every. For the l-value overload we could force the default value to be an l-value via ref parameter in this case, right?
  3. Throw Exception (if no default value is supplied).

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping. I'm sensing we need a voting on this matter.

@andralex ?

My standing point here is a behaviour that is easy to remember.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be fine as is

@nordlow
Copy link
Contributor Author

nordlow commented Jun 27, 2015

The latest commit contains a todo; there's a problem with the conversion trait. It doesn't work with arrays. Is there an alternative trait?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just nothrow, it's all function attributes.

edit:

Or not. Huh.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should I remove this comment for now or tell that no attributes can be inferred?

@andralex
Copy link
Member

Couple more nits and we're ready to go... again :o).

@nordlow nordlow force-pushed the either-and-every branch 3 times, most recently from 504017b to da6fd86 Compare October 29, 2015 16:48
@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

After my latest changes in the first either-overload we need to restrict it to not allow it when all variables are l-values. This in order for the second overload to kick in. How do I do that? Because of this the unittest for either now deliberatly fails as

comparison.d(2017,11): Error: either(x, delegate int() => y) is not an lvalue

@andralex
Copy link
Member

Regarding lvalues: I'd say let's just forgo them.

@nordlow nordlow force-pushed the either-and-every branch 3 times, most recently from ec32762 to f86922f Compare October 29, 2015 19:35
@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

  • Ok @andralex , removed l-value ref overload
  • Updated to use pred(T.init) instead of pred(T())
  • Removed array-check from ifTestable().
  • Moved ifTestable to std.traits
  • Added docs to ifTestable
  • Added static unittests for ifTestable to prove that pred is indeed evaluated

IMO, we're ready.

@nordlow nordlow force-pushed the either-and-every branch 2 times, most recently from 71e550f to c91ed83 Compare October 29, 2015 19:59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just replace these two lines with allSatisfy!(ifTestable, T, Ts)

@andralex
Copy link
Member

One last nit. Then I preapprove (it sounds... oxymoronic).

@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

Done!

@andralex
Copy link
Member

Auto-merge toggled on

@andralex
Copy link
Member

thx!

@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

Great!

@nordlow
Copy link
Contributor Author

nordlow commented Oct 29, 2015

Just realized that we can make use of typeof(return) in

CommonType!(T, Ts) either(alias pred = a => a, T, Ts...)(T first, lazy Ts alternatives)
    if (alternatives.length >= 1 &&
        !is(typeof(return) == void) &&
        allSatisfy!(ifTestable, T, Ts))

Would that be preferrable in other PRs to reduce redundancy and perhaps number of instantiation tries?

andralex added a commit that referenced this pull request Oct 29, 2015
@andralex andralex merged commit 168d96d into dlang:master Oct 29, 2015
@JakobOvrum
Copy link
Contributor

This still isn't passing the predicate to ifTestable, despite my comments...

@nordlow
Copy link
Contributor Author

nordlow commented Oct 30, 2015

Should we revert and fix, @andralex ?

@andralex
Copy link
Member

@nordlow just create a new PR. Thanks @JakobOvrum

@nordlow
Copy link
Contributor Author

nordlow commented Oct 30, 2015

Ok, I can add the typeof(return) in it too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants