Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phobos feels like a very detached standard library #84

Open
Connor-GH opened this issue Nov 13, 2024 · 20 comments
Open

Phobos feels like a very detached standard library #84

Connor-GH opened this issue Nov 13, 2024 · 20 comments

Comments

@Connor-GH
Copy link

As the title says.

Do you get this feeling too? It feels closer to boost than it would to (older) C++ stl types. And just like boost, many of the components I don't feel comfortable touching because of the very expensive runtime or compiletime cost associated with them. C is fast because the man pages document every function that allocates, and I can count them on my fingers (strdup, and printf("%n") is all I could think of). Zig is fast because any allocation is explicit due to the need to pass in an allocator everywhere. C++ is (sometimes) fast because it can allow you to pass in an allocator as a template argument, but this doesn't help the fact that it is optional and that the allocation characteristics of data structures such as std::vector is unspecified.

I believe that, in order for openD to start on the right foot, there needs to be some way of specifying an allocator outside of the GC interface. Also, if we were to start anew, memory allocations should be documented.

This issue was sparked by my experimentation with SumType versus my own Result, which had me conclude that my Result and my .match compiles over 2 times faster than SumType and .match.* Here is a practical example:

import result;
import core.stdc.stdio : printf;
import std.sumtype : SumType, match;

auto my_func(int x) {
	alias R = Result!(int, string);
	if (x == 6)
		return R.ok(0);
	else
		return R.err("uh oh!");
}


auto my_func2(int x) {
	alias R2 = SumType!(int, string);
	if (x == 6)
		return R2(0);
	else
		return R2("uh oh!");
}
/* ideal version: 
Result!(int, string) my_func3(int x) {
if (x == 6)
  return 0; // or ok(0)
else 
return "uh oh!"; // or err("uh oh!")
}
*/
extern(C) int main() {
	Result!(int, string) x = my_func(6);

	printf(result.match(x,
		(int i) => "yay!",
		(string s) => "bad."
	).ptr);

	SumType!(int, string) x2 = my_func2(6);

	printf(x2.match!(
		(int i) => "yay!",
		(string s) => "bad."
		).ptr);

	return 0;
}
  • disclaimer: my match only handles two "arguments" (functions) and does not do exhaustive checking. It is not a true "discriminated union" like SumType is, and is specialized for my kernel uses.
@adamdruppe
Copy link
Contributor

Your "ideal version" thing should be doable in opend fyi.

but generally i consider phobos to be quasi-deprecated abandonware..... don't want to change it in the name of compatibility but don't want to use it cuz it is slow. parts of it are fine but idk i haven't really decided.

in any case, nobody has proposed anything specific regardless.

@Connor-GH
Copy link
Author

Your "ideal version" thing should be doable in opend fyi.

could you explain? is there a specific feature that allows this?

@adamdruppe
Copy link
Contributor

@implict this(T t) - return is a case of implicit construction.

@Connor-GH
Copy link
Author

@implict this(T t) - return is a case of implicit construction.

That'd be great! (If I could get opend's ldc to build....the opend dmd does not support it, it seems)

@adamdruppe
Copy link
Contributor

just use the binary download... so much easiesr.

and don't forget to import core.attribute; for the implicit symbol

@Connor-GH
Copy link
Author

Connor-GH commented Nov 14, 2024

I'm sorry but I'm sure it works for you but it just isn't for me. When I import core.attribute, even making sure it's the right one in case the filename is hardcoded or something, ldc still segfaults. return 0; Still gives the implicit conversion error.

Just to get this right, 0 should convert to Result(0) or whatever?

It seems to work on just basic types like MyType x = 6; but does not work for functions. The problem is, hjow is the compiler supposed to figure this out? It tries to implicitly convert int to MyType but obviously fails.

@adamdruppe
Copy link
Contributor

What segfault? What compiler build? What OS? What sample code?

$ opend ldmd2 --version | head
LDC - the LLVM Open D compiler (1.36.0 - OpenD Nov  1 2024 00:35:56):
  based on DMD v2.106.1 and LLVM 17.0.6
module b0rk;

import core.attribute;
struct A {
        int a;
        @implicit this(int a) { this.a = a; }
}

A foo() {
        return 5; // works
}

void wtf(A a) {}

void main() {
        wtf(5); // works
        assert(foo().a == 5);
}

@Connor-GH
Copy link
Author

Your example just doesn't work for me. I downloaded the preview for my platform and ran the code as ./bin/ldc2 bork.d (i renamed the module to "bork"). I see that this version was built with llvm 17, and I use llvm 18. So I installed llvm 17 and the same thing still happens.

@adamdruppe
Copy link
Contributor

"the preview"......... the old one from feburary? Get the CI Automatic Build, that's always the up to date one (I'd delete the other if it'd let me!)

The --version should show it was built in November, just like mine.

@Connor-GH
Copy link
Author

The CI artifacts are "expired". https://github.com/opendlang/opend/actions/runs/11621956754#artifacts

@adamdruppe
Copy link
Contributor

Works fine from the homepage link: https://github.com/opendlang/opend/releases/tag/CI

@Connor-GH
Copy link
Author

Tested, it does (but you should do more real releases!)

