Re: delegates and functions
On Saturday, 9 June 2018 at 22:28:22 UTC, Ali Çehreli wrote: There is some explanation at the following page, of how the lambda syntax is related to the full syntax: http://ddili.org/ders/d.en/lambda.html#ix_lambda.=%3E copy rect from article as image https://i.gyazo.com/c23a9139688b7ed59fbe9c6cdcf91b93.png well, I can to remember that lambda () => { return ..; } returns another implicit lambda but my mind refuses to understand fact that ... auto l1 = (int a) => a + 1; // returns incremented value auto l2 = (int a) => { return a + 1; }; // return another lambda that increment value ... very different artifacts. WHY? it's looks same, but in DLang it's some kind of mindtrap, looks mindness and falsy. I am upset and angry cause somebody just told me "There Is No Santa Claus in Dlang!" (I fell same yesterday and almost calmed down already) D-compiler: you want one lambda? you are lucky today! I'll give you two lambda in square(^2) for same price! do not thank! really, if I want lambda that returns another lambda I can write auto l1 = (int a) => () => a + 1; // maybe I need it in some special case but if I want lambda with return... well, I should forget about shorter syntax and write auto l1 = (int a) { /*...*/ return a + 1; } // why I can't use "=>" here? silent cry. yes, this version is shorter than with "=>", but my production language is C# (same as Dlang with CIL instead native. before today) and I try to do something in Dlang. my state when I stick to such things looks like this (pic about JavaScript) https://pics.me.me/javascript-%3E-53-53-5-2-5-2-%3E-53-ah-gonna-29130223.png DON'T READ NEXT I readed 2 weeks ago post "Dlang is a crap". well, I agree with author in many thing. * and such small things like (not)using "=>" can kickass you for hours. * and naming exceptions for example in std.concurrency - some ends with "Exception" some not - told me that no one center in Dlang development, no good organization and no standard. * and VisualD sometimes lost autocompletion/intellisense and there's no way to get it back, except to restart the computer. * and parsing Binance(cryptoexchange) JSON-response "All Market Tickers Stream" from https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md (~150 submessages about 200KB) with ctRegex(that told as very fast) spend 13ms. task is find every submessage that starts with '{' and ends with '}' and parse it from JSON to some struct. but simple replace ctRegex to indexOf( '{'|'}' ) decrease time to 2ms only - 90% of time getting filled 150 structs with data (that means find all string values in submessage and convert it to some types like double or long with my CTFE-code, not Dlang-JSON) spend compile-time Regex for searching start{ and end}. WTF? well, Go(version maybe 1.9) was a little bit faster about 10ms, but C# with Json.NET, where JIT and no string-spans(that means alloc every substring in GC) and all strings are Unicode(WebSocket sent UTF8, that adds Encoding time to Unicode) - spend only 3ms for all. Dlang can do things better and faster but it don't want (or people don't want) * and many "nothrow nogc trusted pure" near every function in Phobos and you should read every function attrubute when you try to understand exactly what does and how works this function, cause you can miss some important attribute. I don't know how it should be done/removed from eyes. probably better such stuff describe before function like [context: nogc noBoundsCheck nothrow pure trusted] int func( args... ) cause you can vissually ignore [context..] and look at function without urban garbage.. maybe it should looks in editor like checkbox spans in right side of editor where you can turn off/on such attributes but text is clear not over..loaded? (English is not my native) * and everybody will change nothing with textual representation of code cause this things used already in many code and packages. (except VisualD that in some days will be fixed) it's sad. over Dlang' code textual representation should be used another sugar representation that hides and reorganize some things (like TypeScript over JavaScript or MOC in Qt) when Rust will add GC I will go to Rust - I dont like ownership stuff. when Go will add OOP & exceptions I will go to Go - I dont like check every function call with if err.. also I can live with type traits without classes in most cases. probably I will go. I like in Dlang: native (with asm), GC, CTFE, OOP, functional style... (I am not a professional with Dlang, I learning it) but sometimes Dlang is little baby who can piss/shit you in moments when you really not expect and you haven't diaper or any protection for that cases. YOU CAN READ AGAIN can somebody tell me please what was the reason that lambda (blah) => { return value + 1; } actionally returns another lambda that returns value+1? what case it can be usefull? why Dlang gives me lambda of lambda w
Re: Passing C++ class to DLL for callbacks from D (Steam)
On Sunday, 10 June 2018 at 02:57:34 UTC, evilrat wrote: Only subsystems getters like SteamUser() or SteamInventory() requires wrapping. I really can't understand why they ever choose to silently ignore registering callbacks received with C API systems handles... Thanks to the information you supplied I was able to get it working without a wrapper, like so: extern(C++) abstract class ISteamClient { //public abstract IntPtr GetIntPtr(); public abstract uint CreateSteamPipe(); public abstract bool BReleaseSteamPipe(uint hSteamPipe); public abstract uint ConnectToGlobalUser(uint hSteamPipe); public abstract uint CreateLocalUser(ref uint phSteamPipe,uint eAccountType); public abstract void ReleaseUser(uint hSteamPipe,uint hUser); public abstract ISteamUser GetISteamUser(uint hSteamUser,uint hSteamPipe,const(char)* pchVersion); ... public abstract ISteamUserStats GetISteamUserStats(uint hSteamUser,uint hSteamPipe,const(char)* pchVersion); ... } HSteamUser hSteamUser = SteamAPI_GetHSteamUser(); HSteamPipe hSteamPipe = SteamAPI_GetHSteamPipe(); auto steamClient = cast(ISteamClient) SteamInternal_CreateInterface(STEAMCLIENT_INTERFACE_VERSION); auto userStats = steamClient.GetISteamUserStats(hSteamUser, hSteamPipe, STEAMUSERSTATS_INTERFACE_VERSION); auto hid = SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers(userStats); auto cbk = new CallResult!NumberOfCurrentPlayers_t(); SteamAPI_RegisterCallResult(cbk, hid); And it successfully fires the 3-arg Run method of the callback object. However for some reason the function table of the ISteamClient seems to be off by one.. it kept calling the wrong methods until I commented one out, in this case GetIntPtr() as seen above, then everything seemed to line up. Not sure what the proper way to ensure it matches the C++ layout here, but at least it seems to be mostly working for now. Thanks again!
Re: Passing C++ class to DLL for callbacks from D (Steam)
On 10/06/2018 10:29 PM, cc wrote: And it successfully fires the 3-arg Run method of the callback object. However for some reason the function table of the ISteamClient seems to be off by one.. it kept calling the wrong methods until I commented one out, in this case GetIntPtr() as seen above, then everything seemed to line up. Not sure what the proper way to ensure it matches the C++ layout here, but at least it seems to be mostly working for now. Thanks again! Ugh what GetIntPtr? Unless of course this header file is wrong[0]. Make the members match exactly, order and everything and it should "just work". [0] https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/public/steam/isteamclient.h#L113
Re: WTF! new in class is static?!?!
On Sunday, 10 June 2018 at 02:34:11 UTC, KingJoffrey wrote: On Sunday, 10 June 2018 at 01:27:50 UTC, bauss wrote: On Saturday, 9 June 2018 at 12:40:07 UTC, RealProgrammer wrote: maybe you and others in the D 'community' should start paying attention to the 'opinions' of those who do professional development with professional compilers. I do professional work with a professional compiler aka. The D compiler and I've been doing so for years. Well then, you should like my idea for the dconf '2019' logo... Dman and a bug, holding hands, and the bug saying "I'm not a bug, I'm a feature!". Sure, because other compilers do not have bugs.
Re: Passing C++ class to DLL for callbacks from D (Steam)
On Sunday, 10 June 2018 at 10:47:58 UTC, rikki cattermole wrote: On 10/06/2018 10:29 PM, cc wrote: And it successfully fires the 3-arg Run method of the callback object. However for some reason the function table of the ISteamClient seems to be off by one.. it kept calling the wrong methods until I commented one out, in this case GetIntPtr() as seen above, then everything seemed to line up. Not sure what the proper way to ensure it matches the C++ layout here, but at least it seems to be mostly working for now. Thanks again! Ugh what GetIntPtr? Unless of course this header file is wrong[0]. Make the members match exactly, order and everything and it should "just work". [0] https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/public/steam/isteamclient.h#L113 Woops, that GetIntPtr came from the .cs header in the same folder as the C++ headers distributed with the SDK, that'll teach me to ctrl+f "class ISteamClient" in all open files and copy/paste before reading. Anyway I played around with it some more and found the one single line that was causing the problem. It needs this: HSteamPipe SteamAPI_GetHSteamPipe(); rather than one of these: HSteamPipe SteamAPI_ISteamClient_CreateSteamPipe(ISteamClient instancePtr); virtual HSteamPipe CreateSteamPipe() = 0; It looks like the pipe is already created upon initializing and calling CreateSteamPipe creates a secondary one that doesn't receive callbacks. SteamAPI_GetHSteamPipe is called inline to retrieve the current pipe on initialization of the the classed version of the API, but the documentation doesn't mention it exists at all, leading to confusion when using the flat version. Well, mystery solved.
Re: Passing C++ class to DLL for callbacks from D (Steam)
On 10/06/2018 11:28 PM, cc wrote: Woops, that GetIntPtr came from the .cs header in the same folder as the C++ headers distributed with the SDK, that'll teach me to ctrl+f "class ISteamClient" in all open files and copy/paste before reading. Stay with the c/c++ headers for c/c++ code. We don't generally don't go testing the long way ;)
Re: delegates and functions
On 10.06.2018 12:21, OlegZ wrote: On Saturday, 9 June 2018 at 22:28:22 UTC, Ali Çehreli wrote: There is some explanation at the following page, of how the lambda syntax is related to the full syntax: http://ddili.org/ders/d.en/lambda.html#ix_lambda.=%3E copy rect from article as image https://i.gyazo.com/c23a9139688b7ed59fbe9c6cdcf91b93.png > well, I can to remember that lambda () => { return ..; } returns another implicit lambda you shouldn't remember it. you need to understand that `a=>2*a` is short form of `(a) { return 2a; }. So using `a=>{ return 2*a; }` you get ``` (a) { return (){ return 2*a; }; } ``` i.e. a function returning delegate. ``` a => { return 2*a; } /\ \ / || \ / ||\ / || \ / || \ / This is \ / function \ This is definition of delegate definition \ so you have a function that returns delegate. ``` it's like a => a => 2*a; or (a){ return () { return 2*a; } just first version is much simpler and namely convenience is the reason of this syntax I guess. > can somebody tell me please what was the reason that lambda (blah) => { return value + 1; } actionally returns another lambda that returns value+1? what case it can be usefull? why Dlang gives me lambda of lambda where I want lambda written with "=> { return"? I am expecting some magic and simple trick with lambdas. You misuse two different form of the same. The form with "=>" is very convenient if expression is short, in other cases the form `(){}` is suited better.
@nogc logging library
Hi, I'm D-newbie and looking for no-gc loggig library, ideally with timestamps and formatting with output to stdout / file. Is there any? Thanks! WBR, basiliscos
Re: Access to structures defined in C
On Wednesday, 14 March 2018 at 02:17:57 UTC, Adam D. Ruppe wrote: The type system would *like* to know, certainly for correct range errors, but if you declare it as the wrong length and use the .ptr, it still works like it does in C: extern(C) __gshared extern char*[1] files; // still works import core.stdc.stdio; void main() { printf("%s\n", files.ptr[2]); // ptr bypasses the range check } That worked but now I have a more convoluted case: a C array of pointers to int pointers, e.g., int **xs[] = {x1, x2, 0}; int *x1[] = {x1a, 0}; int *x2[] = {x2a, x2b, 0}; ... int x2a[] = { 1, 3, 5, 0}; Only the first line is exposed (and without the initialization). So I tried: extern(C) __gshared extern int**[1] xs; The D compiler accepts that, but just about any manipulation gets screamed at, usually with Error: only one index allowed to index int. Note that I'm trying to access the ints, i.e., in C something like xs[1][0][2] to access the 5 in x2a. Do I have to mimic the intermediate C arrays? But if you can match the length, that's ideal. Unfortunately, although the C array lengths are known at C compile time, they're not made available otherwise so I'm afraid the [1] trick will have to do for now.
Re: delegates and functions
a => { return 2*a; } /\ \ / || \ / ||\ / || \ / || \ / This is \ / function \ This is definition of delegate definition \ so you have a function that returns delegate. ``` it's like a => a => 2*a; or (a){ return () { return 2*a; } I believe that a lot of other languages work differently, so when their knowledge are transposed to D, people get confused at first. Eg. in JS: x = x => x * 2; x = x =>{ return x * 2; } //equivalent
Re: Access to structures defined in C
On Sunday, 10 June 2018 at 17:59:12 UTC, Joe wrote: That worked but now I have a more convoluted case: a C array of pointers to int pointers, e.g., int **xs[] = {x1, x2, 0}; int *x1[] = {x1a, 0}; int *x2[] = {x2a, x2b, 0}; ... int x2a[] = { 1, 3, 5, 0}; Only the first line is exposed (and without the initialization). So I tried: extern(C) __gshared extern int**[1] xs; The D compiler accepts that, but just about any manipulation gets screamed at, usually with Error: only one index allowed to index int. Note that I'm trying to access the ints, i.e., in C something like xs[1][0][2] to access the 5 in x2a. Do I have to mimic the intermediate C arrays? I don't know why I didn't try this first. It seems that the D equivalent of C's xs[1][0][2] is simply xs.ptr[[1][0][2].
Re: delegates and functions
On 10.06.2018 20:58, SrMordred wrote: a => { return 2*a; } /\ \ / || \ / || \ / || \ / || \ / This is \ / function \ This is definition of delegate definition \ so you have a function that returns delegate. ``` it's like a => a => 2*a; or (a){ return () { return 2*a; } I believe that a lot of other languages work differently, so when their knowledge are transposed to D, people get confused at first. Eg. in JS: x = x => x * 2; x = x =>{ return x * 2; } //equivalent Ah, I see. From this point of view there is some inconsistency indeed.
What is the point of nothrow?
What is the point of nothrow if it can only detect when Exception is thrown and not when Error is thrown? It seems like the attribute is useless because you can't really use it as protection to write bugless, safe code since the nasty bugs will pass by just fine. I'm aware that it's a feature that nothrow can throw Error, but it makes the attribute completely useless because you basically have no safety to guard against writing code that throws Error. To an extend @safe works, but there are tons of stuff that throws Error which you can only detect and guard against manually. So what is the point of nothrow when it can only detect exceptions you'd catch anyway. To me it would be so much more useful if you could detect code that could possibly throw Error.
Re: What is the point of nothrow?
On Sunday, June 10, 2018 23:59:17 Bauss via Digitalmars-d-learn wrote: > What is the point of nothrow if it can only detect when Exception > is thrown and not when Error is thrown? > > It seems like the attribute is useless because you can't really > use it as protection to write bugless, safe code since the nasty > bugs will pass by just fine. > > I'm aware that it's a feature that nothrow can throw Error, but > it makes the attribute completely useless because you basically > have no safety to guard against writing code that throws Error. > > To an extend @safe works, but there are tons of stuff that throws > Error which you can only detect and guard against manually. > > So what is the point of nothrow when it can only detect > exceptions you'd catch anyway. > > To me it would be so much more useful if you could detect code > that could possibly throw Error. Why do you care about detecting code that can throw an Error? Errors are supposed to kill the program, not get caught. As such, why does it matter if it can throw an Error? Now, personally, I'm increasingly of the opinion that the fact that we have Errors is kind of dumb given that if it's going to kill the program, and it's not safe to do clean-up at that point, because the program is in an invalid state, then why not just print the message and stack trace right there and then kill the program instead of throwing anything? But unforntunately, that's not what happens, which does put things in the weird state where code can catch an Error even though it shouldn't be doing that. As for the benefits of nothrow, as I understand it, they're twofold: 1. You know that you don't have to worry about any Exceptions being thrown from that code. You don't have to worry about doing any exception handling or having to ensure that anything gets cleaned up because of an Exception being thrown. 2. If the compiler knows that a function can't throw an Exception, then it doesn't have to insert any of the Exception handling mechanism stuff that it normally does when a function is called. It can assume that nothing ever gets thrown. If an Error does get thrown, then none of the proper clean-up will get done (e.g. constructors or scope statements), but because an Error being thrown means that the program is in an invalid state, it's not actually safe to be doing clean-up anyway. So, the fact that a function is nothrow gives you a performance benefit, because none of that extra Exception handling stuff gets inserted. How large a benefit that is in practice, I don't know, but it is a gain that can't be had with a function that isn't nothrow. - Jonathan M Davis
How to sort byCodeUnit.permutations.filter(...)
I wrote a small program for Project Euler problem 41 ( https://projecteuler.net/problem=41 ). --- project_euler_41.d void main() { import math_common : primesLessThan; import std.stdio : writeln; import std.conv : parse; import std.algorithm : permutations, canFind, filter, each, sort; import std.utf : byCodeUnit; import std.range : assumeSorted; import std.array : array; auto primes = primesLessThan(9_999_999UL).assumeSorted; "1234567".byCodeUnit .permutations .filter!(a => primes.canFind(a.parse!uint)) .each!(a => a.writeln); } --- The problem is this prints a list of numbers. The task requires only the largest, so the intuitive fix is to add `.array.sort[$ - 1].writeln` in place of the `each`, but this prints an array of `1234567`s instead of the permutations. Does anyone know how to sort the filter result without modifying the individual results?
Re: How to sort byCodeUnit.permutations.filter(...)
On Monday, 11 June 2018 at 04:06:44 UTC, Uknown wrote: The problem is this prints a list of numbers. The task requires only the largest, so the intuitive fix I would just pull the max out of it. http://dpldocs.info/experimental-docs/std.algorithm.searching.maxElement.2.html
Re: What is the point of nothrow?
On Monday, 11 June 2018 at 00:47:27 UTC, Jonathan M Davis wrote: On Sunday, June 10, 2018 23:59:17 Bauss via Digitalmars-d-learn wrote: What is the point of nothrow if it can only detect when Exception is thrown and not when Error is thrown? It seems like the attribute is useless because you can't really use it as protection to write bugless, safe code since the nasty bugs will pass by just fine. I'm aware that it's a feature that nothrow can throw Error, but it makes the attribute completely useless because you basically have no safety to guard against writing code that throws Error. To an extend @safe works, but there are tons of stuff that throws Error which you can only detect and guard against manually. So what is the point of nothrow when it can only detect exceptions you'd catch anyway. To me it would be so much more useful if you could detect code that could possibly throw Error. Why do you care about detecting code that can throw an Error? Errors are supposed to kill the program, not get caught. As such, why does it matter if it can throw an Error? Now, personally, I'm increasingly of the opinion that the fact that we have Errors is kind of dumb given that if it's going to kill the program, and it's not safe to do clean-up at that point, because the program is in an invalid state, then why not just print the message and stack trace right there and then kill the program instead of throwing anything? But unforntunately, that's not what happens, which does put things in the weird state where code can catch an Error even though it shouldn't be doing that. As for the benefits of nothrow, as I understand it, they're twofold: 1. You know that you don't have to worry about any Exceptions being thrown from that code. You don't have to worry about doing any exception handling or having to ensure that anything gets cleaned up because of an Exception being thrown. 2. If the compiler knows that a function can't throw an Exception, then it doesn't have to insert any of the Exception handling mechanism stuff that it normally does when a function is called. It can assume that nothing ever gets thrown. If an Error does get thrown, then none of the proper clean-up will get done (e.g. constructors or scope statements), but because an Error being thrown means that the program is in an invalid state, it's not actually safe to be doing clean-up anyway. So, the fact that a function is nothrow gives you a performance benefit, because none of that extra Exception handling stuff gets inserted. How large a benefit that is in practice, I don't know, but it is a gain that can't be had with a function that isn't nothrow. - Jonathan M Davis Well at least from my point of view I would care about code that can throw Error, because if say nothrow could detect that then you could prevent writing that code that throws jt at all and thus you'd be writing less error prone code. Maybe not necessarily nothrow, but something else that could ensure that your code is "100% safe" to run without any errors happening from ex. Accessing out of bounds, accessing invalid memory, attempting to access the member of an uninitialized class etc. Like you'd have to handle each such cases. Writing code in D today you have to think about each statement you write and whether it could possibly throw Error because you have little to no tools that helps you preventing writing such code. I'm very well aware that Error is not supposed to be caught and that the program is in an invalid state, but ehat I'm trying to get at is that if nothrow or at least a feature similar existed that could detect code that may throw Error, then you could prevent writing code that throws it in the first place. It would be a great tool to writing bugless code.
Re: What is the point of nothrow?
On Monday, June 11, 2018 04:11:38 Bauss via Digitalmars-d-learn wrote: > I'm very well aware that Error is not supposed to be caught and > that the program is in an invalid state, but ehat I'm trying to > get at is that if nothrow or at least a feature similar existed > that could detect code that may throw Error, then you could > prevent writing code that throws it in the first place. > > It would be a great tool to writing bugless code. Well, the problem with that is that if you do anything involving assertions, dynamic arrays, or GC memory allocations, you can get an Error. The same goes for some stuff like final switch statements. Some of those Errors don't happen with -release (e.g. the check on final switch and asserion failures), but enough operations have to be checked at runtime that I expect that even if you ignored the ones that don't happen with -release, relatively little code could be guaranteed to not throw Errors. And most Errors are thrown because of Error conditions that can't reasonably be caught at compile time. So, while knowing that none of those Errors can happen in a particular piece of code might be nice, all it really means is that you're not doing any operations in that code which can be checked for error conditions at runtime but which can't be checked at compile time. On the whole, what Errors are really doing is catching the bugs that you didn't manage to catch yourself and that the compiler can't catch for you, but the runtime can. So, arguably, all you'd really be doing if you guaranteed that a piece of code didn't throw any Errors is guarantee that the code didn't do any operations where the runtime can catch bugs for you. As such, while you obviously don't want to end up having any Errors thrown in your program, I seriously question that trying to write code that is statically guaranteed to not throw any Errors is very useful or desirable - especially since it's not like such code is guaranteed to be bug-free. It just wouldn't have any of the bugs that the runtime can catch for you. - Jonathan M Davis
Re: How to sort byCodeUnit.permutations.filter(...)
On Monday, 11 June 2018 at 04:12:57 UTC, Adam D. Ruppe wrote: On Monday, 11 June 2018 at 04:06:44 UTC, Uknown wrote: The problem is this prints a list of numbers. The task requires only the largest, so the intuitive fix I would just pull the max out of it. http://dpldocs.info/experimental-docs/std.algorithm.searching.maxElement.2.html Thanks for your reply. I completely forgot about maxElement. I used it, but it prints 1234567, not the permutations. The same happens when using array. Why are the strings getting modified? The same happens with this: "123".byCodeUnit.permutations.writeln;//[123, 213, 312, 132, 231, 321] "123".byCodeUnit.permutations.array.writeln;//[123, 123, 123, 123, 123, 123] Seems odd. Is this a bug or expected behaviour?
Pass function (not alias) to template and/or delegate-ize a template argument
Is it possible to pass a class member function as a template argument in such a way that it can 1. be called during runtime, and 2. still retrieve the UDAs of the function itself? For example, given this setup: struct MYUDA {} class Foo { @(MYUDA) int bar(int x) { return x*2; } } auto foo = new Foo(); Neither of these will work: void FCALL(alias F, V...)(V args) { assert(hasUDA!(F, MYUDA)); // Ok //assert(F(args) == 10); // Compilation Error: need `this` for `bar` of type `int(int x)` } FCALL!(foo.bar)(5); void DCALL(V...)(int delegate(V) func, V args) { //assert(hasUDA!(func, MYUDA)); // Assertion failure (runtime delegate can't see compile-time UDAs) assert(func(args) == 12); // Ok } DCALL(&foo.bar, 6); However, I can combine them into one function like so: void TCALL(alias F, V...)(int delegate(V) func, V args) { assert(hasUDA!(F, MYUDA)); // Ok assert(func(args) == 14); // Ok } TCALL!(foo.bar)(&foo.bar, 7); But it feels ugly having to write the same thing twice (foo.bar and &foo.bar). I'd like to simply this somehow. Given a template declaration like "void CALL(alias F)( ... )" is there some way to write a function parameter that takes a delegate-of-F? Such that I can ultimate call the function as CALL(&foo.bar) and the template parameter alias is inferred. I did figure out I can do it with mixins like so: void MCALL(string func, V...)(V args) { mixin(`assert(hasUDA!(`~func~`, MYUDA));`); // Ok mixin(`assert(`~func~`(args) == 16);`); // Ok } MCALL!"foo.bar"(8); Which works. Is this the "best" way to do this? Or is there another pattern that looks cleaner?