October 31, 2017
On Thursday, 26 October 2017 at 23:29:24 UTC, Walter Bright wrote:
> You can also do things like:
>
> --- s.d -------
> struct S { int x; ref int X() { return x; } }
>
> --- splus.d ----
> public import s;
> int increment(S s) { s.X() += 1; } // note no access to S.x
>
> --- user.d ----
> import splus;
> void foo(ref S s) { s.increment(); }

I'd like to do this too:
-------------------------
import std.stdio;

void main()
{
    //int foo;
    if (int foo.bar != 0)  // no, sorry, you cannot declare foo here.
    {
        throw new Exception("foo.bar != 0");
    }
}

auto bar(int x,)
{
    return -10;
}

-----------------------------------------
October 31, 2017
On Tuesday, 31 October 2017 at 01:44:58 UTC, codephantom wrote:
> On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
>>
>> But in D, UFCS allows obj.func() to work for both member functions and free functions, so if the client code uses the obj.func() syntax, it won't have to care about the difference.
>>
>
> I don't like it.
>
> When I see obj.func(), to me, func() is a member function. Why should I spend any time trying to work out whether it's a member function or a free function? It doesn't make sense to me.

You dont need to know how its implemented. There's no benefit to knowing. It's a waste of time / distraction to even think about it.


October 31, 2017
On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
>
> Yeah, the whole "private is module-private, not aggregate-private" throws a monkey wrench into the works.  I can understand the logic behind module-private vs. aggregate-private, but sometimes you really *do* want aggregate-private, but D doesn't let you express that except via splitting things up into submodules, which is a lot of overhead for minor payback.
> 

Have you ever heard of the difference in how private works in D vs. C++ ever causing any problems when calling D code from C++, or vice-versa?

I imagine if there is an issue with calling C++ code from D, you can do what you say wrt submodules, but I'm not sure it really matters or not.
October 31, 2017
On Tue, Oct 31, 2017 at 01:44:58AM +0000, codephantom via Digitalmars-d wrote:
> On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
> > 
> > But in D, UFCS allows obj.func() to work for both member functions
> > and free functions, so if the client code uses the obj.func()
> > syntax, it won't have to care about the difference.
> > 
> 
> I don't like it.
> 
> When I see obj.func(), to me, func() is a member function. Why should
> I spend any time trying to work out whether it's a member function or
> a free function? It doesn't make sense to me.

The point is, you *shouldn't have to care*.  If I use a library L that provides some type T, and the library provides operation X that I can perform on type T, all I care about is that variables of type T can have X performed on it.  Is X a member function? A free function?  Who cares! That's just an implementation detail. As the user of library L, how T and X are implemented are none of my business.  X can be a remote procedure call to a network service for all I care -- my code doesn't have to know.  All it needs to know is that type T can have operation X performed on it.

That's the whole point of encapsulation.  You *don't have to know* how something is implemented.  In fact, it's better that you don't, because later when you upgrade library L, and X changes from member function to free function, or vice versa, *your code doesn't have to change at all*. That's why we care about encapsulation in the first place.  Why should *I* have to change my code just because the upstream authors of library L decides that X is better implemented as a free function vs. a member function, or vice versa?  I don't care, and I shouldn't have to care. Having the same interface (i.e., UFCS syntax) to call X regardless of how it's implemented is a big advantage, because it frees me, the downstream user, from needing to care about fiddly details of library L's implementation.  Instead of upgrading library L and then having to spend 10 hours fixing all my function calls to X, I can just upgrade library L and let the compiler figure out which calls are member functions and which are free functions. My code doesn't have to change one bit.  Let the machine do the grunt work, free up the human to do the higher level stuff.

The one case where the difference matters is when you're trying to debug something.  In that case, I'd say the onus is really upon the debugger to tell you what kind of function it was.  Surely the debugger must have this information; it's just a matter of conveying the information to the user adequately.  If current tools don't allow you to do this easily, that's a problem with the tools, not with the concept of encapsulation.


T

-- 
Государство делает вид, что платит нам зарплату, а мы делаем вид, что работаем.
October 31, 2017
On Tue, Oct 31, 2017 at 08:45:11AM +0000, Dukc via Digitalmars-d wrote:
> On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
> > For example, suppose you're using a proprietary library that provides a class X that behaves pretty closely to a range, but doesn't quite have a range API.  (Or any other API, really.)  Well, that's not a problem, you just write free functions that forward to class X's methods to bridge the API gap, and off you go.  You don't have to work with your upstream provider, who may not be able to provide a fix until months later, and you don't have to create all sorts of wrapper types just to adapt one API to another.
> 
> Yes, the idea here is great. It will work with range functions you define.  The problem is, it won't work with Phobos functions because they do not see your extensions to that proprietary type. They see only the true member functions of it.
> 
> Unless you hack phobos and add your extensions to that propietary library there, which would be a very ugly solution.
> 
> You can of course wrap that proprietary range type but that is a lot of manual work and requires maintenance. Alias this solves some cases but not all of them.
> 
> I am not sure what would be the best way for the language to handle this but for sure not the present way. The idiom is otherwise so great.

