soegaard2
2020-7-5 17:17:48

In metapict I have rotate whose angle argument is in radian. The function rotatedd uses degrees for the angle argument.

Now rotate-about rotates some angle (in radian) around a point. Should the degrees version be named rotate-aboutd or rotatedd-about ?


laurent.orseau
2020-7-5 17:18:28

+1 for rotatedd-about


soegaard2
2020-7-5 17:19:09

(writing documentation on transformations)


notjack
2020-7-5 17:25:34

could you make wrappers like (degrees 270) and (radians pi)? then you don’t even need multiple functions to accept both


notjack
2020-7-5 17:27:06

the extra d naming convention is obscure and easy to misread, and I don’t think there’s anything that suggests it’s obvious that the radians are the default


laurent.orseau
2020-7-5 17:28:38

@notjack’s idea sounds like a good idea to me


laurent.orseau
2020-7-5 17:29:34

you could use the measures package :stuck_out_tongue:


soegaard2
2020-7-5 17:31:18

There are already conversion functions back and forth between radians and degrees. The “problem” is that mathematically the sensible default is radian - but when drawing everybody thinks in degrees.


notjack
2020-7-5 17:31:45

No I mean make structs for radians and degrees


soegaard2
2020-7-5 17:32:29

Ahh. Need to think about that.


notjack
2020-7-5 17:34:13

(struct radians (value) #:transparent) (struct degrees (value) #:transparent)


soegaard2
2020-7-5 17:35:41

It’s tricky to make convenient. Need special versions of sin, cos, and friends.


laurent.orseau
2020-7-5 17:37:40

Use a parameter?


notjack
2020-7-5 17:37:56

I’d rather read a few extra (degrees ...) and (radians ...) wrappers than read duplicated versions of each function that takes an angle as an input


soegaard2
2020-7-5 17:40:21

MetaPict is leaning towards the terse end of the spectrum. It’s due to the inspiration from MetaPost and Tikz, which both are very terse.


soegaard2
2020-7-5 17:40:53

The d-convention is already in use in several places (sind, cosd, pt@d, etc) so I think it is too late to change.


soegaard2
2020-7-5 17:41:21

But I get that a “value with a unit” is better represented as a struct.


laurent.orseau
2020-7-5 17:41:48

Keep them, add a parameter, make radian the default, and make radian function work with degrees based on parameter


laurent.orseau
2020-7-5 17:42:36

And Mark the d variants as deprecated


notjack
2020-7-5 17:42:51

I’d actually rather use separate functions than a parameter


laurent.orseau
2020-7-5 17:43:11

Why?


soegaard2
2020-7-5 17:43:37

Poor style - but I have to go. I’ll be back later.


notjack
2020-7-5 17:46:45

if it would be weird for someone to pass in a non-literal for an input, then don’t give them the option. Parameter means you could do something like (parameterize ([angle-mode (if tuesday? 'degrees 'radians)]) ...), which doesn’t seem like a use case that should be encouraged.


laurent.orseau
2020-7-5 17:47:16

That sounds very contrived to me


laurent.orseau
2020-7-5 17:47:52

I don’t doubt you have a good reason, but you’ll have to find a better counter example to convince me :)


laurent.orseau
2020-7-5 17:49:15

(because that’s true for every parameter ever, like log-ticks and whatnot)


notjack
2020-7-5 17:49:36

One sec, I’ll dig up the design guidance from work that I’m half-remembering


notjack
2020-7-5 17:57:20

So the specific guidance is “use two uniquely named methods instead of a single method with a boolean parameter” (in a Java context). > If the parameter is used to change some aspect of the method’s behavior, consider providing two differently-named methods instead. A common prefix is probably a good idea. For example, see FluentIterable’s partition and partitionWithPadding methods. Note that we can easily count or view the usages of these two methods separately. > > Different behaviors call for different methods. Of course, these methods can still be _implemented_ by passing a boolean to a private helper. > > If this is a constructor, you can’t exactly split it into differently-named constructors. But you can switch to factory methods, as recommended in _Effective Java_ <https://goto.google.com/ej3e–1|Item 1>. > This change may result in some callers having to write code such as condition() ? foo.methodA() : foo.methodB(). If it’s _many_ callers, you may need to reevaluate. In such cases prefer the alternatives below (@notjack which I’ve omitted from this slack message) instead of a boolean. > > (Aside: this same advice holds for methods that use null or a special magic value to trigger an alternative behavior. Provide for that behavior explicitly, with its own method, instead.) I think a similar spirit applies to dynamic parameter v.s. separate functions. Really I’d rather not do either of those, I’d prefer wrapper types so the function can act like it’s overloaded, but if that’s not feasible than duplicate functions would be my second choice.


notjack
2020-7-5 17:59:23

