PistonDevelopers / conrod

An easy-to-use, 2D GUI library written entirely in Rust.
Other
3.35k stars 296 forks source link

'1st impressions' #1114

Open dobkeratops opened 6 years ago

dobkeratops commented 6 years ago

so, it's a great library and I hope to use it (and eventually contribute widgets).

I just want to get this general rant out of the way. I get 2 negative triggers from the first line I see; One point is a simple choice fixable with 2chars changed in a fork , and the other is a general language issue, rather than a conrod issue, it's just i'll comment here where people might get into the discussion from the perspective of GUIs.

My perspective getting back into rust recently included pursuit of asmjs i.e wanting to write bits of ui that would run in a browser (I want one language that can do a bit of everything whilst being primarily focussed on efficient systems.. one language means the same interfaces and ability to move code. 'productivity' and 'performance' aren't mutually exclusive.. you might want a bit of UI to help test/visualze something.. or you might need to drill down and optimise something that started as an ad-hoc small project. I think the use cases of ASMJS/WASM will have some overlap with some concerns from my previous 'console-game-engine' hat).

anyway...

    // construct our `Ui`.
    let mut ui = conrod::UiBuilder::new([WIDTH as f64, HEIGHT as f64]).build();

(i) f64 coords. see other post #1113 :) the problem though is it contributes to a sense that I'm not amongst like minded people in the Rust community. yes profiler profiler profiler, but you use an initial guess to avoid having to measure everything. I'd use 32bits and then switch to 64 if experimental evidence tells me I need more precision/range

(ii) builder pattern.. I'm so surprised the rust community seems to like this. In most languages this could be one call.. one mental step. one piece of vocabulary. It's bloated with the ::new() (because of rusts choice not to double up the type name with a constructor function, which could be done un-ambiguously in rusts gramar), and 'the builder pattern' .build(), because these guys dont want default/named parameters.

I've always thought the builder pattern was just an ugly hack we had in C++ to workaround missing features; I enjoyed trailing defaults, and was envious of the cleaner syntax for initialisers in other languages facilitated without C/C++'s suboptimal syntax choices ( '=' can't be default params because it's mid-expression assignment, but it could be in rust..)

it just looks horrible to me.... multiple calls to do one thing.

There's times rust amazes me with elegance (I really like it's expression based syntax.. and implementing message queues using enum/match is awesome.. etc etc) ... and others where it annoys the hell out of me with unnecassery verbosity and clunkiness. there's ways it could be fixed.. it's surprising that so many people dont want to, and keep saying these things are virtues.

people keep saying "macros yada yada" but its more names, syntax changes* more code navigation, more pollution in your autocomplete menu, more documentation to sift through, more names to argue over .. inbuilt constructors and named params keep things streamlined and let you do more through one piece of code., get more insight/functionality through one piece of documentation..

Is it going to be this verbose all the way through when setting up widgets... ( .. and yes rust macros are great, but I think of them being useful for another layer, e.g. interacting with fileformats, debug.. mixing macros/templates/functioncalls for regular code* where other languages give you variadic args/defaults etc is just horrible)

r.e. efficiency , giving the compiler more information we can always contribute to the compiler to make it do the most efficient thing by default (whether it's translating the default params into seperate function calls.. figuring out which parts dont need to be over-written .. , or filling in the argument gaps.. make a choice based on a thresholds of 'num args', 'num supplied args'. You'd just be writing those out with lots of cut-paste anyway, rolling macros which is more for the user to comprehend )

in one of my past phases of working with Rust I'd added default argument initialiser expressions to the AST, but quickly discovered the community doesn't want it. This is just reminding me of why I haven't stuck with this language (and there's other core things for a graphics programmer vectormaths with operators, 32bit indices where C++ is actually more ergonomic, surprisingly).

I've stuck with rust for a bit longer this time because I'm accepting safety as a goal for 'working on something who's components could be run in a browser or web-server' .. but the flip side is a different set of concerns 'rewriting in rust' something that I've done in JS (rather than my usual C++)

As a tangent, all the AI frameworks have the same issue, they make extensive use of default params for setup.. intelligently chosen so you 'get stuff working quickly' with the library-writers having made educated guesses on what most people need, then you go in and refine more options. learn by tinkering rather than expecting people to stop and read..

daboross commented 6 years ago

re: builder pattern

I definitely agree that it isn't the best, but I think it works well within Rust's type system. Having functionality separated into different methods allows for much simpler methods, and shared traits like Positionable which could be used in a generic context to do the same thing to multiple widgets. If everything for every widget were configured in a single constructor, sharing code to modify multiple widgets the same way would be much more difficult.

Conrod's use of builders is definitely a bit odd: there is no final "widget" state that you get as a result of the builder. I'm not sure whether this makes builders more or less appropriate.

As for the rust community in general: it's unfortunate that we've adopted the builder pattern as the way to go, but I think it's pretty set. I mean, default & named parameters definitely collect everything in one place, but I don't think they really make much of a difference. X::new().a(A).b(B) is only syntactically different from X(a: A, b: B): neither has any difference in performance, a writer must know and supply the exact same information writing both of them, and the code gives the reader the exact same amount of information.

dobkeratops commented 6 years ago

is only syntactically different and the code gives the reader the exact same amount of information.

sure, but it's the grouping and how many words you have to sift through words that make the latter so much easier/clearer both to read and write (shift manipulations/finger movements; what is grouped with what;)

anyway it's an ongoing battle .. I might accept the absence of defaultparams if the language got currying, but given the relatively compact lambda syntax I tend toward thinking defaults are the better feature (to fully leverage the function call syntax)

dobkeratops commented 6 years ago

and shared traits like Positionable

ok that sort of thing does make a bit more sense; still when browsing the docs you get things like set_foo(&self, foo:type) // 'foo' repeated, sift through the noise of 'set' ..

i suppose that could be improved elsewhere in tooling... I just think it points to a fundamental efficiency in communication by maximising the capability of the straightforward function call syntax

(tangentially I'd like to see default expressions on struct fields too if we were talking about the ability to describe widgets through structs. i have messed with macros to do that sort of thing.