Why do our programs need to read input and write output?
haskellforall.comWhat's the appeal of taking pure functional all the way to an executable tautology? Inputs can be passed around in a pure FP world without reducing purity, so why remove them?
We never got around to output at all. Assuming the compiler can't write the output file before the program runs, how would you see the result of any program run in this world?
What about UI or network input?
I'm imagining that if only the compiler were allowed to do I/O, I'd have to rebuild Chrome whenever I wanted to see a new site, or even see an update to a site, or change my preferences. Wait, scratch that, I can't change preferences, because it can't save. Is the shell allowed to do I/O? What about output to the monitor, does that count? It's not a file, but it's Turing equivalent to an output file.
Inputs in a purely functional world are limited to things that you can serialize and deserialize, which does not include functions or types. If you ingest values via imports instead of via traditional I/O then you can transmit any language feature
You're right that I didn't get to output at all. That's my mistake since I wrote the post in a hurry. The idea is that the interpreter of this language is not a general purpose compiler but is a specialized application with a built-in output. For example, one such interpreter might be a browser that just displays the result to the user.
Using Chrome as an example, the Chrome executable would be an interpreter. Chrome itself would be compiled. For simplicity I'll assume that we browse static HTML pages. Your URL bar would be replaced with a code bar that accepts any arbitrary Dhall expression that builds a DOM. However, since Dhall accepts URLs in place of expressions it is still a URL bar (as long as the URL you input refers to an expression that assembles a DOM). The browser would then interpret that expression to build the DOM and render it as a web page
There are several ways you could configure preferences but I'll throw out a simple idea just to convey the point. Chrome itself could be configured via a Dhall expression that assembles a giant record of user settings. Since a Dhall expression can also be a file you can configure user settings via a path to a file (as long as that file you refer to contains an expression for building that record). That file could itself contain references to other files in order to delegate the configuration of certain options.
That's not the only way you could do it, though. Web pages could be pure functions of user settings, too. CSS would just becomes a special case of treating a DOM as a pure function of style settings.
UI or network input is trickier, not because it's hard to model UI or network input in a purely functional setting but rather because you want to do it in a different way on a case-by-case basis. For example, some types of UIs are unchangeable requirements that you have to adapt to (like a game: the UI is the requirement). However, other forms of UIs or network input are just work-arounds for inability to compose code effectively. For example, batch network input can be replaced by just importing URLs as code. Similarly, batch user input can be replaced by just importing files as code.
All inputs are serializable, including functions and types. We can already transmit language features without imports.
If you compile your inputs into your program, what you're left with is a fancy constant. You might as well let the compiler take the final step of running the program for you and printing the output.
If you think Dhall is great and more people should use it as their I/O library, that's awesome, maybe show some of the cool things it does. Motivating it's usage by trying to make I/O a functional vs imperative issue just seems really contrived, and mostly wrong and prone to argument. I/O isn't a functional issue, and it is one of the main reasons to write a program: to do some work parameterized over different inputs.
Your program can be a pure function which somebody else can invoke, so it's not necessarily a constant
However, Dhall does have the ability to normalize under lambda when possible so it will actually do this for you if it can! For example, if you try to interpret this program, which is a function:
The interpreter will simplify that down to:let replicate = https://ipfs.io/ipfs/QmQ8w5PLcsNz56dMvRtq54vbuPe9cNnCCUXAQp6xLc6Ccx/Prelude/List/replicate in let exclaim = λ(t : Text) → t ++ "!" in λ(x : Text) → replicate +3 Text (exclaim x)
... although it can't do anything else until somebody else comes along later and calls the function on a specific inputλ(x : Text) → [x ++ "!", x ++ "!", x ++ "!"] : List Text
Chrome already ships a compiler today. Why wouldn't it continue to do so in your hypothetical?
Are you referring to the JavaScript compiler? I don't understand your point, would you elaborate? The article is about factoring out all inputs from a program. Chrome's compiler is used to compile new inputs on the fly, it's reason for existence doesn't seem to mesh with the author's goal.
FWIW, I'm trying to imagine the author's hypothetical. If you think I'm imagining something other than what he proposed, I'm all ears!
> I'm imagining that if only the compiler were allowed to do I/O, I'd have to rebuild Chrome whenever I wanted to see a new site, or even see an update to a site, or change my preferences. Wait, scratch that, I can't change preferences, because it can't save.
Chrome IS the compiler. Your preferences are a program provided as input. Websites are programs provided as input. You wouldn't need to rebuild Chrome to change things.
Can you elaborate on what you mean?
How would I save my preferences?
Do all websites have to be compiled as programs first, so they can be input as programs into new Chrome?
If my websites were provided as input and compiled into Chrome, how do I avoid rebuilding Chrome when the websites are updated?
If Chrome is the compiler, then Chrome is allowed to do all the I/O it wants, just like it does right now?
I mean, at some point you're just asking me to implement it for real, but I'll give you some hand-wavy answers:
You save your preferences the same way you might save `jquery.js` on your computer.
Chrome IS the compiler, you don't need to compile them first to give them to Chrome.
I'm not sure what you mean by "compiled into Chrome". Since when you do compile a program "into" the compiler? Look, your browser probably JIT compiled a dozen programs just for you to be able to view this page and comment. Did you need to rebuild Chrome?
Yes, Chrome is doing the IO. Importantly, the page is NOT doing the IO. So, there's no XMLHttpRequest, for example.
To make an analogy to Python: you don't need to recompile Python to interpret new Python programs. Chrome would be like Python: Chrome is itself a compiled program, but it interprets user settings and web pages.
Chrome is allowed to do whatever I/O it wants, but the programs it interprets (like user settings and web pages) are not allowed to do any I/O at all. These interpreted programs are sandboxed.
This isn't really surprising if you think about it. Chrome is already an interpreter for a sandboxed language (JavaScript)
A closer analogy to Dhall would be if web pages were assembled entirely from JavaScript instead of HTML, URLs were just pointers to Javascript expressions, and JavaScript code could refer to other JavaScript code anywhere within the syntax tree just by URL (instead of out-of-band via a <script> directive)
I don't know Dhall, but that sounds like what we already have. URLs are already pointers to JavaScript expressions, and many websites are already JS that constructs HTML... that's the main direction React is going, for example. The script directives are irrelevant, it'd be easy to show equivalence without them. And I don't see what any of this has to do with writing programs that don't read inputs or write outputs.
Right, and where I'm going with this is that we should take the JavaScript model to its natural conclusion and use it more pervasively in other domains. In other words, more applications and domains should be configured via sandboxed and interpreted programs that can easily reference other programs
However, the difference is that I don't think we should use an untyped and Turing-complete language for doing this (which is my primary objection to JavaScript). Also, I think the mechanism for referring to other programs should be more lightweight (i.e. just dump the path and URL into your code and you're done)
I don't understand the article. It's still input and output but in a more convoluted way and therefore better? Reminds me a little of articles you see from time to time like "if-less programming".
Author here: this is written for a primarily functional audience who already take for granted that it's good to minimize effects, but let me try to rephrase it another way for people who don't have that background
Typically there are two ways that our programs can ingest values:
* statically, via imports
* dynamically, via reading values
Why not ingest all values via imported code? If the import system is sufficiently lightweight this is simpler and easier than reading values the traditional way, plus you can read in things that are not plain values (like functions and types)
In that sense, reading/downloading text and parsing it becomes an implementation detail of the compiler and from the programmer's point of view what you're left with is a network of pure functions that can refer to each other across impure boundaries
You seem to be just playing with semantics and claiming it offers a new way of thinking about I/O.
I don't want to think about I/O. That's the point
I want to focus on connecting pure code together without thinking about the details of how that happens. I want this for the same reason that I don't want to think about manually allocating registers, managing memory, or caring about evaluation order
Also, like I mentioned, traditional I/O can't import functions or types, so this is strictly more powerful
"Playing with semantics" is a good description of programming language theory.
"Compiling" throws me off a bit, because I usually think of that as a process that bakes in everything except for I/O. It seems like for Dhall, this is done on-demand, like an interpreter, and so the result of execution could change if the contents of the referenced files change. But I'm not certain how this would extend to ahead-of-time compilation. Would you have to mark file references by whether they are to be read at compile-time or run-time?
So you do dynamic compilation? How is that different from including files in PHP or one bash script calling another? I can even do that in .NET languages like C#.
In Dhall's case it is a typed interpreter. So every time you interpret an expression there are three phases:
* Resolve all imports (transitively, if necessary)
* Type-check the code
* Normalize the code (a.k.a. evaluation, but it can normalize functions, too)
It's a little more complex than that (for example, imported code is itself type-checked before substitution into the syntax tree), but that's the basic idea
It's closer in spirit to a Bash script sourcing another bash script (i.e. using "source"). However, the difference from Bash is that:
* In Bash the unit of code composition is statements (as opposed to expressions in Dhall) * When you source another script you can only do so at the top-level of the program, as a statement (as opposed to Dhall where you can import other expressions anywhere within the syntax tree)
I'm less familiar with PHP so perhaps somebody who knows it better can explain how Dhall relates to the PHP model
Look into .NET. You can do the same thing with C# by compiling and loading it dynamically.
That was my question as well. This doesn't seem substantially different than the dynamic include/require/eval functionality in many interpreted languages like Python, Ruby, Perl, PHP, etc.
The primary distinction from a dynamic language is that Dhall is typed, total, and does not permit arbitrary effects (only importing other code is allowed), so it's safe to evaluate arbitrary remote code and it's also safe to use Dhall expressions to configure programs since they consist of nothing but pure functional logic. Dhall is first and foremost a programmable configuration language so safety is one of the primary language requirements
Sounds a bit like safe tcl, a subset of tcl you could embed into your own program for untrusted scripts.
https://www.tcl.tk/software/plugin/safetcl.html
Minus the type safety, functional approach, etc...but similar in purpose.
Yes, that's a good analogy
However, I think the more important thing I'm trying to fix is how we compose code. The Rube-Goldberg machine the post refers to is the complicated mechanisms we have to deal with for combining code fragments. Reading in a value shouldn't be any different from reading in code and shouldn't require any more overhead than just copy-and-pasting a URL into your program
What I don't get is, is your compiler different from a reflective framework that takes functions that receive the world state and update its interface as a consequence of the functions output?
So, I am missing how this would work. Let me pick 2 examples.
The first is about networking. How would I build, say, an http server in this thing? From the rest of this thread, it seems like you would advocate essentially a separate compiler for networking, and to 'read' from a socket, you just import. That seems like you are moving a lot of complexity to the world of compilers.
Moreover, I don't see any way to then 'write' to that socket. Does the compiler essentially take a return value of main and then say 'well, guess this is what I want to send back'. That would suggest you can't do something like 'recieve handshake' 'send handshake' 'receive request' 'respond to request'. It seems to block interspersing input with output.
My second example is a gui. It feels to me like your program couldn't in any way define the interface layout. All it could do is 'receive' and 'issue' events. It would take a specialized compiler, and a whole separate mark-up language to actually define the interface. How do you change the interface in response to input?
I love pure code, and see the value in pushing the boundary of pure code as close to the edges as possible. However, code still needs side-effects to be useful, and I'd like to specify those side-effects in the same language as I specify my pure code. I'd like to decide for myself how far out to push the boundary of purity.
Yes, this entails moving more logic into compilers/interpreters. For example, Dhall is actually designed this way: not only is it a command line interpreter but it's also a Haskell library that you can use to interpret Dhall expressions and marshal them into Haskell programs. So the Haskell language combined with the Dhall library becomes an "interpreter builder": a way to crank out a large number of specialized applications that are customized via an effect-free programmable configuration language
For the specific example of a server, there would be two layers:
* in Haskell you would implement a server configured by a Dhall expression (i.e. a hybrid server/interpreter)
* end users program in Dhall, not Haskell
* end users provide a record of pure functions (one for each API endpoint) that translate user input to output
However, I think that's still just a superficial answer to the question. The next level is to ask: why do we even need a server? A server is just a way to distribute values, but Dhall already has a way to distribute values (and code): we just import them by reference. So why not just use that directly instead of standing up a server to reimplement what the Dhall interpreter already does internally
You can encode markup in a purely functional languages. That's actually the easy part since you can treat it as an ordinary data structure. Actually, the harder part is coming up with a user-friendly way to transform a stream of events into a stream of outputs within a purely functional language. In my opinion, there is no clear consensus on the "right" way to do this, but the field of functional reactive programming is based on searching for clean solutions to this problem
However, again, you need to step back and ask: what did we need this GUI for? In some cases, the GUI is the requirement (like in a game) so there's not much you can do to simplify things. However, in other cases -the GUI is just a poor man's interface to composing values and code, in which case we might just be able to replace it with importing URLs and paths using Dhall's built-in mechanisms (and perhaps replace all these bespoke GUIs with a general purpose GUI for manipulating Dhall expressions)
You say 'importing urls', but what is going to serve the content at that URL? A server.
I dont know what it means by saying there is no input or output... reading from a URL is an input. It is an I/O operation to pull data from a network stream.
Even the whole writing functions to files things is an input and output situation. Just because you change the words doesn't change what it is doing.
Yes, but it's a restricted set of input and output: the only thing you can do is import other code. You can't, say, launch missiles or delete files unless the compiler/interpreter allows it
> My second example is a gui. It feels to me like your program couldn't in any way define the interface layout. All it could do is 'receive' and 'issue' events. It would take a specialized compiler, and a whole separate mark-up language to actually define the interface. How do you change the interface in response to input?
As one example, the program might be written in model-view-controller style:
* Your "model" is a type `Model`.
* Your "view" is a function of type `Model -> Display`.
* Your "controller" is a function of type `(Model * Event) -> Model`.
The runtime is responsible for feeding your controller events to get a new model, then running your view on the new model to create a display, then actually rendering that display.
Like others i do not understand this article.
It seem to propose an abstraction where we separate the I/O part from the "elaboration" part of a programs, so that our programs are entirely pure. But i don't understand why the import system and why only the compiler can read and write?
Why can't we have the same benefits by using multi-threading/process? we can have "pure" thread and "i/o" thread that communicate with message passing? i feel like I'm missing something from the article.
There are two separate questions here:
* "Why should we limit I/O to the compiler"?
Think of the compiler or interpreter as a small trusted kernel. It's a mostly fixed code base that you can inspect and audit. The programs that it compiles or interpret on another hand don't have to be trusted because they are perfectly sandboxed. They are effect-free purely functional code
* "Why not use multi-threading for communication?"
This is (in my eyes) the real problem that I'm trying to solve. Using effects as a message bus for composing code seems incredibly awkward and primitive, like explicitly managing stack registers.
Perhaps I simply didn't understand the argument, but doesn't this simply move reading and writing to a different abstraction? Perhaps someone can reword the author's point, I feel like I'm missing something here.
He just described batch processing like it was a new thing.
What part of the article makes you think the author believes he's describing something new?
The lack of any acknowledgement that it had been done before.
If you were trying to convince someone your concept is possible, wouldn't you point to an already existing example of it if you could?
Excellent, I've actually been working on something similar and I'm glad to see it validated.
As I expected though, most people in this thread don't get it, which is understandable since it's pretty out there compared to the mainstream. I think they will once larger, "real" programs are written.
I don't think I'm gonna be able to "reword" this for people here so it suddenly clicks, you're probably just going to need to see it in action to understand it.
I don't think I'm gonna be able to "reword" this for people here so it suddenly clicks, you're probably just going to need to see it in action to understand it.
So called "visual" programming languages like LabVIEW and Visual Basic seem to come very close. There's a supervisory program that manages and parses inputs, and schedules the invocation of functions when those inputs change. I'm not sure what those programs do with the outputs of functions is exactly what the author proposes, but you could let the supervisory program route the outputs of functions to their desired effects.
Definition of the expected input format and its interpretation, and likewise for the output format, would presumably be done in some meta-programming language.
My Visual Basic programs contained little or no input/output code, until I started using VB to control industrial machines.
Why don't you describe a "real" program that uses this approach so we can see the benefit?
Giving ~precise and practical example can be suprisingly hard when you are working with some very different kind of thinking for a long time. At the end idea should be used in real world of course but it can take a lot of time to recreate lot of solutions which are resolved in mainstream but "reopened" in your world.
I think this article is quite clearly just reprsenting idea for people working with functional side and maybe thinking something similiar already. So if you "cannot get it" and author cannot give ~easy answer don't judge yourself or the idea/author too much but accept that idea can be still very early phase and work-in-progress.
I just don't understand what's funktional or novel about this. That's why I asked for an example that could illustrate it. The good thing is that the author has no obligation to listen to me so let's see where this ends up.
Yep, that's exactly what I should do. My stuff is not ready to show off yet but I'll be happy to make an HN post once it's ready.
In the meantime, Dhall is much further ahead, so you could check out more of Gabriel's stuff if you need more convincing.
Dismissing inputs for the moment: if you cannot write or transmit outputs, how does your program actually perform anything. If your answer is "side effects", you've merely redefined the term "outputs".
As I see it, you'd have to have a program which was supplied its input, by the compiler, was evaluated by the compilier, and had its output imputed, by the compiler. You've only shifted the problem of input and output sanitisation elsewhere.
The way I would phrase it is that you've concentrated your input and output sanitisation in a trusted kernel (i.e. the compiler/interpreter) and that puts an upper bound on the amount of code that you need to audit (just the compiler/interpreter code base). That's more realistic than auditing all programs written within the compiled/interpreted language
How would this IO model affect, say, Unix piping on the command line?
My favorite part is that injecting an attacker's code would not cause a security problem, because the platform is immune to side effects.
The compiler is perfectly able to create side-effects.
I'm not making the claim. It seems to be in the linked article:
""That's a security vulnerability!", you protest. "You are ... literally ... injecting remote code into your program."
Playing the devil's advocate, I ask you what is wrong with remote code injection
"Well, for starters, if the URL is compromised the attacker can run arbitrary code like ..."
... reading and writing files? Dhall is totally pure and doesn't support any effects at all (besides heating up CPUs ).
This brings us full circle back to our original "
Oh, ok. On the specific case of Dhall, it can not.
However, the compiler/interpreter places an upper bound on the amount of code that we need to audit because it acts like a trusted kernel. We only need to audit the compiler/interpreter itself for safety and once we do so we can automatically trust all programs written in the language that it compiles/interprets
In a general case, the compiler acts as a sandbox. How much harm the malicious code can do depends on the specifics of how much IO it enables.
On the case of Dhall it's entirely safe, if you generalize it into a full OS equivalent system, you'd have to audit everything.
Say the user clicks somewhere in my program’s window. How does my program discover the location of the click without reading some input?
As one example, the program might be written in model-view-controller style:
* Your "model" is a type `Model`.
* Your "view" is a function of type `Model -> Display`.
* Your "controller" is a function of type `(Model * Event) -> Model`.
So you don't need to perform IO to get the mouse click, since your program definition was already setup to handle incoming events. The runtime is responsible for feeding your controller events to get a new model, then running your view on the new model to create a display, then actually rendering that display.
To clarify: the idea is that both the view and controller are built into the interpreter. The interpreted program builds the model and is written in a a purely functional and effect-free language
Isn't that just pushing the side-effects to the runtime? What does it achieve?
It achieves the goal of making your code side-effect free. The explicit goal of these sorts of projects is to push the side-effects to the very edge of the stack.
I want my five minutes back.