2026/07/04

Newest at the top

2026-07-04 20:20:24 +0000merijn(~merijn@host-cl.cgnat-g.v4.dfn.nl) merijn
2026-07-04 20:16:09 +0000 <tomsmeding> that crazy thunk-inspecting unsafeUnperformIO would have a really amusing failure case though: if it's not a state token application, "Cannot unperformIO: already performed"
2026-07-04 20:15:41 +0000bdkl(~bdkl@user/bdkl) (Quit: bdkl)
2026-07-04 20:14:42 +0000tromp(~textual@2001:1c00:340e:2700:ec0e:2a6c:3177:923e)
2026-07-04 20:14:30 +0000 <int-e> etc.
2026-07-04 20:14:28 +0000 <int-e> You can't have unsafeUnperformIO;
2026-07-04 20:14:14 +0000 <int-e> You can't have unsafePerformIO; the way I'd describe the reason is that IO actions have a "lever" that triggers the underlying IO operations independently of forcing the action's result; unsafePerformIO inextricably ties these two things together. Unless you go crazy and write code that inspects thunks and code to find pending applications of state tokens I suppose.
2026-07-04 20:11:41 +0000 <int-e> (that's also wrong of course)
2026-07-04 20:11:29 +0000 <lambdabot> a -> IO a
2026-07-04 20:11:28 +0000 <int-e> :t Control.Exception.evaluate
2026-07-04 20:11:17 +0000 <monochrom> I don't actually know. Maybe it works out of the box.
2026-07-04 20:09:36 +0000 <EvanR> aw
2026-07-04 20:09:20 +0000merijn(~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 245 seconds)
2026-07-04 20:08:46 +0000 <yahb2> <no output>
2026-07-04 20:08:45 +0000 <tomsmeding> % :unset +s
2026-07-04 20:08:35 +0000 <monochrom> The way GHC encodes IO, it may be impossible.
2026-07-04 20:08:32 +0000 <yahb2> (0.01 secs, 0 bytes)
2026-07-04 20:08:31 +0000 <tomsmeding> % unsafe²id :: a -> a ; unsafe²id = System.IO.Unsafe.unsafePerformIO . unsafeUnperformIO where unsafeUnperformIO = return
2026-07-04 20:07:36 +0000 <EvanR> unsafeSquaredId
2026-07-04 20:07:10 +0000 <EvanR> it gives you id :: IO a -> IO a but pick up an unsafe squared
2026-07-04 20:06:57 +0000 <tomsmeding> well, unsafePerformIO . unsafeUnperformIO = id :: a -> a just fine. :)
2026-07-04 20:06:36 +0000 <monochrom> I was hoping unsafeUnperformIO . unsafePerformIO = id :: IO a -> IO a
2026-07-04 20:06:23 +0000haritz(~hrtz@user/haritz) (Quit: ZNC 1.8.2+deb3.1+deb12u1 - https://znc.in)
2026-07-04 20:06:06 +0000tomsmeding. o O ( return? )
2026-07-04 20:05:54 +0000 <monochrom> Consider unsafeUnperformIO :: a -> IO a >:)
2026-07-04 20:05:26 +0000 <tomsmeding> yeah good point
2026-07-04 20:05:24 +0000 <tomsmeding> uh, oh right, the point was that this is in pure code
2026-07-04 20:05:12 +0000 <EvanR> that's an external shenanigan xD
2026-07-04 20:05:00 +0000 <tomsmeding> EvanR: watch out, you're in IO so you can `catch`
2026-07-04 20:04:44 +0000 <EvanR> whoever gets there when it's a bottom can't observe a difference, without external shenanignas
2026-07-04 20:04:28 +0000Lord_of_Life(~Lord@user/lord-of-life/x-2819915) Lord_of_Life
2026-07-04 20:04:16 +0000 <EvanR> i.e. poor man's IVar
2026-07-04 20:04:02 +0000 <EvanR> if you update an IORef containing a bottom so it doesn't contain a bottom, and it's never updated again / updated with that same value only, then you might make an argument that it's still right
2026-07-04 20:03:24 +0000 <tomsmeding> ah yes, common subexpression elimination :)
2026-07-04 20:02:41 +0000 <monochrom> Usually the surprise is the converse. You run "unsafePerformIO (print "hello" >> return 10) 3 times expecting to print hello 3 times, but clearly GHC is right to let it happen just once (and cache the 10).
2026-07-04 20:02:40 +0000 <brooke2k> woah I hadn't thought of that either, good point... if the internal IORef gets copied to another thread that could get dangerous
2026-07-04 20:02:37 +0000 <tomsmeding> so that's a realistic situation where a pure computation may be evaluated more than once
2026-07-04 20:02:26 +0000merijn(~merijn@host-cl.cgnat-g.v4.dfn.nl) merijn
2026-07-04 20:02:18 +0000 <tomsmeding> (the locking-like technique that prevents this from happening, called "blackholing", is disabled by default because it turned out that enabling it cost more in the vast majority of cases than it won)
2026-07-04 20:01:38 +0000 <tomsmeding> also, if a thread encounters a thunk while another thread has already started evaluating it, the second thread may also start evaluating the thunk in parallel
2026-07-04 20:01:07 +0000 <tomsmeding> GHC does not duplicate computation willy-nilly (because that would be a pessimisation!), but yes it can happen
2026-07-04 20:00:40 +0000 <brooke2k> the idempotency thing helps though, thanks. I guess GHC optimizes under the assumption that a thunk can get run multiple times with no issues
2026-07-04 19:59:40 +0000 <brooke2k> which is how they implement Effectful.Error.Static already, I'm just trying to make sure that my version is safe
2026-07-04 19:58:52 +0000 <brooke2k> I don't think I can use ST in this instance... I'm trying to write an effectful static effect that internally uses IO, but gets wrapped in a pure interface
2026-07-04 19:58:18 +0000s00pcan(~s00pcan@71.214.104.207) s00pcan
2026-07-04 19:58:14 +0000 <brooke2k> damn actually that's helpful I'm not sure the function in question is idempotent
2026-07-04 19:58:07 +0000 <tomsmeding> if you just want some mutable references inside your function that aren't visible outside, then you may be able to write your function using ST, in which case you do retain purity (because you can runST safely)
2026-07-04 19:56:52 +0000 <brooke2k> hmm that makes sense
2026-07-04 19:56:18 +0000 <tomsmeding> if you modify IORefs that are also visible outside the function, then such things matter very much
2026-07-04 19:56:01 +0000 <tomsmeding> brooke2k: being a pure function also means that evaluating it twice, or evaluating it later, doesn't matter semantically