Haskell, 306 + 624 = 930 bytes
Program 1: An anonymous function taking a dummy argument and returning a string.
(\b c()->foldr(\a->map pred)b(show()>>c)`mappend`show(map(map fromEnum)$tail(show c):pure b))"İĴİóđđđÝöÝâÝæÝääē××êääē××İēÀħđĮâħēĕóİóòòĮááħááđéêâéêēááĮÀħ""(\b c()->foldr(\a->map pred)b(show()>>c)`mappend`show(map(map fromEnum)$tail(show c):pure b))"
Try it online!
Program 2: q[[40,...]]
at the end is an anonymous function taking a dummy argument and returning a string.
z~z=[[['@','0'..]!!4..]!!z]
q[x,q]_=z=<<x++q++[34,34]++x
q[[40,92,98,32,99,40,41,45,62,102,111,108,100,114,40,92,97,45,62,109,97,112,32,112,114,101,100,41,98,40,115,104,111,119,40,41,62,62,99,41,96,109,97,112,112,101,110,100,96,115,104,111,119,40,109,97,112,40,109,97,112,32,102,114,111,109,69,110,117,109,41,36,116,97,105,108,40,115,104,111,119,32,99,41,58,112,117,114,101,32,98,41,41,34],[304,308,304,243,273,273,273,221,246,221,226,221,230,221,228,228,275,215,215,234,228,228,275,215,215,304,275,192,295,273,302,226,295,275,277,243,304,243,242,242,302,225,225,295,225,225,273,233,234,226,233,234,275,225,225,302,192,295]]
Try it online!
Character set 1 (includes space):
"$()-:>E\`abcdefhilmnoprstuw×ÝáâäæéêñòóöđēĕħĮİĴ
Character set 2 (includes newline):
!'+,.0123456789<=@[]_qxz~
Since only set 1 contains non-ASCII characters, their UTF-8 bytes are also disjoint.
How it works
Program 1 is generally written with lambda expressions, spaces and parentheses, free use of builtin alphanumeric functions, and with the quine data as string literals at the end.
- Program 1's own core code is turned into string literal data simply by surrounding it with quote marks.
- To support this, every backslash is followed by
a
or b
, which form valid escape sequences that roundtrip through show
.
- Another tiny benefit is that
a
, b
and c
are the only lower case letters whose ASCII codes are less than 100, saving a digit in the numerical encoding used by program 2.
- The string literal encoding of program 2's core code is more obfuscated by using non-ASCII Unicode: Every character has 182 added to its code point to ensure there is no overlap with the original characters.
- 182 used to be 128, until I realized I could abuse the fact that 182 is twice the length of the string literal for program 1's code to shorten the decoding. (As a bonus, program 2 can use newlines.)
Program 2 is generally written with top level function equations (except for the final anonymous one), character literals and decimal numbers, list/range syntax and operators, and with the quine data as a list of lists of Int
s at the end.
- Program 1's core code is encoded as a list of its code points, with a final double quote.
- Program 2's core code is encoded as the list of code points of the string literal used in program 1, still shifted upwards by 182.
Walkthrough, program 1
b
and c
are the values of the string literals for program 2 and 1, respectively, given as final arguments to the lambda expression. ()
is a dummy argument solely to satisfy PPCG's rule that the program should define a function.
foldr(\a->map pred)b(show()>>c)
decodes the string b
to the core code of program 2 by applying map pred
to it a number of times equal to the length of show()>>c == c++c
, or 182
.
tail(show c)
converts the string c
to the core code of program 1, with a final double quote appended.
:pure b
combines this in a list with the string b
.
map(map fromEnum)$
converts the strings to lists of code points.
`mappend`show(...)
serializes the resulting list of lists and finally appends it to the core code of program 2.
Walkthrough, program 2
- The toplevel
z~z=[[['@','0'..]!!4..]!!z]
is a function converting code points back to characters (necessary to write since not all the characters in toEnum
are available.)
- Its code point argument is also called
z
. The laziness marker ~
has no effect in this position but avoids a space character.
['@','0'..]
is a backwards stepping list range starting at ASCII code 64, then jumping 16 down each step.
- Applying
!!4
to this gives a \NUL
character.
- Wrapping that in a
[ ..]
range gives a list of all characters, which !!z
indexes.
- The character is finally wrapped in a singleton list. This allows mapping the function
z
over lists using =<<
instead of the unavailable map
and <$>
.
- The toplevel
q[x,q]_=z=<<x++q++[34,34]++x
is a function constructing program 1 from the quine data list.
x
is the data for the core of program 1 (including a final double quote) and the inner q
is the obfuscated data for the core of program 2. _
is another dummy argument solely to make the final anonymous function a function instead of just a string.
x++q++[34,34]++x
concatenates the pieces, including two double quote marks with ASCII code 34.
z=<<
constructs program 1 by mapping z
over the concatenation to convert from code points to characters.
- The final
q[[40,...]]
is an anonymous function combining q
with the quine data.