
On Mon, 2007-25-06 at 12:19 +0300, Benja Fallenstein wrote:
2007/6/25, Michael T. Richter
: OK, just to prevent this getting side-tracked: I'm absolutely uninterested in the results of performActionA before determining if performActionB is permitted/possible/whatever. Think more in terms of security permissions or resource availability/claiming than in terms of chaining results. I want to know before I begin to collect the results of performAction* that I will actually stand a chance at getting results at all.
Uh, the posts you quote were precisely about how to do that. No side-tracking going on. :-)
It looked to me like there were people arguing about whether the "x"
returned from one action was going to be used in the next action.
Let me try and rephrase the question. :)
A conventional approach to what I'm doing would be something like this
(in bad pseudocode):
doStuff():
if checkPossible([opA, opB, opC]):
A
B
C
else:
exception "Preconditions not met"
My objection to this is its error-prone scaffolding:
1. There's no enforced link between the checking operations and the
actual operations. By mistake or by deliberate action it is
possible to put operations in the main body which have not been
checked by the guards.
2. As code evolves and changes, it is very easy to have the check
diverge from the contents of the body as well.
Now if the actions were trivial or easily reversible, an alternative
model is something like this (in bad pseudocode) where it's assumed that
each operation checks for its privileges/capabilities/whatever as part
of its operation:
doStuff2():
A
try:
B
try:
C
catch:
undoB
throw
catch:
undoA
This looks to me like Don Stuart's "executable semi-colons" and could be
coded as a pretty conventional monad (unless my eyes are deceiving me).
But if doing A, say, involved expensive operations (think: generating an
RSA key or making a database connection on a heavily-loaded server) or
if doing B involved modifying some external state that is difficult to
undo this is a less-than-ideal model. Let's say that C fails for
whatever reason (insufficient privileges, the database server is dead,
the phase of the moon is wrong for the species of chicken sacrificed at
the keyboard -- anything), then we've got time wasted in A and B has
just changed something we can't easily unchange.
So I'd like some way of getting the automated check of
permission/capability/availability/whatever done before performing the
actual actions.
Now in a language where functions are identifiable types, a solution
could look like this (among a myriad of other possible solutions):
check(Operation):
case Operation of:
A:
return checkConditionA
B:
return checkConditionB
C:
return checkConditionC
runStuff(actions):
for each action in actions:
if not check(action.left):
throw CheckFailure
for each action in actions:
action.left(action.right)
doStuff3():
actions=[(A, a_args), (B, b_args), (C, c_args)]
try:
runStuff(actions)
catch CheckFailure:
actions=nil
The check() function called here can use the identity of the action
provided plus any information provided externally (network connections
open, permissions available, etc.) to pass/fail the
capabilities/resources/whatever and the action's execution is deferred
until the check has passed. The action's check *AND* its execution is
unavailable to the programmer so there's less room for fraud and
oversight and all the other things which make programs buggy and
unreliable and such joys to work with both as a programmer and as a
user. In fact with languages as malleable as Ruby (or possibly even
Python) some of the ugly scaffolding above could be made to vanish
behind the scenes leaving pretty clean code behind. (Uglier languages
like C/C++, of course, would leave all the scaffolding lying around, but
it would still be doable.)
But of course this can't be done in Haskell this way because functions
aren't items in Haskell. There is no function equality check. My
check() function can't look like:
check :: (a->b)
check A = ...
check B = ...
check C = ...
check _ = error "no such function"
This leaves me in a position I can't think myself out of (hence the cry
for help). I'd like it to be possible to have a do block with as little
visible scaffolding as possible (ideally none) where I can do the
equivalent of doStuff3() and runStuff() from the above pseudocode.
Now here's the tricky part....
I'd ideally like to be able to do this so that it would be possible to
start with the doStuff2 implementation behind the scenes (check as you
go) and then, by changing the scaffolding behind the scenes, do the
doStuff3() implementation without touching a line of client code. In
effect I'd like to be able to change computing strategies on the fly
without the client code having to be modified. If we look at my
doStuff3() as an example, for instance, I could switch it over from a
pre-check to a check-as-you-go system pretty easily by modifying
runStuff() and the check(). runStuff() would be modified to interleave
the check with the call and check would be modified to return an undo
operation kept in an accumulator by runStuff() or nil. If nil is
returned, the list of undo operations maintained by runStuff would get
executed and the failure signalled.
Does this make more sense now? And can it be done somehow in Haskell?
--
Michael T. Richter