5

If I am inside a STM whose transaction fail, and I retry as part of the normal control flow (no STM collision etc..), I might want to indicate to someone outside a way to take corrective action.

If it's purely 'outgoing', then my STM can still be replayed purely.

How might I perform outgoing IO from a STM retry ?

Has anyone encountered this ? How evil is this ?

nicolas
  • 9,549
  • 3
  • 39
  • 83

1 Answers1

5

If you are sure that the performed IO does not affect the invariants of STM, you can use unsafeIOToSTM. Use it with great care, as all the unsafe stuff.

I do, however, wonder if you really need that. It's impossible to tell from the question. If you can, you should avoid it.

chi
  • 111,837
  • 3
  • 133
  • 218
  • I am performing STM in my server, and I'd like to display to the user some text (post a message to his UI) as to why the transaction has failed, so that he fixes the form submission and writes to the TVar again. – nicolas May 11 '16 at 12:35
  • 5
    @nicolas Use [`orElse`](https://hackage.haskell.org/package/stm-2.4.4.1/docs/Control-Monad-STM.html#v:orElse) and return a success/failure code. ``atomically $ (myPossiblyFailingSTMAction >> return True) `orElse` (return False)``. If you get `False` you know `myPossiblyFailingSTMAction` failed. Much cleaner than `unsafeIOToSTM` – Benjamin Hodgson May 11 '16 at 12:55
  • 1
    @nicolas In this case, one should make the transaction succeed but return a "failure" result, as Benjamin Hodgson suggests. – chi May 11 '16 at 13:46
  • @BenjaminHodgson indeed, but that's control flow which is orthogonal to delivering a STM value. what would be cleaner IMO would be to have a notion of directionality somewhere, where sending out information to a region of IO which is independant of my IO region is allowed – nicolas May 11 '16 at 14:44
  • @nicolas I don't understand your comment. If you need to return a value from your `STM` block then it's pretty easy to modify my suggestion: ``atomically $ (fmap Just myAction) `orElse` (return Nothing)`` (aka [`optional`](https://hackage.haskell.org/package/base-4.8.2.0/docs/Control-Applicative.html#v:optional)) – Benjamin Hodgson May 11 '16 at 14:56
  • @nicolas I see. That would make `atomically` not completely atomic, but that could be useful in some scenarios. I think that is currently unfeasible without `unsafe` primitives. It could be worth to wrap up the `unsafe`, directional approach in a library, maybe using a custom `STM'` variant which allows `retry value` making the value visible somehow. I am not sure about what a sensible design here could be (nor if there is one, actually). – chi May 11 '16 at 15:11
  • @BenjaminHodgson I mean the final value. whatever correction steps my user has to do to the input TVar is between him and the STM monad. I could indeed mix this control flow with my applicative logic. That's the point of retry, you leave it to the STM and .. itself (like the giveItem example from RWH http://book.realworldhaskell.org/read/software-transactional-memory.html ). but you could open that loop and leave it to the STM and the client where they are contrained in the data flow. – nicolas May 11 '16 at 15:12
  • @nicolas I just remembered that STM reads may refer to an _inconsistent_ state (since no locks are used), and that might cause a retry for a spurious reason (which should not be reported to the user). In such cases, `atomically` observes the retry, notices that some previously read `TVar` was changed, and re-attempts the transaction. To exclude these cases you would need - it seems - more information than what the STM library currently exposes. – chi May 11 '16 at 15:17
  • @nicolas I still don't really understand you. Are you suggesting continually retrying the transaction and asking the user to modify the `TVar` on another thread in the meantime? That seems like a strange thing to do. Why not just abort the transaction, ask the user to take corrective action, and retry when they've done so? – Benjamin Hodgson May 11 '16 at 15:20
  • @BenjaminHodgson for the same reason as those exposed in the RWH in their "Retrying a transaction" section : "Not only do we have to check whether the item was given, we have to propagate an indication of success back to our caller. The complexity thus cascades outwards " it's mixing control flow for getting a value with control flow for the application... STM can take care of it by itself when not touching IO with retry, and you could imagine it taking care of it when talking directionally with another system. But I dont think it can as pointed out by chi – nicolas May 11 '16 at 15:23
  • 2
    @BenjaminHodgson My understanding is that to "ask the user to take corrective action" we need to report the problem to the user. The problem is known at the `retry` point, but that information can not (easily) escape the STM monad. The non-compositional option is to `return (Left "reason")` instead of retrying, which requires to use a different `orElse` which is aware of this convention. Worse, when returning the error we probably do _not_ want the previous writes to take effect. It's quite tricky, and I am not yet sure that the approach can actually work sensibly. – chi May 11 '16 at 15:28
  • 4
    @nicolas I think I see what you mean now - you want the automatic wait/retry functionality of STM but you also want to report the occurrence and circumstances of a retry to the user. I'm afraid your idea is not (safely) compatible with the semantics of STM. It's a _transaction_ - either the whole transaction successfully took place or none of it did. Putting IO inside a transaction breaks the "or none of it did" part. The RWH chapter on STM has some guidance on when `unsafeIOToSTM` is appropriate – Benjamin Hodgson May 11 '16 at 15:33
  • @BenjaminHodgson you got the idea. ultimately that's a transaction. but retry is about the way to the commit, abstracting it away. I wanted to have the same abstraction, taking into account the fact that I have a user in front who would benefit of a clue to help the transaction to happen. and he is on a independant system than mine, so it is safe to send him a message as to why if my applicative logic has decided to not proceed with the transaction. I think you are right STM semantic is not good for that.. although I wish there was a way.. – nicolas May 11 '16 at 18:00
  • Thank you for the discussion, it helped me clarify a bit. I am wondering if it would may be possible with a refinement of IO. A clearer analogy is memoization. From outside I am pure. Now inside I might need IO but it's not the business of my users. In the normal course of operation it should be hidden. Maybe adding some typelevel quantification could allow me to hide that IO externally, and give structure to distinguish IO from here from IO from there – nicolas May 14 '16 at 09:44