Logs: liberachat/#haskell
| 2026-04-06 10:34:04 | <ski> | you're not using `acc' |
| 2026-04-06 10:34:31 | <ski> | so you're discarding all `c's but the last one |
| 2026-04-06 10:34:44 | <ski> | you need to use `>>' (or `do'-notation) |
| 2026-04-06 10:34:51 | <ski> | (or `>>=') |
| 2026-04-06 10:35:33 | <ski> | however, i would suggest using `foldr' (or `foldM', perhaps) |
| 2026-04-06 10:36:15 | <ski> | fp` : perhaps i could show an example, to make you understand what happens, above |
| 2026-04-06 10:36:39 | <ski> | putStr' ['a','b','c'] |
| 2026-04-06 10:36:54 | <ski> | = foldl (\acc c -> putChar c) (return ()) ['a','b','c'] |
| 2026-04-06 10:37:08 | <ski> | = foldl (\acc c -> putChar c) (putChar 'a') ['b','c'] |
| 2026-04-06 10:37:15 | <ski> | = foldl (\acc c -> putChar c) (putChar 'b') ['c'] |
| 2026-04-06 10:37:19 | <ski> | = foldl (\acc c -> putChar c) (putChar 'c') [] |
| 2026-04-06 10:37:26 | <ski> | = putChar 'c' |
| 2026-04-06 10:37:45 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 244 seconds) |
| 2026-04-06 10:37:46 | <fp`> | Mm I see |
| 2026-04-06 10:37:53 | <ski> | and then, only at this point, when `foldl' returns, do we actually start to execute the IO-action : putChar 'c' |
| 2026-04-06 10:38:32 | <ski> | so, you need to combine `acc' with the `putChar' call |
| 2026-04-06 10:38:52 | <ski> | otherwise you're just throwing away the old value of the accumulator, each time |
| 2026-04-06 10:40:18 | <fp`> | Why isn't putChar 'a' evaluated? Is this due to some laziness in evaluation? |
| 2026-04-06 10:40:39 | <ski> | it is evaluated (at the end, after `foldl' returns). and after that, it is executed |
| 2026-04-06 10:42:20 | <ski> | (but `IO'-actions (in this case, the `putChar' call has type `IO ()') are of an abstract data type, can't really be printed, so i just left the evaluation trace above at showing the `putChar' call) |
| 2026-04-06 10:42:52 | <probie> | fp`: Because `putChar 'a'` doesn't do anything until it's "evaluated" in the IO monad |
| 2026-04-06 10:43:03 | <ski> | until it's *executed* |
| 2026-04-06 10:43:09 | <probie> | > [putChar 'a', putChar 'b', putChar 'c'] !! 1 |
| 2026-04-06 10:43:10 | <lambdabot> | <IO ()> |
| 2026-04-06 10:43:26 | <probie> | % [putChar 'a', putChar 'b', putChar 'c'] !! 1 |
| 2026-04-06 10:43:26 | <yahb2> | b |
| 2026-04-06 10:43:30 | <ski> | evaluation of `IO'-actions only determine *which* action to (later, if at all) to perform. it doesn't actually perform/execute/do the action |
| 2026-04-06 10:44:18 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-04-06 10:44:43 | <ski> | the only two ways to *execute* (not evaluate) an `IO'-action is to (a) make it the value of `main' (or a part of that value); or (b) enter it into the interactor (like e.g. GHCi, or in this case the yahb2 bot) |
| 2026-04-06 10:48:08 | <fp`> | Mm so the way to make this folding impl of putStr would be to produce a queue of IO actions |
| 2026-04-06 10:48:19 | <ski> | (of course, only if your `IO'-action is a part of a branch of execution that's activated in `main' will it be executed) |
| 2026-04-06 10:48:30 | <ski> | yes, basically |
| 2026-04-06 10:48:53 | <ski> | but that "queue" can just be a single, compound, `IO'-action, that contains all the `putChar' calls, in sequence |
| 2026-04-06 10:49:02 | <ski> | and the `>>' operator is good for this |
| 2026-04-06 10:49:13 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 248 seconds) |
| 2026-04-06 10:49:15 | <ski> | % putChar 'a' >> putChar 'b' |
| 2026-04-06 10:49:15 | <yahb2> | ab |
| 2026-04-06 10:49:20 | <ski> | @type putChar 'a' >> putChar 'b' |
| 2026-04-06 10:49:21 | <lambdabot> | IO () |
| 2026-04-06 10:49:45 | <ski> | that is a single compound action, that performs those two sub-actions, in that order, when it is executed |
| 2026-04-06 10:50:01 | × | TimWolla quits (~timwolla@2a01:4f8:150:6153:beef::6667) (Quit: Bye) |
| 2026-04-06 10:50:40 | <ski> | so, try combining your `putChar c' with the `acc' action (representing everything you're planning to do with all the previous `Char'acters you've seen, so far, in the list), combining them with the `>>' operator |
| 2026-04-06 10:50:51 | <ski> | you could also use `do', if you prefer |
| 2026-04-06 10:50:59 | <ski> | % do putChar 'a'; putChar 'b' |
| 2026-04-06 10:50:59 | <yahb2> | ab |
| 2026-04-06 10:56:48 | <fp`> | Ok yeah I was able to come up with this after perusing the docs of IO, so the lambda should be (\acc c -> acc >> putChar c) |
| 2026-04-06 10:57:40 | <fp`> | And I guess if I wanted to make putStrLn', I'd just initialize the acc to putChar '\n' |
| 2026-04-06 10:58:18 | → | TimWolla joins (~timwolla@2a01:4f8:150:6153:beef::6667) |
| 2026-04-06 10:59:03 | <ski> | yes, that's correct |
| 2026-04-06 10:59:11 | <ski> | now, try to use `foldr' rather than `foldl' |
| 2026-04-06 10:59:26 | <ski> | "I'd just initialize the acc to putChar '\n'" -- no |
| 2026-04-06 10:59:32 | <ski> | that would give you |
| 2026-04-06 10:59:51 | <ski> | putChar '\n' >> putChar 'a' >> putChar 'b' >> putChar 'c' |
| 2026-04-06 10:59:56 | <fp`> | Ah yeah |
| 2026-04-06 11:00:05 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-04-06 11:02:03 | <fp`> | But why foldr? My understanding is that one uses foldr for its laziness, which benefits for infinitely long lists, but surely people don't want to print infinitely long strings |
| 2026-04-06 11:02:31 | × | fun-safe-math quits (~fun-safe-@97.115.234.213) () |
| 2026-04-06 11:02:40 | <ski> | using `foldr', you can do `putStr' in constant space. with `foldl', it's not constant space |
| 2026-04-06 11:03:39 | <ski> | this is because `foldr' can be incremental (depending on the callback you pass it), can return parts of the result, piecemeal, so that you can start executing sub-actions, before you've determined all the sub-actions |
| 2026-04-06 11:04:26 | <ski> | with `foldl', it *always* executes to the end (reaching the end of the list), before providing any result back to its caller. it's "bulky". so you can't start performing `IO'-sub-actions here, until you've seen the whole list |
| 2026-04-06 11:04:42 | → | fun-safe-math joins (~fun-safe-@97.115.234.213) |
| 2026-04-06 11:04:53 | <ski> | let's assume you defined |
| 2026-04-06 11:05:05 | <ski> | putStr' str = foldl (\acc c -> acc >> putChar c) (return ()) str |
| 2026-04-06 11:05:21 | <ski> | then the evaluation trace looks like |
| 2026-04-06 11:05:21 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 272 seconds) |
| 2026-04-06 11:05:27 | <ski> | putStr' ['a','b','c'] |
| 2026-04-06 11:05:44 | <ski> | = foldl (\acc c -> acc >> putChar c) (return ()) ['a','b','c'] |
| 2026-04-06 11:05:54 | <ski> | = foldl (\acc c -> acc >> putChar c) (return () >> putChar 'a') ['b','c'] |
| 2026-04-06 11:06:02 | <ski> | = foldl (\acc c -> acc >> putChar c) (return () >> putChar 'a' >> putChar 'b') ['c'] |
| 2026-04-06 11:06:09 | <ski> | = foldl (\acc c -> acc >> putChar c) (return () >> putChar 'a' >> putChar 'b' >> putChar 'c') [] |
| 2026-04-06 11:06:15 | <ski> | = return () >> putChar 'a' >> putChar 'b' >> putChar 'c' |
| 2026-04-06 11:06:30 | <ski> | and only at this point can we start performing, executing, the `putChar' actions |
| 2026-04-06 11:07:02 | <ski> | so, you can see that the accumulator keeps growing, as `foldl' continues looping through the list, requiring more and more space to be allocated |
| 2026-04-06 11:09:03 | <gentauro> | ski: what's the name of the `>>` operator? https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Prelude.html#v:-62--62- |
| 2026-04-06 11:09:08 | <ski> | "then" |
| 2026-04-06 11:09:12 | <gentauro> | (Y) |
| 2026-04-06 11:09:22 | <ski> | and `>>=' is "bind" |
| 2026-04-06 11:09:53 | <fp`> | But why would foldr be better? Since I'm iterating through the list backward, I can't even start thinking about IO actions until I know what the first thing to print is |
| 2026-04-06 11:10:20 | <ski> | if you define the `foldr' version, i can then explain why it's better here |
| 2026-04-06 11:10:41 | <ski> | (a direct recursion would also be fine) |
| 2026-04-06 11:12:19 | <fp`> | (\c acc -> putChar c >> acc) |
| 2026-04-06 11:13:07 | <fp`> | or foldr (\c acc -> putChar c >> acc) (return ()) "abc" |
| 2026-04-06 11:14:22 | <ski> | ok, so here's what happens |
| 2026-04-06 11:14:43 | <ski> | foldr (\c acc -> putChar c >> acc) (return ()) ['a','b','c'] |
| 2026-04-06 11:14:54 | <ski> | = putChar 'a' >> foldr (\c acc -> putChar c >> acc) (return ()) ['b','c'] |
| 2026-04-06 11:15:53 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-04-06 11:16:20 | <ski> | { at this point, `main' / the interactor sees that the action is an `>>'-action, with first sub-action being putChar 'a' so it performs that part now, and then tries to perform the second one, which triggers the evaluation of it, so that `foldr' continunes one more step. i'll show putChar 'a' still as part of the result value (action), but remember that it has already happened now} |
| 2026-04-06 11:16:34 | <ski> | = putChar 'a' >> putChar 'b' >> foldr (\c acc -> putChar c >> acc) (return ()) [c'] |
| 2026-04-06 11:16:53 | <ski> | { at this point, the putChar 'b' part is executed } |
| 2026-04-06 11:17:04 | <ski> | = putChar 'a' >> putChar 'b' >> putChar 'c' >> foldr (\c acc -> putChar c >> acc) (return ()) [] |
| 2026-04-06 11:17:15 | <ski> | { and now the putChar 'c' happens } |
| 2026-04-06 11:17:24 | <ski> | = putChar 'a' >> putChar 'b' >> putChar 'c' >> return () |
| 2026-04-06 11:17:41 | <ski> | { and now the return () which does nothing } |
| 2026-04-06 11:18:08 | <fp`> | Mm so it's not actually iterating from the right |
| 2026-04-06 11:18:31 | <ski> | do note that, as soon as the putChar 'a' has been executed, we don't need to keep memory around for it (although i showed it still in all the remaining lines above, for clarity), so we can discard that memory, and similarly for the other actions, so therefore we run in constant space |
| 2026-04-06 11:18:37 | <ski> | exactly |
| 2026-04-06 11:20:07 | × | __monty__ quits (~toonn@user/toonn) (Ping timeout: 244 seconds) |
| 2026-04-06 11:20:16 | <ski> | btw, do note that if you defined |
| 2026-04-06 11:20:26 | <ski> | putStr' [ ] = return () |
| 2026-04-06 11:20:44 | <ski> | putStr' (c:cs) = putChar c >> putStr' cs |
All times are in UTC.