4 ms·
Like with functional programming, which at extremes is only declarative, there is a tendency to call “declarative” only those approaches that are perfectly dec
by strogonoff 3y ago
Like with functional programming, which at extremes is only declarative, there is a tendency to call “declarative” only those approaches that are perfectly declarative and incapable of imperativeness. However, not unlike purely functional programming, declarativeness is useless unless it is contaminated with real world at some boundary.
If you pretend that COPY means with these files like those files on host machine as of execution time, RUN means with this command executed at build time, etc., then even Dockerfile becomes fairly declarative. Every declaration defines a new immutable layer with its own unique hash, it’s just that some declarations can easily be used in ways that make the outcome vary based on the state of the entire world as of build time.
Just as in Ansible you can use “declarative” YAML in a very imperative lasagna of a setup, you can do the same with a Dockerfile, Nix, Haskell, or Python. You can also get pretty close to purely declarative bliss with any of them, but it will grow impractical before that point.
- tyingq 3y agoI feel like "not declarative" is fair enough if you look at how the important bits work in a Dockerfile. Like what software is installed, that's not usually some structured thing...but typically shell commands out to apk or apt-get. I get why it is the way it is, but if it were more declarative, it would be easier to manage Dockerfiles through changes, security updates, etc.
- strogonoff 3y agoYou can shell out in Nix and Haskell. If they are not “declarative” then what is [both declarative and useful, a.k.a. capable of interfacing with outside world]? Dockerfile is intentionally bare-bones. It just gives you RUN straight up, no scary hidden option, but it’s up to you how to use it. If you want to write imperative, you can. If you don’t write imperative, pin everything to a hash, shell out only to Dhall and Prolog, it can get very declarative… …at a cost. The fact that people do not tend to go this route means equally that 1) they are lazy, and 2) using RUN this way is pragmatic.
- tyingq 3y agoThat's what I meant by "I get why it is the way it is". I'm not suggesting it change. I'm saying that calling it "not declarative" seems fair, based on the way that most people actually use it.
- strogonoff 3y agoAnd I’m saying that it is not fair, for the same reason (as well as for another reason, which in short is “nothing can be both declarative and useful by that logic”).
- globular-toast 3y agoYeah, that's why I would consider a Makefile declarative even though it can contain bits saying how to do something. I can't consider a Dockerfile declarative simply because it is necessarily executed top to bottom.
- strogonoff 3y agoAn array can be declarative even if it has order. Think of the ordered list of statements in a Dockerfile as syntax sugar for a linked list of (previousLayerPointer, nextLayerDeclaration) tuples. I expanded on this in another comment.
- shawabawa3 3y agoFair enough that everything is a scale, and you _can_ write dockerfiles in a more or less declarative way But the syntax itself is conducive to imperative image definitions, and they are by far the most common
- rgoulter 3y ago> Every declaration defines a new immutable layer with its own unique hash... This blurs the meaning of "declarative" to the point of meaningless. Consider the following pseudocode: x = 5 print x x = 10 print x This is clearly 'imperative'. But from the perspective of 'each line of code declares a new program', then we consider the code snippet a declaration of a program. > Just as in Ansible you can use “declarative” YAML in a very imperative lasagna of a setup One of the things which makes Dockerfiles imperative is the sequence of Dockerfile commands is significant; if you swap the order of a COPY, RUN to a RUN, COPY, the result changes significantly.
- strogonoff 3y ago> This blurs the meaning of "declarative" to the point of meaningless The alternative is to draw a clear line where there is none. > One of the things which makes Dockerfiles imperative is the sequence of Dockerfile commands is significant; if you swap the order of a COPY, RUN to a RUN, COPY, the result changes significantly. By that logic you can call every Nix program “imperative”. They are sequences of strings, the order of which matters. The horror! A Docker image is a stack of layers. Every layer points to a preceding layer. It’s not a huge leap from that structure to a fairly elegant sugar of a string array, where each string is a declaration that applies to preceding layer thus describing the next one. I don’t see anything fundamentally imperative about it; it can be used in imperative ways, but anything can.
- rgoulter 3y ago> The alternative is to draw a clear line where there is none. I'd say that more/less "imperative" refers to describing sequences of actions that take place (especially those which might modify some state), whereas "declarative" is more about a structure of what the result should be. I can agree that a Docker image can be considered as a structure, and that it's possible to construct one declaratively. Whereas I'd say a Dockerfile is an imperative construction of that.
- strogonoff 3y ago
- lolinder 3y agoThe thing is, some of the first advice you'll get about Docker is that the order matters a ton. These two Dockerfiles technically create the same result, but the difference between the two is very important: COPY . . RUN npm install And: COPY package.* . RUN npm install COPY . . The first file reruns npm install every single time any file changes in the code. The second only reruns npm install if the packages change. That can make the difference between a 5-minute build and a 5-second build, so it's not a small optimization. Given how important the order of instructions is, it's hard for me to think of it as declarative. It fits better in my mind as a sequence of instructions, which is the very definition of imperative.
- strogonoff 3y agoCorrect. I find Docker an interesting example of a fundamentally elegant approach that, possibly to facilitate commercialisation, was documented in a way that, instead of imposing any particular culture (such as, say, Nix or Haskell), strongly aligned with the preexisting culture of operations and system administration—the resulting ease of adoption and popularity (and, I assume, broadly deserved financial well-being of the original creators), however, could not come without a variety footguns along the lines of which you have described.