To do this you made the Debug effect a special case in the compiler. Won't users want to create their own special effect types? Could these special effects be expressed within the language itself instead of being compiler builtins?
> 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.
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?
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.
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!
The other major use case that leaps to mind is "observability"; I want to be able to poke a metric without the function becoming impure, since it is not uncommon for something deep down the call stack to poke a metric and I don't want it to propagate up the stack. You can also make a case for logging that isn't just debug logging. Propagating up a new effect just because some deep function needs to push a log is not very friendly or useful.
Perhaps there is two or three more, but I do think this is a finite set that we can write a language around and just sort of consider them "ambient effects". While metrics and logging are nominally impure, in that they are certainly mutations, if you can't read the logs or the metrics without an effect, you still retain the really important aspect of purity, which is that the pure code can't cause a change that is observable by that or other code, with the very specific exception of the logging stream and metrics.
I wouldn't be quite ready to put all my chips on this, but I think "the inability to create changes that can be witnessed" is actually the true goal, not "the inability to create changes" with no qualifications. Pure code already necessarily creates changes in a system, the key is that while the CPU registers may change and other parts of the system may mutate, the code can't witness those changes and conditionalize future execution on it. All effects-based systems have already agreed that there are things that are mutations in something real in the physical world they aren't going to consider effects, adding a couple more categories is not going from 0 to 1 but 12 to 15. It's not a strict purity question but a cost/benefits question.
It occurs to me as I type this that a really ambitious language with a strong enough type system might even be able to turn this into a completely safe proposition, allowing users to declare effects with some sort of very safe "sink" associated with them that the type system checks can not escape out into the rest of the code in any visible way and constraining the visibility somewhere that is contained in the conventional effects system. All these things I'm talking about here and in the blog post are taking the form of values that simply disappear into the ether from the point of view of the creating function. I'd like the language to make it easy to query a function for which ambient effects it uses (as a development-time operation, not a run-time one), and I think that would clean up most of the rest of the practical problems.
> 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.
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?
Is there a target use case(s) for this language, at least initially? It seems to focus on programming in a platform-independent sense, but where is it most likely to actually succeed? Are there particular OS’s or other languages that they try be particularly compatible with?
It runs on the JVM, so I suppose that's the "OS" it's compatible with and Java is the language. It's still mostly academic AFAICT, but I could see experimenting with it for backend stuff.
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?
It makes it easier to reason about a program. For an dev, you can immediately understand what global state, if any, can affect a function’s behaviour. And for the compiler, knowing about what effects an operation has can enable optimizations like dead code removal and autovectorization; the article goes into that.
How does Flix decide if it’s worth it to auto parallelize a map operation? Eg, if it’s a very trivial map operation, it may add overhead to parallelize it
AFAIK the first somewhat widely-known language to do this was Haskell (through libraries). I'm not 100% clear on the entire history, but I think it goes something like:
1. Initially there was no way to do effects in Haskell, everything was pure.
2. Then it was realized that IO can be modeled with monads, so the IO type and do notation were added.
3. Gradual realization that monads can be used to also constrain effects, ie you can construct a type of "stateful computations" that can read and write to a specific state, but not touch other states or write to disk or something.
4. Monad transformers are invented, which allow stacking monads on top of eachother to support multiple effects. Together with type classes, this gets us pretty close to extensible effects (the approach used in Flix, if I understand it correctly). So for example you can express that your function needs to write to a log and may exit early with an error message with the constraints `(MonadWriter w m, MonadError e m) => ... -> m resultType`, and you can then use monad transformers to build a stack that provides both of these effects.
5. Monad transformers have some issues though: they affect performance significantly and the interaction between effects is tricky to reason about. So an alternative is sought and found in extensible effects. The initial proposals were, iirc, based on free monads, but those aren't great for performance either, so ever since there has been a whole zoo of different effects and handlers implementations that all make different trade-offs and compromises, of which I think the `effectful` library is now the de facto default, and I think what it offers is quite similar to the Flix language's effect system (I'm not sure on what finer points it differs).
Koka[1] jumps to mind as a language built around effect types. Without having used Flex or Koka, I'm not sure how their effect types actually differ from the normal monad gauntlet you get in pure languages.
That's why I don't like "purity above all costs" languages.
It's not just print debugging. It's also logging. Oh, you want/have to add useful/required logging? Good luck refactoring your entire code to thread IO everywhere.
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.
It needs to be an effect so the compiler knows how optimizations are permitted to handle it. Otherwise your print statements might appear in totally the wrong order or even not at all.
Imagine saying that certain memory stores are allowed to cross a memory barrier operation, you'd completely lose the ability to reason about concurrency around those stores.
matheusmoreira|5 months ago
jorkadeen|5 months ago
Do you have some specific special effects in mind?
vlovich123|5 months ago
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|5 months ago
> 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|5 months ago
For systems in production, we have the `Logger` effect and associated handlers.
svieira|5 months ago
Either way, extremely well explained both in motivate and implementation!
jerf|5 months ago
Perhaps there is two or three more, but I do think this is a finite set that we can write a language around and just sort of consider them "ambient effects". While metrics and logging are nominally impure, in that they are certainly mutations, if you can't read the logs or the metrics without an effect, you still retain the really important aspect of purity, which is that the pure code can't cause a change that is observable by that or other code, with the very specific exception of the logging stream and metrics.
I wouldn't be quite ready to put all my chips on this, but I think "the inability to create changes that can be witnessed" is actually the true goal, not "the inability to create changes" with no qualifications. Pure code already necessarily creates changes in a system, the key is that while the CPU registers may change and other parts of the system may mutate, the code can't witness those changes and conditionalize future execution on it. All effects-based systems have already agreed that there are things that are mutations in something real in the physical world they aren't going to consider effects, adding a couple more categories is not going from 0 to 1 but 12 to 15. It's not a strict purity question but a cost/benefits question.
It occurs to me as I type this that a really ambitious language with a strong enough type system might even be able to turn this into a completely safe proposition, allowing users to declare effects with some sort of very safe "sink" associated with them that the type system checks can not escape out into the rest of the code in any visible way and constraining the visibility somewhere that is contained in the conventional effects system. All these things I'm talking about here and in the blog post are taking the form of values that simply disappear into the ether from the point of view of the creating function. I'd like the language to make it easy to query a function for which ambient effects it uses (as a development-time operation, not a run-time one), and I think that would clean up most of the rest of the practical problems.
vilunov|5 months ago
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.
epolanski|5 months ago
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?
meisel|5 months ago
wk_end|5 months ago
noreplydev|5 months ago
wk_end|5 months ago
meisel|5 months ago
podgorniy|5 months ago
I'm curious what other languages are trying to achieve such?
ookdatnog|5 months ago
1. Initially there was no way to do effects in Haskell, everything was pure.
2. Then it was realized that IO can be modeled with monads, so the IO type and do notation were added.
3. Gradual realization that monads can be used to also constrain effects, ie you can construct a type of "stateful computations" that can read and write to a specific state, but not touch other states or write to disk or something.
4. Monad transformers are invented, which allow stacking monads on top of eachother to support multiple effects. Together with type classes, this gets us pretty close to extensible effects (the approach used in Flix, if I understand it correctly). So for example you can express that your function needs to write to a log and may exit early with an error message with the constraints `(MonadWriter w m, MonadError e m) => ... -> m resultType`, and you can then use monad transformers to build a stack that provides both of these effects.
5. Monad transformers have some issues though: they affect performance significantly and the interaction between effects is tricky to reason about. So an alternative is sought and found in extensible effects. The initial proposals were, iirc, based on free monads, but those aren't great for performance either, so ever since there has been a whole zoo of different effects and handlers implementations that all make different trade-offs and compromises, of which I think the `effectful` library is now the de facto default, and I think what it offers is quite similar to the Flix language's effect system (I'm not sure on what finer points it differs).
jorkadeen|5 months ago
- https://effekt-lang.org/
- https://koka-lang.github.io/
- https://www.unison-lang.org/
- https://antelang.org/
ux266478|5 months ago
[1] - https://koka-lang.github.io/koka/doc/index.html
anentropic|5 months ago
davidatbu|5 months ago
ducdetronquito|5 months ago
troupo|5 months ago
It's not just print debugging. It's also logging. Oh, you want/have to add useful/required logging? Good luck refactoring your entire code to thread IO everywhere.
throwawaymaths|5 months ago
maleldil|5 months ago
[1] https://hackage.haskell.org/package/base-4.21.0.0/docs/Debug...
jorkadeen|5 months ago
If printing- or logging statements have no effect, the compiler might reorder them or even remove them.
naasking|5 months ago
Imagine saying that certain memory stores are allowed to cross a memory barrier operation, you'd completely lose the ability to reason about concurrency around those stores.
bstudios|5 months ago
[deleted]
draw_down|5 months ago
[deleted]