Effect Systems vs. Print Debugging: A Pragmatic Solution

(blog.flix.dev)

54 points | by degurechaff 3 days ago ago

20 comments

  • vlovich123 an hour ago ago

    > Hence, when the compiler is run in production mode, we disable the lie that allows the implicit Debug effect. As a result, using dprintln in production mode causes a compilation error.

    The team can’t seem to make up its minds of the language is intended for high performance or not. They talk about the importance of purity for automatic optimizations but in the real world there’s all sorts of practical reasons for needing to debug production compiled code (eg imagine something like a browser and you’re trying to figure out some weird behavior that’s difficult to catch in a debugger but too slow to reproduce in a debug build or even not reproducible due to different timings resulting in different race conditions)

    Also blaming the users of your language for your language not being able to meet their needs isn’t a good look. It suggests the language is probably attracting the wrong users or positioning itself incorrectly in the market place.

    • benschulz an hour ago ago

      This seems like an uncharitable reading of the post.

      > They talk about the importance of purity for automatic optimizations but in the real world there’s all sorts of practical reasons for needing to debug production compiled code

      I imagine they're talking about their defaults. One can commonly reconfigure how different build profiles work.

      > Also blaming the users of your language for your language not being able to meet their needs isn’t a good look.

      Isn't that what the whole post is about though? They even say the following.

      > Returning to earth: we may be academics, but we are trying to build a real programming language. That means listening to our users and that means we have to support print debugging. The question is how?

    • jorkadeen an hour ago ago

      We think that functional programmers should be able to write e.g. `List.count(x -> x > 5, l)` (or e.g. use pipelines with |>) and have it run as fast as an ordinary imperative loop with a mutable variable. The Flix compiler gives them that-- but it requires the program to undergo certain transformations that may require expressions to be moved around and eliminated. It is dangerous to perform such optimizations with incorrect assumptions about types or effects. How to support that together with print-debugging is the challenge.

      For systems in production, we have the `Logger` effect and associated handlers.

      • vlovich123 an hour ago ago

        And yet modern optimizers don’t actually seem to have a problem with a transformation like that as you must know. Try list.iter().filter(|x| x>5).count() in Rust

        And yes, Rust doesn’t have an effect system yet, but others have mentioned Haskell and how it handles tracing and logging and the limitations of effect systems interplaying with such things.

        • jorkadeen an hour ago ago

          It was a simple example; whether a specific optimization applies is very tricky. We have to look at the details. When can Rust move or eliminate a binder? Does Rust support automatic parallelization? What happens if you use unsafe blocks to lie to their type and ownership system? I think many of the same issues will surface.

          To be me, the interesting question is: What happens when you lie to the type (and effect or ownership) system?

  • svieira 3 hours ago ago

    I wonder if this will wind up being a category of problems and the solution is a separate "system" set of effects (effectively `({user}, {system})`) or if this one-off extension is all that will be needed.

    Either way, extremely well explained both in motivate and implementation!

    • vilunov 2 hours ago ago

      > I wonder if this will wind up being a category of problems and the solution is a separate "system" set of effects (effectively `({user}, {system})`) or if this one-off extension is all that will be needed.

      It already is, kinda. In my practice very often you have global singleton values that are either defined as static variables, or passed as arguments to nearly all functions in a module. Since implicit presence of `Debug` effect is already a compilation parameter, it could be generalized to support any sets of implicit effects. Thus you might design a module that has implicit Logger and Database effects in all its functions.

      • yccs27 an hour ago ago

        > Thus you might design a module that has implicit Logger and Database effects in all its functions.

        Logging does seem like a very similar case to debugging, only that you expect to leave it on in production. On the other hand an implicit Database effect kind of defeat the point of an effect system.

        I think the key is that Debug and Logger effects don't really affect the rest of the code - if you remove all debug/log statements, the only thing that changes is the debug/log output (and slightly faster execution probably).

      • epolanski 2 hours ago ago

        Singleton pattern isn't really used in functional-effect systems, dependency injection is generally used instead.

    • epolanski 3 hours ago ago

      I think the solution is to not have side effects in pure functions really.

      At some point you're composing your pure functions and _have_ to call them by some effectful function (otherwise they're never executed or you're doing computations that aren't consumed by anything).

      The only sane alternative is to have a debug effectful variant where you turn off these checks. But then why would `stdout` debugging fine, and not say writing to a different stream or file?

  • noreplydev 2 hours ago ago

    As I understand, Flix implements a system to type not only a return type but an Effect too. If that's the case, It's not an overkill solution? and why?

  • ducdetronquito an hour ago ago

    Pleasant and interesting read !

  • podgorniy an hour ago ago

    Cool idea to "type" side effects

    I'm curious what other languages are trying to achieve such?

  • throwawaymaths 3 hours ago ago

    Honestly a pragmatic solution would be to have printing to stderr (debug print, logs, whatever) not be an "effect". then as an added benefit if its context is elided out in an optimization you know.

    • maleldil 2 hours ago ago

      This is similar to what Haskell has with trace[1]. It pretends to be a pure function (so it doesn't need the IO monad), but prints to stderr.

      [1] https://hackage.haskell.org/package/base-4.21.0.0/docs/Debug...

    • jorkadeen 2 hours ago ago

      The challenge is that the compiler uses the type-and-effect system for many of its tasks, including whole-program optimization and code generation.

      If printing- or logging statements have no effect, the compiler might reorder them or even remove them.