the reasoning isn’t mentioned much on this page, I’ll see if I can find some of the discussions that led to this advice


laurent.orseau
2020-7-5 18:01:45

I don’t think this applies here. Its not different behaviours, is different units. If that applied, then the same argument would apply to all arguments, and all functions would have no argument


laurent.orseau
2020-7-5 18:02:36

The problem with the parameter is that it’s silent. You may not know if you’re currently using radians or degrees


laurent.orseau
2020-7-5 18:03:35

But it’s usually not true because everyone should leave the default value of the parameter when exposed to the user


notjack
2020-7-5 18:04:26

I don’t see how it would apply to all arguments. Most arguments don’t come in multiple flavors of units.


soegaard2
2020-7-5 18:12:05

The guide resembles the style used in Apple apis (which has very long names - but they are very easy to google). If we consider keywords part of the name of a function, then (rotate #:radian 3.14) (rotate #:degrees 180) are more or less the same as rotateWithRadian and rotateWithDegrees.


soegaard2
2020-7-5 18:13:51

So the rationale must be: If a “small” change in the arguments triggers a “large” change in behaviour, then it can potentially lead to bugs. Therefore we advertise the difference using different function names.


notjack
2020-7-5 18:15:29

It’s also about call-site readability


soegaard2
2020-7-5 18:15:41

Nice term.


notjack
2020-7-5 18:18:28

strongly agreed (and not my invention)


laurent.orseau
2020-7-5 18:20:03

All arguments change the behaviour of the function


laurent.orseau
2020-7-5 18:22:52

I’m not sure about the parameter yet, one way or the other, but i don’t find duplicating every function particularly tasty.


sorawee
2020-7-5 18:23:09

I don’t like (rotate #:radian 3.14)


sorawee
2020-7-5 18:23:23

It allows people to write (rotate #:radian 3.14 #:degrees 10)


sorawee
2020-7-5 18:23:54

so you would need extra check to make sure that they are mutually exclusive


laurent.orseau
2020-7-5 18:27:52

The conversion functions to put everything in radians don’t seem so bad to me


soegaard2
2020-7-5 18:32:59

The mathematician in me likes the “stick to radian” approach. But the d-functions are (hopefully) convenient for people that only know about degrees (and haven’t heard about radians).


laurent.orseau
2020-7-5 18:34:41

Then you’d have to give them a proper name like rotated/degrees, and for those people the rotated one will be confusing


laurent.orseau
2020-7-5 18:50:34

Syntax parse has this, we should have this for functions too.


sorawee
2020-7-5 18:52:16

I feel really conflicted about this


sorawee
2020-7-5 18:52:37

syntax-parse is OK because it operates at compile-time.


sorawee
2020-7-5 18:52:51

For functions, you would want your functions to run fast at runtime (especially for recursive functions — it gets invoked many many times)


sorawee
2020-7-5 18:53:16

So I dislike runtime check for mutually exclusion


sorawee
2020-7-5 18:54:34

Compile-time check is possible, but you need to change define/lambda, which is not appealing


laurent.orseau
2020-7-5 18:56:09

Fair enough. One more thing on the list for racket2?


notjack
2020-7-5 19:00:01

(lambda (x) x)?


laurent.orseau
2020-7-5 19:03:32

changes the behaviour of the function: the return value is different if the input value is different.


laurent.orseau
2020-7-5 19:03:45

What I mean is that I don’t see a clear separation line


notjack
2020-7-5 20:22:57

I think it’s different for boolean parameters because you can actually reasonably make a function for each possible input.


laurent.orseau
2020-7-5 20:27:00

and when the boolean degree? turns into a category unit: (one-of 'rad 'deg 'grad) ? or if you have 2 booleans? The ‘call site readability’ is a good argument, and keywords do substantially mitigate the issue (by contrast to Java which has only positional arguments, unless this has changed)


laurent.orseau
2020-7-5 20:27:35

but the description doesn’t hint at that argument.


laurent.orseau
2020-7-5 20:29:04

This leads to an interesting general question: The good default is A, but the majority knows only B. I can make B an option, but then those who know only B will be confused by A. Those who know A will be annoyed/irritated if B is the default. How do we solve this?


laurent.orseau
2020-7-5 20:33:48

Also, keyword calls are notoriously ‘slow’. Don’t use keywords on recursive functions. (Though most of the time I start with a recursive function and end up using an internal let loop to avoid passing unchanging arguments around)


spdegabrielle
2020-7-5 21:05:11

Can the unit be included? Eg (rotate n degrees) or (rotate n radians)


spdegabrielle
2020-7-5 21:05:52

~~~That way you annoy everyone~~~


notjack
2020-7-5 21:32:26

For context, it’s from the internal java best practices site, which tries to provide terse guidelines (so mostly without long explanations). For minor stuff the reasoning behind the guidelines is usually not on that site, it’s found in separate mailing list discussions and bug reports that motivated the change to the site.


notjack
2020-7-5 21:33:25

@spdegabrielle that’s about what the wrapper types do, just with some extra nesting


notjack
2020-7-5 21:34:36

> The good default is A, but the majority knows only B. If this is the case, then I think it can’t be assumed that the good default is A. The API needs to put in effort to explain that to the reader and convince them of it.


laurent.orseau
2020-7-5 21:36:43

Suppose good = only option used by experts.


notjack
2020-7-5 21:37:21

Do you want to require that users of your API be experts?


notjack
2020-7-5 21:37:41

(not necessarily a bad thing to do, but definitely a choice that should be made consciously)


notjack
2020-7-5 21:41:54

I thought they were roughly compiled away in most cases


notjack
2020-7-5 21:42:35

unrelated: hi everyone, I’m so glad this channel exists :smile:


samth
2020-7-5 21:48:23

I think it’s not so much that the option is only known by experts, but that once you know enough to be an expert, you know that it’s definitely the right option


rokitna
2020-7-5 21:54:27

If this is in speed-critical code, specialized functions are probably good to have around.

The specialized functions should probably have the unit in their names, even if the unit is radians. People who are immersed in degrees or radians might not even consider there’s a choice unless they’re required to make it explicit.

If the extra verbosity is concerning, then putting the unit in the name of the module might be a way to keep the choice explicit but keep programs terse at the same time. However, even this might be a recipe for some surprises; when someone’s debugging a piece of control flow that passes from one module to another, they might not realize that one module uses radians and the other uses degrees. It’s a hazard to watch out for, and in this case I think I’d just live with the verbosity rather than risking it.

For things that aren’t so speed-critical, a good abstraction would be to have an angle data type like notjack’s saying, with at least degrees and radians constructors. That way, we don’t lose any precision, the intended units of the angle are clear, and the units are easy to forward along from one function call to another.

The specialized versions and angle data type can coexist.


notjack
2020-7-5 21:55:36

Oh the performance is a good point


notjack
2020-7-5 21:57:09

(someone please come up with a way for me to define functions that include simplification/inlining rules to optimize away allocations like that)


notjack
2020-7-5 22:07:09

pie-in-the-sky approach: (rewrite-rule rotate-degrees-specialization @summary{Using the @racket[rotate-degrees] function avoids allocating the intermediate @racket[degrees] value.} #:surface-as hidden-optimization (rotate (degrees 270)) (rotate-degrees 270))


rokitna
2020-7-5 22:13:37

starting with something like that, I’d find it nifty to be able to write (define (rotate-forwards x) (rotate x)) and have it automatically get the same optimizations


notjack
2020-7-5 22:14:20

I think I wouldn’t use that, probably


notjack
2020-7-5 22:14:26

or rather, wouldn’t need that


notjack
2020-7-5 22:14:45

if I’m making a new API on top of an existing one, I’m not sure there’s many cases where I want to wholesale copy a ton of rules


notjack
2020-7-5 22:20:36

sorawee
2020-7-6 01:38:59

Keyword calls are slow when they can’t be inlined. Unless you pass a function that has keywords around as a higher-order value, inliner is likely to succeed.


sorawee
2020-7-6 01:42:23

@notjack can you submit this at https://github.com/racket/rhombus-brainstorming?


notjack
2020-7-6 03:07:24

Sure, I’ll try to get around to that soon.


franci.dainese
2020-7-6 03:53:33

How about specifying the type of the angle as a symbol? (rotate 270 'radian), then we are back to the A is better but expert and B is more commonly known issue on what do choose as default to keep most of it terse


franci.dainese
2020-7-6 03:55:50

There are similar apis in the lib


sorawee
2020-7-6 03:56:40

why is that better than (rotate (radian 270))?


franci.dainese
2020-7-6 04:00:40

the default could be 'radian , which seems to be the case of the lib anyways. So you’ll specify (rotate 360 'degree) where you want degrees… But the main point is that it’s present in the standard library already for some operation (just that I can’t find it right now…)


franci.dainese
2020-7-6 04:01:05

it’s halfway verbose I’d say


sorawee
2020-7-6 04:03:35

What I like about using binding is that if you misspell, it’s a compile-time error. If you misspell a symbol, you might not even know it if the code is not reach in your test


sorawee
2020-7-6 04:04:28

Also, you can carry around the information


franci.dainese
2020-7-6 05:48:23

yeah I like compile-time errors the best but I noticed this pattern in the core library and I saw it missing in the discussion so I thought it was worth pointing it out


notjack
2020-7-6 05:49:42

I think it’s a pattern in the core library because it’s easy and simple for the implementor, not because it’s ideal for the caller