Anyways, back to the topic at hand: the "new generation" D library you started has nearly nothing in it, do you have any data structures you would like to see or do, but don't have the time for? Make a wishlist and I will try to get to them. I am no metaprogramming wizard and cannot do exhaustive pattern matching with templates, but I don't think the library should have much of that, personally. The general rule seems to be that the harder it is for a human to figure out the templates by hand, the longer it takes to compile.

By the way, @implicit this is probably never making it upstream due to Walter's thoughts on it. Implicit construction can be abused but I like that it is explicit that you have to be implicit about it. Although there is no information about it outside of the type. A function annotation would be nice. Could you make it where the @implicit attribute is required on functions that use implicit instantiation?

@adamdruppe
Copy link
Contributor

oh every time I push to master is a real release!

but yeah, that D library thing is something Grim wanted to do and I'm not really sure what he has in mind for it. I really have no need for much of anything like that; the arsd namespace has most everything I need. there's some embedded data structures there too - a circular buffer as part of the terminal scrollback history, a stack as part of dom tree traversal, a double linked list as part of async io tracking, etc., but i tend to just do them ad hoc as i need them where i need them. tbh they're usually not hard anyway. So my preference is to leave that lib to people who do actually use it, so it reflects reality instead of my weird, twisted mind.

That said... idk if you have ideas we can talk through it. A lot of people say phobos needs containers and it is an embarrassment that it doesn't have them but like.... then they never follow up saying what, specifically, they have trouble with. so part of why that repo was made is to encourage people who talk about it to go ahead and put something in too.

re implicit, of course upstream isn't going to do it. if i had any faith in upstream's leadership, even just a tiny inkling that might indicate a seed of faith, this fork wouldn't exist. but putting implicit on the function itself... is that really necessary? Lots of functions can be called with implicit conversions and constructions:

void foo(long a) {}
foo(4); // this is an implicit conversion from int to long

Should that be marked too?

@Connor-GH
Copy link
Author

re implicit, of course upstream isn't going to do it. if i had any faith in upstream's leadership, even just a tiny inkling that might indicate a seed of faith, this fork wouldn't exist. but putting implicit on the function itself... is that really necessary? Lots of functions can be called with implicit conversions and constructions:

void foo(long a) {}
foo(4); // this is an implicit conversion from int to long

Should that be marked too?

I would say no, of course, because that's how it's always been. Also, almost never does it cause problems (the exception would be that you thought it was a long, but it's an it). On the other hand, I have looked at C++ code in the past that used std::optional (very similar to my Result with one type being a None) and seeing return {} and going "what on earth does that do?". Well, it turns out, it constructs an object using the default constructor for it, but that is documented exactly nowhere. I believe that the code should be as self-documenting (to a reasonable extent) as possible, and D has done a fantastic job of that so far. I can look at any given API signature and know exactly what it does, given all the scope, return scope, pure, const, etc attributes that can be applied to variables and functions (yes, i know pure and const are only for functions).

First thing anyone looks at when going into a function is its signature. If i were to see @implicit MyObject func(scope int *x), you can infer a lot about what you're going to see inside of that function. Implicit declarations? Probably, but not guaranteed (the compiler should not need to do exhaustiveness checking for this if it is too much of a performance cost). But we know that we will be constructing a MyObject and consuming x. I understand that someone might stick @implicit on main and just go back to C++ bad behavior but this is a case for exhaustiveness checking. Better yet, disallow @implicit on main. I would write the tests for these if you're willing to compromise with me on this.

@adamdruppe
Copy link
Contributor

What would implicit on main ever do? That doesn't make any sense, main's arguments are required to be empty or string[], no user defined types there at all.

@Connor-GH
Copy link
Author

What would implicit on main ever do? That doesn't make any sense, main's arguments are required to be empty or string[], no user defined types there at all.

@implicit main would work the same as @nogc main: it trickles down. @implicit on functions would be an annotation that "this function uses implicit construction".

@adamdruppe
Copy link
Contributor

Implicit construction only ever happens in two places: function calls and return values, and even then, only if it is the only option the type has opted into. Ambiguities are still errors so you won't get a surprising overload resolution.

C++ does some weird things but I think we've avoided that with this more conservative approach.

I'm not in favor of additional attributes at the usage site, that removes the whole point.

@Connor-GH
Copy link
Author

Hmm, the tests I performed confirmed this. Unfortunately, you can still implicitly and explicitly construct through the same constructor. Also, implicit constructions have a performance impact, and that shuould probably be obvious to the person using it, especially if they happen to stumble onto that API and did not create it themselves. People reading the code would immediately see that implicit conversion is being used. The annotation does not have to tell you what object does it, but it should be a correct annotation. Also it would be easier for serve-d to figure out what's going on (if you intend to contribute opend options to it).

@Connor-GH
Copy link
Author

Also, opAssign should probably be considered harmful.

@adamdruppe
Copy link
Contributor

Unfortunately, you can still implicitly and explicitly construct through the same constructor.

why is this bad? it is still a constructor, after all.

But implicit conversions and potentially expensive function calls already happen all over the place. Yes, opAssign and other overloaded operators, alias this, opDispatch, properties, tons of stuff. At some point you just have to hope people don't do crazy things.

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

No branches or pull requests

2 participants