val()=hd[(fn s=>let val$ =s^"\""^String.toString s^"\"]"val(189,%)=(size$,$)in print%end)"val()=hd[(fn s=>let val$ =s^\"\\\"\"^String.toString s^\"\\\")\"val(189,%)=(size$,$)in print%end)"]
Try it online!
For MLton, full SML programs are either expressions delimited and terminated by
print"Hello";print"World";) or declarations with the
fun keywords (e.g.
var _=print"Hello"var _=print"World") where
_ is a wild card which could also be replaced by any variable name.
The first option is useless for pristine programming because
; on its own is a valid program (which does nothing, but doesn't error either). The problem with the second approach is that declarations like
var _=print"Hello" can be shortened to just
var _="Hello" (or even
var _=print) because the declaration with
var works as long as the right-hand side is a valid SML expression or value (SML is a functional language, so functions can be used as values too).
At this point, I was ready to declare pristine programming in SML impossible, when by chance I stumbled upon pattern matching in
val-declarations. It turns out that the syntax for declarations is not
val <variable_name> = <expression> but
val <pattern> = <expression>, where a pattern can consist of variable names, constants and constructors. As the
print function has type
string -> unit, we can use a pattern match on the
() to enforce that the print function is actually applied to the string:
val()=print"Hey". With this approach, removing either
"Hey" results in a
Pattern and expression disagree-error.
With this way of pristine printing at hand, the next step is to write a quine, before finally some more save-guarding needs to be added. I previously used an easy SML quine technique (see the revision history), but Anders Kaseorg pointed out a different approach which can save some bytes in his case. It uses the built-in
String.toString function to handle string escaping and is of the general form
"<data>" is an escaped string of the
val()=(fn s=>print(s^"\""^String.toString s^"\""))"val()=(fn s=>print(s^\"\\\"\"^String.toString s^\"\\\"\"))"
This is a working quine but not yet pristine. First of all Anders Kaseorg found out that MLton accepts a single quote
" as code without producing errors, which means we cannot have code ending in a quote as above. The shortest way to prevent this would be to wrap everything after
val()= in a pair of parenthesis, however then the code could be reduced to
val()=(). The second shortest way I found is to use
val()=hd[ ... ], that is we wrap everything into a list and return its first element to make the type checker happy.
To make sure that no part of the data string can be removed without being noticed, the pattern-matching in
val-declarations comes in handy again: The length of the final string to be printed (and thus the program length) should equal 195, so we can write
let val t=... val 195=size t in print t end in the body of the
fn abstraction instead of
print(...). Removing a part of the string results in a length less than 189, thus causing a
Bind exception to be thrown.
There is still an issue left: the whole
val 195=size t check could simply be dropped. We can prevent this by expanding the check to match on a tuple:
val t=... val(216,u)=(n+size t,t)in print u end, such that removing the check results in an unbound variable
Altogether, this yields the following 195 byte solution:
val()=hd[(fn s=>let val t=s^"\""^String.toString s^"\")"val(195,u)=(size t,t)in print u end)"val()=hd[(fn s=>let val t=s^\"\\\"\"^String.toString s^\"\\\")\"val(195,u)=(size t,t)in print u end)"]
Applying the golfing trick of using operator variable names like
% instead of
u in order to save some white space (see this tip) leads to the final 182 byte version.
All other substring-removals which where not explicitly stated in the explanation should result in a syntax or type error.
length(explode t) is just
Edit 2: Thanks to Anders Kaseorg for a different quine approach and pointing out a "vulnerability".