Haha, I knew somebody would bring this up.  I don't have a good solution to this either.  The problem is that you can't export the type to Phobos along with the additional UFCS stuff you tacked on to it.  I think Phobos itself contains several hacks in order to work around this problem for basic types, like importing std.array in generic code that doesn't actually reference any arrays, but the import is necessary so that arrays retain their range API.  Similar problems arise when you pass user-defined types to Phobos where some methods are implemented as UFCS.

The basic problem is that when a generic function in Phobos looks up a method of type T, the lookup is done *in the scope of the Phobos module*, not the caller's scope that passed in the T in the first place. For example:

	/* usertype.d */
	module usertype;
	struct UserType {
		int memberMethod(Args...)(Args args);
	}

	/* ufcs.d */
	module ufcs;
	import usertype;
	int ufcsMethod(Args...)(UserType u, Args args);

	/* main.d */
	module main;
	import usertype, ufcs;
	void main() {
		UserType u;
		int x, y, z;

		// Lookup happens in module main, function main.  Since
		// usertype.UserType is visible here, .memberMethod
		// resolves to usertype.UserType.memberMethod.
		u.memberMethod(x, y, z);

		// Lookup happens in module main, function main.  Since
		// ufcs.ufcsMethod is visible here, .ufcsMethod resolves
		// to ufcs.memberMethod.
		u.ufcsMethod(x, y, z);

		// Calls Phobos function
		u.find(x);
	}

	/* snippet of std.algorithm */
	module std.algorithm;
	...
	HayStack find(HayStack, Needle)(HayStack h, Needle n) {
		...
		// Lookup happens in module std.algorithm. Since we
		// don't have the import of module ufcs here,
		// .ufcsMethod cannot be resolved.
		h.ufcsMethod(n);
		...
	}

I wonder if there's an easy way to extend the language so that you can specify which scope a function lookup will happen in.  Suppose, hypothetically, we can specify a lookup to happen in the caller's context.  Then UFCS would work:

	/* snippet of std.algorithm */
	module std.algorithm;
	...
	HayStack find(HayStack, Needle)(HayStack h, Needle n) {
		...
		// Hypothetical syntax:
		with (HayStack.__callerContext)
			h.ufcsMethod(n);
		// Now lookup happens in the caller's context, i.e.,
		// module main, function main.  Since module ufcs is
		// visible there, this call resolves to ufcs.ufcsMethod.
		...
	}

The problem with this is that allowing the callee to access the context of the caller opens up a can of worms w.r.t. symbol hijacking and accessing local variables in the caller, which should be illegal.

Seems like the only workable solution in the current language is to wrap the type in a custom type.  But like you said, that's high-maintenance, and decreases encapsulation because when the implementation of UserType changes, the wrapper type needs to change accordingly.  And the current implementation of alias this is not without its own set of problems.

There *is* a generic wrapper type in Phobos that uses opDispatch for member forwarding, but last time I checked, it also comes with its own set of issues.  So currently, we still don't have a perfect solution for this, even though we're so tantalizingly close.


T

-- 
Unix is my IDE. -- Justin Whear
November 01, 2017
On Tuesday, 31 October 2017 at 15:45:42 UTC, H. S. Teoh wrote:
> The one case where the difference matters is when you're trying to debug something.  In that case, I'd say the onus is really upon the debugger to tell you what kind of function it was.

Yes, this is my main concern I guess, as I use pretty plain editors that tell me nothing. I rely on the code to tell me what I need to know.

foo.bar();
foo.\bar();  // where \ means a free function

A different syntax for calling free functions would certainly make it clearer (as the above demonstrates), but as you argue, it would have a negative effect on encapsulation.

I guess with a more enhanced editor I could just mouse over UFCS syntax, and it could identify a free function from a member function. That would be nice, since there's no other way to know without exploring code elsewhere...

I guess the days of use a plain text editor...are slowly coming to and end ;-(

..what a shame...as I only just recently 'upgraded' from using vi to using micro....

https://github.com/zyedidia/micro

November 01, 2017
On Wed, Nov 01, 2017 at 03:38:32AM +0000, codephantom via Digitalmars-d wrote:
> On Tuesday, 31 October 2017 at 15:45:42 UTC, H. S. Teoh wrote:
> > The one case where the difference matters is when you're trying to debug something.  In that case, I'd say the onus is really upon the debugger to tell you what kind of function it was.
> 
> Yes, this is my main concern I guess, as I use pretty plain editors that tell me nothing. I rely on the code to tell me what I need to know.
[...]
> I guess with a more enhanced editor I could just mouse over UFCS syntax, and it could identify a free function from a member function. That would be nice, since there's no other way to know without exploring code elsewhere...

I'm a vim user, and frankly, I don't find the need for "enhanced" editors at all. (I don't even use syntax highlighting, but that's another story. :P)

If there's a function call that could be either a member function or a UFCS free function, all I need is to have the debugger print a stacktrace and that ought to clear things up.  It will even resolve other issues like telling me exactly which overload is being called, if there are complicated overload sets, and point me to the exact file/line of the code.  At that point, the difference between UFCS free function or member function is basically irrelevant.


> I guess the days of use a plain text editor...are slowly coming to and end ;-(
[...]

Nope.  Plain text editors still rule.  GUIs are for wimps. :-P


T

-- 
The best way to destroy a cause is to defend it poorly.
1 2 3 4
Next ›   Last »