Logs: liberachat/#haskell
| 2026-04-09 11:32:49 | <mauke> | Milan_Vanca: that's not a function |
| 2026-04-09 11:32:50 | <Freakie> | i mean heavy wouldn't be a function, it would just be an expression |
| 2026-04-09 11:33:59 | <Freakie> | if you're unsure you can always write some code that calls it and add some debug traces |
| 2026-04-09 11:35:49 | <Freakie> | also I've asked before but I'm hoping someone knows more than I do; does anyone know of an effective way to link dependencies installed through cabal when doing FFI from Haskell to C++? |
| 2026-04-09 11:36:16 | <Milan_Vanca> | mauke: Is the distiction necessary? |
| 2026-04-09 11:36:36 | <Freakie> | the only way I've found to do it seems to be to manually give the install locations as arguments during the linking phase but that is extremely cumbersome and error-prone |
| 2026-04-09 11:36:57 | <mauke> | Milan_Vanca: depends. necessary for what? |
| 2026-04-09 11:38:05 | × | myme quits (~myme@2a01:799:d5e:5f00:fdf3:5ded:cda8:566f) (Ping timeout: 245 seconds) |
| 2026-04-09 11:38:58 | <Milan_Vanca> | heavy_pi :: Double main = print heavy_pi; print heavy_pi... |
| 2026-04-09 11:39:09 | → | myme joins (~myme@2a01:799:d5e:5f00:374a:96c4:fb0d:7007) |
| 2026-04-09 11:39:51 | <Milan_Vanca> | maybe i am missing "do" |
| 2026-04-09 11:41:00 | <Freakie> | you need do notation when in the IO monad |
| 2026-04-09 11:41:01 | × | troydm quits (~troydm@user/troydm) (Quit: What is Hope? That all of your wishes and all of your dreams come true? To turn back time because things were not supposed to happen like that (C) Rau Le Creuset) |
| 2026-04-09 11:41:28 | <Freakie> | to answer your question though, pure expressions will only be evaluated once (heavy_pi :: Double etc) |
| 2026-04-09 11:41:30 | <mauke> | you never "need" do notation |
| 2026-04-09 11:42:02 | <tomsmeding> | Milan_Vanca: using a variable name multiple times does not result in its definition being computed multiple times, except when that variable is secretly a function |
| 2026-04-09 11:42:48 | <mauke> | (where "secretly a function" is that class constraints thing I was talking about) |
| 2026-04-09 11:42:56 | <tomsmeding> | variables can be secretly functions if they are actually polymorphic with a typeclass argument, such as `x = 4` |
| 2026-04-09 11:43:18 | <tomsmeding> | there we have `x :: Num a => a`, and x's value is different for each type you can fill in for `a` |
| 2026-04-09 11:43:26 | <Milan_Vanca> | so x in your example is function and wont be reused? |
| 2026-04-09 11:43:42 | <tomsmeding> | thus it's really a function: from the "Num a" evidence to the resulting value; the `=>` arrow should be read as a function arrow here |
| 2026-04-09 11:43:53 | <tomsmeding> | Milan_Vanca: indeed, because it _cannot_ be reused: its value is different at different types 'a'! |
| 2026-04-09 11:44:20 | <mauke> | well... |
| 2026-04-09 11:44:22 | <tomsmeding> | what GHC _could_ do is reuse x when used twice at the same type; whether that actually happens depends on what GHC's optimiser decides to do, no guarantees |
| 2026-04-09 11:44:30 | <Freakie> | I mean if called with the same type argument it should reuse the expression? |
| 2026-04-09 11:44:31 | <Milan_Vanca> | yeah but in example of main = do print x; print x it is always same right? should not be reevaluated? |
| 2026-04-09 11:44:35 | <mauke> | functions are also reused |
| 2026-04-09 11:44:50 | <mauke> | but it sounds like you're talking about the result of a function |
| 2026-04-09 11:45:14 | <Freakie> | if x is a value from a non-deterministic monad it should be recomputed |
| 2026-04-09 11:45:25 | <tomsmeding> | assuming you mean `print (x :: Int)`, that compiles to something like `main = print (x dNumInt) >> print (x dNumInt)`, where dNumInt is the "Num Int" type class dictionary (evidence) |
| 2026-04-09 11:45:26 | <mauke> | Freakie: ? |
| 2026-04-09 11:45:59 | → | haritz joins (~hrtz@2a01:4b00:bc2e:7000:d5af:a266:ca31:5ef8) |
| 2026-04-09 11:45:59 | × | haritz quits (~hrtz@2a01:4b00:bc2e:7000:d5af:a266:ca31:5ef8) (Changing host) |
| 2026-04-09 11:45:59 | → | haritz joins (~hrtz@user/haritz) |
| 2026-04-09 11:46:00 | <tomsmeding> | then GHC can decide to do common subexpression elimination, and rewrite that to `main = let x' = x dNumInt in print x' >> print x'`, but GHC may or may not decide to do this |
| 2026-04-09 11:46:21 | <tomsmeding> | at which point, `x' :: Int` and is a normal variable, so that `print x' >> print x'` will definitely compute x only once |
| 2026-04-09 11:47:10 | <tomsmeding> | Freakie: the only way I can make sense of that is `unsafePerformIO (uniform <$> newStdGen)` or whatever the precise API is of System.Random |
| 2026-04-09 11:47:23 | <Freakie> | would it have to use unsafePerformIO? |
| 2026-04-09 11:47:24 | <tomsmeding> | in which case, well, you're using unsafePerformIO, you better know what you're doing |
| 2026-04-09 11:47:29 | <Milan_Vanca> | what about main = do print x; print x; ... lot of other code; print x will last x be kept or recomputed? |
| 2026-04-09 11:47:37 | <tomsmeding> | Freakie: yes, because without IO, everything is pure and hence deterministic |
| 2026-04-09 11:47:40 | <mauke> | Milan_Vanca: depends on the type of x |
| 2026-04-09 11:48:03 | <tomsmeding> | if x has a monomorphic type, reused; if x has a polymorphic type with type class constraints, recomputed (likely) |
| 2026-04-09 11:48:04 | <Milan_Vanca> | the same as first one |
| 2026-04-09 11:48:12 | <mauke> | first what? |
| 2026-04-09 11:48:29 | <Freakie> | tomsmeding we're not strictly talking purity here though |
| 2026-04-09 11:48:51 | <Milan_Vanca> | tomsmeding showed that x will be computed only once.. so first 2 will be reused then forgoten then recomputed? |
| 2026-04-09 11:49:03 | <mauke> | what |
| 2026-04-09 11:49:11 | <tomsmeding> | Milan_Vanca: no, you cannot say anything about your example unless you know what the type of x is |
| 2026-04-09 11:49:24 | <tomsmeding> | if x :: Int, then x will be computed only once |
| 2026-04-09 11:49:33 | → | Enrico63 joins (~Enrico63@host-212-171-80-94.pool212171.interbusiness.it) |
| 2026-04-09 11:49:43 | <Freakie> | it's also tricky because if you assume *any* number of instructions you can't say anything register allocation |
| 2026-04-09 11:49:44 | <tomsmeding> | if x :: Num a => a, or Show a => [a], or something like that, then it will (likely!) be recomputed |
| 2026-04-09 11:49:53 | <Freakie> | if it can't fit in the registers it will have to be recomputed |
| 2026-04-09 11:49:55 | <Freakie> | (I think) |
| 2026-04-09 11:49:59 | <mauke> | Freakie: ??? |
| 2026-04-09 11:50:06 | <Milan_Vanca> | ? |
| 2026-04-09 11:50:18 | <tomsmeding> | Freakie: we're talking about the semantic level here, where reading the value from memory doesn't count as recomputation |
| 2026-04-09 11:50:38 | <Freakie> | i guess memoization invalidates what I just said yeah |
| 2026-04-09 11:50:39 | <tomsmeding> | if you want to know whether a memory read will occur, well, good luck reading the assembly, no guarantees |
| 2026-04-09 11:51:00 | <tomsmeding> | (that's like 4 abstraction levels down suddenly from what we were talking about_) |
| 2026-04-09 11:51:08 | <Milan_Vanca> | what about this? |
| 2026-04-09 11:51:10 | <Milan_Vanca> | https://paste.tomsmeding.com/resvyMiU |
| 2026-04-09 11:51:22 | <tomsmeding> | Milan_Vanca: precisely that code, without any type annotations? |
| 2026-04-09 11:51:30 | <mauke> | depends on the type of x |
| 2026-04-09 11:51:37 | <mauke> | which depends on whether the monomorphism restriction is on |
| 2026-04-09 11:51:38 | <Milan_Vanca> | Lets say yes |
| 2026-04-09 11:52:01 | <tomsmeding> | if precisely that code, then `x = 4` desugars to `x = fromInteger (4::Integer)`, and you get `x :: Num a => a` |
| 2026-04-09 11:52:04 | <Milan_Vanca> | mauke: Can I decide if it is on or not? |
| 2026-04-09 11:52:17 | <tomsmeding> | that call `fromInteger (4::Integer)` will likely be recomputed, but whether that actually happens depends on the GHC optimiser |
| 2026-04-09 11:52:55 | <mauke> | Milan_Vanca: yes, https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/monomorphism.html#extension-MonomorphismRestriction |
| 2026-04-09 11:53:01 | <mauke> | turning it off is a language extension |
| 2026-04-09 11:53:26 | <tomsmeding> | oh, mauke is right |
| 2026-04-09 11:53:32 | → | __monty__ joins (~toonn@user/toonn) |
| 2026-04-09 11:53:53 | <tomsmeding> | x will get a monomorphic type here (probably defaulting to Integer) because MonomorphismRestriction is on by default |
| 2026-04-09 11:54:16 | <mauke> | with MR on, it kinda desugars to 'x = fromInteger (4::Integer) :: Integer' :-) |
| 2026-04-09 11:54:42 | <tomsmeding> | at which point the fromInteger is likely optimised away, but that's irrelevant to the point of whether things are computed once or more than once |
| 2026-04-09 11:54:44 | <mauke> | except '4::Integer' is not actually expressible in Haskell; it's a native Integer literal |
| 2026-04-09 11:54:48 | <tomsmeding> | yeah |
| 2026-04-09 11:54:54 | <Milan_Vanca> | I haven't disabled it so I guess it is enabled and thus x will be recomputed... got it.. Had I done something like this |
| 2026-04-09 11:54:59 | <Milan_Vanca> | https://paste.tomsmeding.com/U94PhW5b |
| 2026-04-09 11:55:11 | <tomsmeding> | Milan_Vanca: no, MR is enabled so x will NOT be recomputed |
| 2026-04-09 11:55:13 | <tomsmeding> | please read |
| 2026-04-09 11:55:14 | <mauke> | Milan_Vanca: no, MR prevents recomputation of x |
| 2026-04-09 11:55:40 | <tomsmeding> | yep, in that example x will be computed once, and it's clearer from the source that that is os |
| 2026-04-09 11:55:42 | <tomsmeding> | *so |
| 2026-04-09 11:56:28 | <tomsmeding> | in general, whether a value is recomputed or not in haskell depends on the type of things and whether things are in the same scope, not how much code is in between the references |
| 2026-04-09 11:56:34 | <tomsmeding> | there's no "limited-time caching" going on |
| 2026-04-09 11:57:11 | <tomsmeding> | there could be, if you interpret the semantics loosely, but that would result in extremely unpredictable performance and memory use and hence GHC does not do that |
| 2026-04-09 11:57:29 | <tomsmeding> | (there's enough unpredictability as it is) |
| 2026-04-09 11:59:13 | <Milan_Vanca> | So basically If instead of X I allocated gigantic bytestring.. it would be ketp in memory for whole time program is running? |
| 2026-04-09 11:59:34 | <tomsmeding> | until you don't have any references to it any more |
| 2026-04-09 11:59:44 | <mauke> | yes |
| 2026-04-09 11:59:46 | <tomsmeding> | because once a value has no references to it any more, the garbage collector (GC) cleans it up |
| 2026-04-09 12:00:44 | <Milan_Vanca> | I thought so.. what about this example https://paste.tomsmeding.com/C5RBYVCY |
| 2026-04-09 12:01:00 | <Milan_Vanca> | There is recursion.. and there is only one reference to x |
| 2026-04-09 12:01:09 | <Milan_Vanca> | This must be recomputed multiple times right? |
| 2026-04-09 12:01:18 | <merijn> | No |
| 2026-04-09 12:01:33 | <mauke> | you can test it, btw |
| 2026-04-09 12:01:44 | <merijn> | `x` is not polymorphic and not "function-like" so the MMR doesn't apply |
All times are in UTC.