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)"]
在线尝试!
对于MLton,完整的SML程序要么是用;(例如print"Hello";print"World";)分隔和终止的表达式,要么是使用var和fun关键字(例如var _=print"Hello"var _=print"World")的声明,其中_是通配符,也可以用任何变量名代替。
第一个选项对原始编程没有用,因为;它本身就是一个有效的程序(不执行任何操作,但也不会出错)。第二种方法的问题是,var _=print"Hello"可以将like的声明缩短为just var _="Hello"(甚至even var _=print),因为var只要右侧的声明是有效的SML表达式或值(SML是一种函数式语言,因此可以使用也用作值)。
在这一点上,我准备在SML不可能申报的原始设置,当一个偶然的机会,我偶然发现模式匹配的val-declarations。事实证明,声明的语法不是val <variable_name> = <expression>but val <pattern> = <expression>,其中模式可以由变量名称,常量和构造函数组成。由于print函数具有类型string -> unit,我们可以在unit-value 上使用模式匹配()来强制将print函数实际应用于字符串:val()=print"Hey"。使用这种方法,删除print或会"Hey"导致Pattern and expression disagree-error。
有了这种原始打印方式,下一步就是写一个卷子,最后再添加一些保存保护功能。我以前使用了一种简单的SML quine技术(请参阅修订历史),但是Anders Kaseorg指出了另一种方法,可以节省一些字节。它使用内置String.toString函数来处理字符串转义,并且具有一般形式<code>"<data>",其中before "<data>"是转义的字符串code:
val()=(fn s=>print(s^"\""^String.toString s^"\""))"val()=(fn s=>print(s^\"\\\"\"^String.toString s^\"\\\"\"))"
这是一个有效的方法,但还不是很原始。首先,Anders Kaseorg发现MLton接受单引号"作为代码而不会产生错误,这意味着我们不能使代码以上述引号结尾。防止这种情况的最短方法是将所有内容包装val()=在一对括号中,但是然后可以将代码简化为val()=()。我发现的第二个最短的方法是使用val()=hd[ ... ],也就是说,我们将所有内容包装到列表中并返回其第一个元素以使类型检查器满意。
为确保不会删除任何未删除的数据字符串部分,val-declaration中的模式匹配再次派上用场:要打印的最终字符串的长度(因此程序长度)应等于195,因此我们可以用抽象let val t=... val 195=size t in print t end体fn代替print(...)。删除字符串的一部分会导致长度小于189,从而引发Bind异常。
仍然存在一个问题:整个val 195=size t支票都可以被删除。我们可以通过将支票扩大为与tuple:匹配来防止这种情况val t=... val(216,u)=(n+size t,t)in print u end,例如,删除支票会导致未绑定变量u。
总之,这产生了以下195字节的解决方案:
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)"]
应用使用操作变量名状的高尔夫技巧!,$并%代替n,t并且u为了节省一些空白(见本提示)导致最终的182字节的版本。
在解释中未明确说明的所有其他子字符串删除应导致语法或类型错误。
编辑1: length(explode t)是size t。
编辑2:感谢Anders Kaseorg提供的另一种方法,并指出了“漏洞”。