kripke
`type 'a t = Cons of 'a * 'a t | Tail` is defining a type with a constructor `Cons` with two arguments and a constructor `Tail` with zero argument. You write values of type `'a t` as `Cons (a, b)` and `Tail`.
`type 'a u = (::) of 'a * 'a u | []` is defining a type with a constructor `(::)` with two arguments and a constructor `[]` with zero argument. You build values of type `'a u` as `(::) (a, b)` and `[]`.
The rest comes from the special syntax support in OCaml for the `(::)` and `[]` names. Namely, `a :: b` is parsed as `(::) (a, b)`, and `[a; b; ...; z]` is parsed as `a :: b :: ... :: z :: []`, and hence as `(::) (a, (::) (b, (::) (..., (::) (z, []))))`.
With experience, I am more or less able to preemptively add the annotations that will probably help (e.g. aiming avoid accidentally polymorphic functions, the return type of mutually recursive functions, etc.).
I don't think I have ever gotten "This expression is of type X but an expression was expected of type X" outside of the toplevel with type re-definitions, and recent OCaml versions have a nice error message for this now:
Error: This expression has type t/1 but an expression was expected of type
t/2
Hint: The type t has been defined multiple times in this toplevel
session. Some toplevel values still refer to old versions of this
type. Did you try to redefine them?
In general the error messages have been continuously improving in recent years.[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/da...
Unless of course you were suggesting to use labels to implement bigger visual checkboxes, which I gather is what the GP is already referring to with "Currently the only reliable way is to hide these inputs and replace them with images."
It's good to hear that you did do some profiling, even if the results are not public! Of course I would not expect the details in the initial announcement, but I would have expected a mention of this work. At least I have a much higher confidence in the success of this project now than just after reading the announcement.
Switching between the proper implementation of high-level concepts is a staple of untyped (and sometimes typed) runtimes, so in this context the changeset makes more sense than simply "replacing an array-backed implementation with an std::vector-backed implementation" as described, thanks for the explanation!
I'm also not putting any blame on Nix --- the fact that the performance of the evaluator is such an issue is in a way a testament to its success. Unfortunately large, long-lived systems do require active care to enforce proper boundaries between subsystems, and it's a shame if it has got to a point where it's no longer possible to reinstate that separation.
That being said, in perusing related issues on GitHub such as [1], I have found no serious attempts to even profile the damn thing. The typical attempt seems to be throwing a random idea at the evaluator and crossing fingers. The example of a "performance experiment" [2] from the article is self-described as follows:
> Add an alternative impl of the now-abstract Bindings base class that is backed by a std::vector, somewhat similar but stylistically a little superior to the array-backed implementation in upstream nix.
I fail to see how that is a performance experiment when it does not mention performance at all.
The post also mentions issues with the general Nix design and a desire to modernize it, which I am unqualified to comment on, but seems a worthwhile endeavor in and of itself. I do hope that they won't end up writing a new evaluator for the Nix language without a proper understanding of what makes the current one slow and a clear plan not to repeat its mistakes, or the new one risks being just as slow as the old one.
Which, sure, gets the job done faster than the single-thread `odiff` on a single image, but is quite irrelevant for a tool marketed for tests/CI where many images are likely to be compared in parallel already (and maybe a single core is even available).
#include<stdio.h>
#define DX 0.0001
typedef double (*function)(double);
typedef double (*closure_body)(void*, double);
struct deriv_env { function f; };
struct closure { void* env; closure_body body; };
#define CALL(c, x) ((c)->body((c)->env, (x)))
double deriv_body(void *env, double x) {
function f = (deriv_env *)env->f;
return (f(x + DX) - f(x)) / DX;
}
void deriv(function f, struct closure *out) {
out->env = (void *)f;
out->body = deriv_body;
}
double cube(double x) { return x * x * x; }
int main(void) {
struct closure cube_deriv;
deriv(&cube, &cube_deriv);
printf("%f\n", CALL(&cube_deriv, 2.));
printf("%f\n", CALL(&cube_deriv, 3.));
printf("%f\n", CALL(&cube_deriv, 4.));
return 0;
}as an example of DEBUG logging is very wrong. Passwords should not end up in your logs, ever.
1: https://arxiv.org/abs/1611.01578 2: https://arxiv.org/abs/1707.07012 3: https://arxiv.org/abs/1802.03268
Building upon an existing, well-documented, and relatively sane serialization format (protobuf, capn't proto, message pack, json, heck even bencode for all I care) is usually a good thing, and so is decoupling the messages from the details of an implementation's internals. Language and framework internal serializers (such as Python's pickle or, apparently, Qt's serializer) tend to make it harder to achieve both goals.