Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: The Sodium API is too dangerous. #72

Closed
romansl opened this issue Aug 4, 2015 · 21 comments
Closed

Java: The Sodium API is too dangerous. #72

romansl opened this issue Aug 4, 2015 · 21 comments
Labels

Comments

@romansl
Copy link

romansl commented Aug 4, 2015

Here some common errors I meet in my code:

  1. forget to call addClanup for the result of listen - the computations graph will be GCed.
  2. send inside listen - Exception
  3. forget to call defer in the flatMap example - the event will be lost
  4. call send second time in transaction - Exception

The send is most dangerous method in lib. Though must be the safest. We need to do something with it.

@the-real-blackh
Copy link

It should only ever be used when you absolutely have to use it. I don't agree that it is more dangerous than it should be. Maybe we need to talk more about what you are trying to do.

@kbaldor
Copy link

kbaldor commented Aug 4, 2015

It sounds like all but the third issue are the result of crossing the
threshold between the FRP and non-FRP code.

I may have to agree that 'defer' is a bit of a strange beast; it seems to
require the programmer to give more thought to the underlying evaluation
model than would be preferred. i.e. it seems to run contrary to the
so-called 'denotative' semantics or at least the declarative style that I
associate with FRP.

On Tue, Aug 4, 2015 at 8:32 AM, the-real-blackh notifications@github.com
wrote:

It should only ever be used when you absolutely have to use it. I don't
agree that it is more dangerous than it should be. Maybe we need to talk
more about what you are trying to do.


Reply to this email directly or view it on GitHub
#72 (comment).

@the-real-blackh
Copy link

Exactly. The interface between FRP and non-FRP does require some care, and so the "danger" of these things is there to help you. The best example of this is send() inside listen().

I understand your reservations about defer(), though strictly speaking it is denotational (except that the implementation doesn't match the semantics until I have fixed issue #71). It was intended for certain operational cases, not for general use. I think the solution is to put it into Operational along with updates() and value().

@romansl
Copy link
Author

romansl commented Aug 5, 2015

It seems I found a solution: to make special version of send:

    public fun sendInExplicitTx(a: A) {
        val tx = Transaction()
        try {
            send(tx, Value(a))
        } finally {
            tx.close()
        }
    }

It solves the problems 2, 3 and 4.

@the-real-blackh
Copy link

This is a bad, bad idea, for two reasons:

  1. It is VERY important that people do not write their own primitives, because denotational semantics and therefore compositionality can be easily compromised. Most of the value of FRP is suddenly lost. This is why I (went to all the trouble I did) to make it so send() can't be used inside a listen() callback.
  2. Even if it were a good idea to encourage people to write their own primitives (which it is not), it is not possible to track dependencies, so it is not possible to guarantee execution in dependency order, so it is not possible to guarantee compositional semantics.

I urge you not to go down this path.

@romansl
Copy link
Author

romansl commented Aug 5, 2015

Yes it allows user to write its own primitive-like operations, but them will be not the real primitives. The send will always be the start of computations, and listen will always be the end.

It easier to understand for user. The order of computations will be defined by this rule.

C1 = { send -> primitive1 -> primitive2 -> listen(doIoAndSendC2) }
C2 = { send -> primitive1 -> listen(doIo) }

@the-real-blackh
Copy link

If send() is the start of a computation, then you won't fall foul of "2. send inside listen - Exception". You also shouldn't use it inside a map() function.

@romansl
Copy link
Author

romansl commented Aug 5, 2015

You right, I think about it.

@romansl
Copy link
Author

romansl commented Aug 5, 2015

I understood one thing: the problem with flatMap and my advantures with send cinsist of trying to do IO in primitives.

Other idea: how about throw listen body from the transaction? We can then safely call send in there, and automatically solve the problem with defer.

Or just provide user the ability to create explicit transaction?

@the-real-blackh
Copy link

I suggest you do it this way:

.listen((x) -> { new Thread(() -> { ... do I/O ... .send(y); }).start(); }

@romansl
Copy link
Author

romansl commented Aug 5, 2015

Yes I can, but as I described in #64, I meet with unpredictable exception with codel like this. I can fix it here, but I can not guarantee that not meet this problem again. I already spent in this place about 10 hours. The code is simple, but not easy.

Actially I want to send - listen pair be as safe as possible.

@romansl
Copy link
Author

romansl commented Aug 5, 2015

Ouch... not like this.

I want to listen - IO - send be as safe as possible.

@the-real-blackh
Copy link

It can be done without an unpredictable exception. I'll try to help. Are you sometimes not launching a new thread? You must guarantee to do so.

@romansl
Copy link
Author

romansl commented Aug 5, 2015

Yes, man can walk through mine field without explosion, but it is wrong way.

@the-real-blackh
Copy link

Sodium should be very free from anything non-deterministic. What exception are you getting?

@romansl
Copy link
Author

romansl commented Aug 5, 2015

Here is the solution of my problem in Android:

val handler = Handler()
stream.listen {
    val result = it.value
    handler.post {
         sink.send(result)
    }
}

Is it legal?

This code is not multithread. And it solution is for Android only. Actually this code eqivalent to my suggestion to make listen execute body outside from the transaction.

@the-real-blackh
Copy link

What does handler.post do?

@romansl
Copy link
Author

romansl commented Aug 5, 2015

It just queue of actions like main application queue.
More detail:

val handler = Handler()
val stream = ...
val sink = ...

stream.listen {
    val result = it.value
    handler.post {
        sink.send(result)
    }
}

handler.post {
    stream.send(Unit)
}

@romansl
Copy link
Author

romansl commented Aug 5, 2015

Handler.post is equivalent to Executors.singleThreadExecutor().execute.

@the-real-blackh
Copy link

Thanks - Not very familiar with that java.util.concurrent stuff. So this code of yours would definitely be legal Sodium, and it shouldn't throw an exception.

val handler = Handler()
stream.listen {
    val result = it.value
    handler.post {
         sink.send(result)
    }
}

@romansl
Copy link
Author

romansl commented Aug 6, 2015

Just small experiment: SodiumFRP/sodium-kotlin@1a0f8fd#diff-9c15ed34aea5dff2bbe7206855238731L120

The listen now is outside of transaction. Now you can call send inside listen, but you still can not do it in map or other primitives.

Also notice the defer implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants