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

bracket releases too early when combined with NonDet #489

Open
tomjaguarpaw opened this issue Sep 13, 2024 · 2 comments
Open

bracket releases too early when combined with NonDet #489

tomjaguarpaw opened this issue Sep 13, 2024 · 2 comments

Comments

@tomjaguarpaw
Copy link

When I use bracket (or rather bracket_, in the example below) inside a runNonDet block the resource is released too early. Is this expected?

(I feel this must be a common question, so apologies if it's answered elsewhere, but I didn't find this precise issue in the documentation, nor from searching the issues here.)

{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE DataKinds #-}

import Control.Applicative ((<|>))
import Polysemy (embed, runM)
import Polysemy.NonDet (runNonDet)
import Polysemy.Resource (bracket_, runResource)

example :: IO [Int]
example =
  runM $
    runNonDet $
      runResource $
        bracket_
          (embed (putStrLn "Acquired"))
          (embed (putStrLn "Released"))
          ( do
              r <- pure 1 <|> pure 2
              embed (putStrLn "Used")
              pure r
          )
-- ghci> example
-- Acquired
-- Used
-- Released
-- Used
-- Released
-- [1,2]
@tek
Copy link
Member

tek commented Sep 13, 2024

Interesting. I assume it's expected for pure interpreters to distribute over runNonDet, but it's pretty weird when it's Resource 😅

This resembles the interaction with Error as described in lexi's semantics-zoo article, so it seems somewhat reasonable that the release action is run as part of each branch before starting the next.

Given that you wrote "too early" rather than "twice", I assume that you expected the distributive semantics – how would you expect the release action to be executed? Should the entire bracket be distributed? I've never thought about this before, so no idea what would be right.

#246 is the closest discussion I can find, though its initial focus is different from this issue.
But if you swap runNonDet and runResource, the release action is only executed once, FWIW.

In case you actually didn't explore the algebraic properties and just want to do IO with bracket, using resourceToIOFinal also provides those more familiar semantics 😄

@tomjaguarpaw
Copy link
Author

Thanks for your response.

Given that you wrote "too early" rather than "twice", I assume that you expected the distributive semantics – how would you expect the release action to be executed? Should the entire bracket be distributed? I've never thought about this before, so no idea what would be right.

I would say that the desirable behaviour is that the release happens exactly once, at the first time at which it is no longer possible to run a continuation that holds the resource.

But if you swap runNonDet and runResource, the release action is only executed once, FWIW.

Unfortunately that's no good either, at least for runNonDetMaybe. Consider the following. example1 is fine, I get the expected result [2]. But example2 is not fine. I get the result Nothing. It should be Just 2!

{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE DataKinds #-}

import Control.Applicative (asum, empty, (<|>))
import Control.Monad (guard)
import Polysemy (embed, runM)
import Polysemy.NonDet (runNonDet, runNonDetMaybe)
import Polysemy.Resource (bracket_, runResource)

example1 :: IO [Int]
example1 =
  runM $
    runResource $ do
      runNonDet $ do
        r <-
          bracket_
            (embed (putStrLn "Acquired"))
            (embed (putStrLn "Released"))
            ( do
                r <- (pure 1 <|> pure 2)
                embed (putStrLn ("Used: " ++ show r))
                pure r
            )
        guard (r /= 1)
        pure r

-- ghci> example1
-- Acquired
-- Used: 1
-- Used: 2
-- Released
-- [2]

example2 :: IO (Maybe Int)
example2 =
  runM $
    runResource $ do
      runNonDetMaybe $ do
        r <-
          bracket_
            (embed (putStrLn "Acquired"))
            (embed (putStrLn "Released"))
            ( do
                r <- (pure 1 <|> pure 2)
                embed (putStrLn ("Used: " ++ show r))
                pure r
            )
        guard (r /= 1)
        pure r

-- ghci> example2
-- Acquired
-- Used: 1
-- Released
-- Nothing

resourceToIOFinal doesn't seem to help either:

{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE DataKinds #-}

import Control.Applicative (asum, empty, (<|>))
import Control.Monad (guard)
import Polysemy (embed, embedFinal, runFinal, runM)
import Polysemy.NonDet (runNonDet, runNonDetMaybe)
import Polysemy.Resource (bracket_, resourceToIOFinal, runResource)

example1 :: IO [Int]
example1 =
  runFinal $
    resourceToIOFinal $ do
      runNonDet $ do
        r <-
          bracket_
            (embedFinal (putStrLn "Acquired"))
            (embedFinal (putStrLn "Released"))
            ( do
                r <- (pure 1 <|> pure 2)
                embedFinal (putStrLn ("Used: " ++ show r))
                pure r
            )
        guard (r /= 1)
        pure r

-- ghci> example1
-- Acquired
-- Used: 1
-- Used: 2
-- Released
-- [2]

example2 :: IO (Maybe Int)
example2 =
  runFinal $
    resourceToIOFinal $ do
      runNonDetMaybe $ do
        r <-
          bracket_
            (embedFinal (putStrLn "Acquired"))
            (embedFinal (putStrLn "Released"))
            ( do
                r <- (pure 1 <|> pure 2)
                embedFinal (putStrLn ("Used: " ++ show r))
                pure r
            )
        guard (r /= 1)
        pure r

-- ghci> example2
-- Acquired
-- Used: 1
-- Released
-- Nothing

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

No branches or pull requests

2 participants