4 ms·
The fugly state in objects is tricky. One way to look at a class is to treat it as a kind of coroutine. Consider some pseudocode: class Stats sum Nu
by ben509 7y ago
The fugly state in objects is tricky. One way to look at a class is to treat it as a kind of coroutine. Consider some pseudocode:
class Stats
sum Num = 0
sum_squared Num = 0
count Num = 0
acceptNum(x)
sum += x
sum_squared += x ^ 2
count += 1
mean()
return sum / count
variance()
# I think this is right...
return (sum_squared / count) - (sum / count) ^ 2
But that could just as well be:
coro Stats
sum = sum_squared = count = 0
result = nothing
loop
msg = yield result
switch msg.name
case acceptNum
sum += msg.x
...
case mean
result = sum / count
...
We don't do objects like this. We really want a file object, for instance, to be something like:
coro FileReader
# Not opened yet
raw_handle = popen(...)
# Reading
...
if msg.name == read
yield read(raw_handle)
...
# Data exhausted
...
if msg.name == read
yield eof
...
# Handle closure
...
if msg.name == read
throw ReadOnClosedFile
...
(I think you could handle inheritance with a scheme like this, presumably a "subclass" would simply yield to the "superclass".)
What's nice about that is the state is explicit in the structure of the coroutine. Ideally, we'd have some sort of protocol types that reflect that we're accepting different kinds of messages at different stages in an object lifecycle. In this example, after a file closes, it's a type error to try and read from it.
Instead, we generally munge it with properties, depending on lots of checks instead of structure.
We can solve state through more classes, of course. The Builder pattern handles a specific case of this quite nicely, you have one mutable construct that then returns an immutable construct.
But to use that more universally would lead to a profusion of classes.