News for hackers. Stuff that matters.
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Smaug123 on Sunday September 06, 2020 @06:13AM (#24390307)
For bonus points, you can use a sufficiently strong type system to make your interface hard to misuse in a slightly different direction.

If you require function `setUp : unit -> unit` to have been called before function `doThing : unit -> unit`, you can instead alter the types, introducing a `type Token = private | Token` which the consumer can't create for themselves but which your implementation can create. Then just tweak the types so you have `setUp : unit -> Token` and `doThing : Token -> unit`; since the user can't make a `Token` themselves, they can only have obtained it from calling `setUp` first.

Re:Code Health: Make Interfaces Hard to Misuse (2018) by chriswarbo on Sunday September 06, 2020 @07:47AM (#24390721)
The easiest way to use types for this is to have separate types for (un)initialised values, e.g. instead of having a 'Database' with methods for 'connect', 'select', 'insert', etc. where the caller has to run 'connect' first, we instead split it into two so that the first object (e.g. 'DisconnectedDatabase') doesn't have any query methods, but does have the 'connect' method which returns a 'Database', and the query methods exist on that.

A few things to keep in mind when doing this:

- If we end up with a classes which only has a single method (e.g. 'DisconnectedDatabase' with only a 'connect' method), then it might be better as a standalone function, or a smart constructor, etc. ( http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom... )

- We can use this approach to prevent code being called prematurely (e.g. querying has to come after connecting), but we can't prevent code being called too late; e.g. trying to query after a 'Database::disconnect' method has been called. (This is where linear/uniqueness types, etc. can be used)

Re:Code Health: Make Interfaces Hard to Misuse (2018) by GeneralMayhem on Sunday September 06, 2020 @01:21PM (#24392921)
>we can't prevent code being called too late

You can get pretty close in C++. All it takes is for the disconnect method to be rvalue-qualified:

void disconnect() &&;

and to turn on use-after-move compiler warnings. Now disconnecting has to be invoked as:

std::move(db).disconnect();

and if you refer to db after that Clang will tell you to knock it off. (Of course, for a database object, it probably makes more sense to use RAII and disconnect in the destructor, but this pattern can help if you have multiple ways for the object's useful lifetime to end.)

That won't catch users who have a pointer or reference alias to the object, but that's more like a use-after-free risk, which exists any time you have references.

Re:Code Health: Make Interfaces Hard to Misuse (2018) by mehrdadn on Sunday September 06, 2020 @06:37AM (#24390392)
I know about this technique and I can pretty much never bring myself to actually do this in practice. For one thing there's pretty much always something more useful to return, and suddenly it becomes incredibly unergonomic to return an extra token and then force the user to pass it elsewhere. For another, it doesn't even automatically prevent passing the token to the wrong instance of the object either, or passing it twice, or other kinds of undesired usage. Adding dynamic checking for any of this also adds runtime overhead, and all this seems quite confusing and difficult to justify, especially for those unfamiliar. (On top of additional issues I'm probably forgetting.) Do you actually find a way to put this technique into good use in practice?
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Smaug123 on Sunday September 06, 2020 @06:54AM (#24390475)
I suppose it's less useful as a standalone technique - I've used it before but not that heavily (e.g. in a library built around sequencing effects in a certain way, where I've wanted to guarantee that one effect can't be called before another). Really we'd want a considerably more powerful type system - Idris 2's quantitative types and/or Rust's ownership, for example - before that particular technique really starts to shine.

However, the general mindset is one I use all the time. For example, if I've written a state machine that is represented explicitly in the types, I might be able to encode some of the state transitions in the type system directly, so the compiler prevents me writing certain invalid transitions. For example, if state B can only be reached after state A, I might write the in-memory representation of B to contain an in-memory representation of A; this guarantees I must have passed something through state A to get anything in state B. Doesn't save you from everything - in particular it doesn't solve your "passing the token to the wrong instance of the object" problem - but any constraint on the state space is useful in my book. When the state machine gets sufficiently large, in my experience, the benefits of the restricted state space start outweighing the negatives of manual token shuffling.

(I have a blog post brewing about this general class of techniques, but I don't know when it'll be ready.)

Re:Code Health: Make Interfaces Hard to Misuse (2018) by Akronymus on Monday September 07, 2020 @05:00AM (#24397865)
Couldn't you return a tuple with the object and the useful return value though?
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Smaug123 on Monday September 07, 2020 @05:52AM (#24398171)
Sure - that's not the difficult part, in my book. (I use F#, where there's tuple destructuring syntax, so it's easy.) The hard parts are about making sure you can't reuse the token in the wrong context.
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Akronymus on Monday September 07, 2020 @05:59AM (#24398201)
Oooh. Yeah, that makes sense.

I sadly don't know enough F# to even begin to think of something to make the tokens context safe.

Re:Code Health: Make Interfaces Hard to Misuse (2018) by Smaug123 on Monday September 07, 2020 @11:54AM (#24400237)
For what it's worth, I don't think it's possible in F# (perhaps modulo absurd tricks that will never go into prod code, like type-level naturals).
Re:Code Health: Make Interfaces Hard to Misuse (2018) by jrimbault on Sunday September 06, 2020 @06:47AM (#24390445)
With explicit enforced lifetimes it's harder to misuse. The first method returns the tuple of (Something, Token) and the second method consumes/takes ownership of the Token plus whatever its arguments. This way each call to function2 has to be preceded by a call to function1 and it's checked by the compiler.
Re:Code Health: Make Interfaces Hard to Misuse (2018) by p_l on Sunday September 06, 2020 @11:25PM (#24396378)
It's reasonably easier when a method can transparently change the type, for example in Common Lisp - where the object system provides a clear controlled way to change the class of an object (using apropriately called method change-class)
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Kinrany on Sunday September 06, 2020 @08:18AM (#24390844)
Even better: make setUp return doThing.
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Smaug123 on Sunday September 06, 2020 @09:18AM (#24391108)
Yes and no - `doThing` itself is less inspectable, and it's sometimes helpful to separate the implementation from the instructions. (I wrote https://www.gresearch.co.uk/article/defunctionalisation/ ; the usual introduction is https://blog.sigplan.org/2019/12/30/defunctionalization-ever... .)
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Kinrany on Sunday September 06, 2020 @05:10PM (#24394700)
Oooh, I did think of that article!
Re:Code Health: Make Interfaces Hard to Misuse (2018) by hsson on Sunday September 06, 2020 @08:20AM (#24390854)
Isn't this basically the point of the builder pattern?
Re:Code Health: Make Interfaces Hard to Misuse (2018) by Smaug123 on Sunday September 06, 2020 @12:43PM (#24392614)
I don't think so? I'm not very well up on my OOP patterns, but in my mind the builder pattern in OOP corresponds to the initial algebra pattern in FP, and initial algebras can model domains with or without that kind of restriction. I think they're basically orthogonal.