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)"]
Provalo online!
Per MLton, i programmi SML completi sono espressioni delimitate e terminate da ;
(ad esempio print"Hello";print"World";
) o dichiarazioni con le parole chiave var
e fun
(ad esempio var _=print"Hello"var _=print"World"
) in cui _
è un carattere jolly che può anche essere sostituito da qualsiasi nome di variabile.
La prima opzione è inutile per la programmazione incontaminata perché di ;
per sé è un programma valido (che non fa nulla, ma non sbaglia neanche). Il problema con il secondo approccio è che dichiarazioni simili var _=print"Hello"
possono essere abbreviate in solo var _="Hello"
(o anche var _=print
) perché la dichiarazione con var
funziona fintanto che il lato destro è un'espressione o un valore SML valido (SML è un linguaggio funzionale, quindi le funzioni possono essere usato anche come valori).
A questo punto, ero pronto a dichiarare impossibile la programmazione incontaminata in SML, quando per caso mi sono imbattuto nel pattern matching in val
-declarations. Si scopre che la sintassi per le dichiarazioni non è val <variable_name> = <expression>
ma val <pattern> = <expression>
, dove un modello può essere costituito da nomi di variabili, costanti e costruttori. Come la print
funzione ha tipo string -> unit
, possiamo usare una corrispondenza motivo sul unit
-value ()
per imporre che la funzione di stampa viene effettivamente applicato alla stringa: val()=print"Hey"
. Con questo approccio, la rimozione di uno print
o "Hey"
risulta in un Pattern and expression disagree
errore.
Con questo modo di stampare in modo impeccabile, il passo successivo è quello di scrivere un quine, prima di aggiungere infine un po 'di protezione in più. In precedenza ho usato una semplice tecnica SML Quine (vedi la cronologia delle revisioni ), ma Anders Kaseorg ha sottolineato un approccio diverso che può salvare alcuni byte nel suo caso. Utilizza la String.toString
funzione integrata per gestire l'escaping delle stringhe ed è di forma generale <code>"<data>"
, dove si "<data>"
trova una stringa con escape del code
precedente:
val()=(fn s=>print(s^"\""^String.toString s^"\""))"val()=(fn s=>print(s^\"\\\"\"^String.toString s^\"\\\"\"))"
Questo è un quine funzionante ma non ancora incontaminato. Prima di tutto Anders Kaseorg ha scoperto che MLton accetta una singola virgoletta "
come codice senza produrre errori, il che significa che non possiamo avere il codice che termina con una citazione come sopra. Il modo più breve per evitarlo sarebbe racchiudere tutto val()=
in una parentesi, tuttavia il codice potrebbe essere ridotto a val()=()
. Il secondo modo più breve che ho trovato è quello di utilizzare val()=hd[ ... ]
, ovvero avvolgere tutto in un elenco e restituire il suo primo elemento per rendere felice il controllo del tipo.
Per assicurarsi che nessuna parte della stringa di dati possa essere rimossa senza essere notata, la corrispondenza del modello in val
-declarations torna utile: la lunghezza della stringa finale da stampare (e quindi la lunghezza del programma) dovrebbe essere 195, quindi possiamo scrivere let val t=... val 195=size t in print t end
nel corpo fn
dell'astrazione invece che print(...)
. La rimozione di una parte della stringa comporta una lunghezza inferiore a 189, causando così Bind
un'eccezione.
Rimane ancora un problema: l'intero val 195=size t
controllo potrebbe essere semplicemente abbandonato. Possiamo impedirlo espandendo il controllo in modo che corrisponda a una tupla:, in modo val t=... val(216,u)=(n+size t,t)in print u end
tale che la rimozione del controllo comporti una variabile non associata u
.
Complessivamente, ciò produce la seguente soluzione di 195 byte:
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)"]
Applicare il trucco del golf sull'uso di nomi di variabili dell'operatore come !
, $
e %
invece di n
, t
e u
al fine di risparmiare spazio bianco (vedere questo suggerimento ) porta alla versione finale di 182 byte.
Tutte le altre rimozioni di sottostringa che non sono state esplicitamente indicate nella spiegazione dovrebbero provocare una sintassi o un errore di tipo.
Modifica 1: length(explode t)
è giusto size t
.
Modifica 2: Grazie a Anders Kaseorg per un diverso approccio quine e la segnalazione di una "vulnerabilità".