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 ;
(e.g. print"Hello";print"World";
) or declarations with the var
and 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 unit
-value ()
to enforce that the print function is actually applied to the string: val()=print"Hey"
. With this approach, removing either print
or "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 <code>"<data>"
, where "<data>"
is an escaped string of the code
before:
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 u
.
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 !
, $
and %
instead of n
, t
and 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.
Edit 1: length(explode t)
is just size t
.
Edit 2: Thanks to Anders Kaseorg for a different quine approach and pointing out a "vulnerability".