(~!)(!)((~)~*):((!)~^)*(:^)(~(!)~^(~)~*)(()~(~)~^~*)
Try it online! (includes a testsuite and text identifying parts of the program)
This scores surprisingly well for a very low-level esolang. (Church numerals, Church booleans, etc. are very commonly used in Underload for this reason; the language doesn't have numbers and booleans built in, and this is one of the easier ways to simulate them. That said, it's also common to encode booleans as the Church numerals 0 and 1.)
For anyone who's confused: Underload lets you define reusable functions, but doesn't let you name them in the normal way, they just sort of float around on the argument stack (so if you define five functions and then want to call the first one you defined, you need to write a new function that takes five arguments and calls the fifth of them, then call it with insufficiently many arguments so that it looks for spare arguments to use). Calling them destroys them by default but you can modify the call to make it non-destructive (in simple cases, you just need to add a colon to the call, although the complex cases are more common because you need to make sure that the copies on the stack don't get in your way), so Underload's function support has all the requirements we'd need from the question.
Explanation
true
(~!)
( ) Define function:
~ Swap arguments
! Delete new first argument (original second argument)
This one's fairly straightforward; we get rid of the argument we don't want and the argument we do want just stays there, serving as the return value.
false
(!)
( ) Define function:
! Delete first argument
This one's even more straightforward.
not
((~)~*)
( ) Define function:
~* Modify first argument by pre-composing it with:
(~) Swap arguments
This one's fun: not
doesn't call its argument at all, it just uses a function composition. This is a common trick in Underload, in which you don't inspect your data at all, you just change how it functions by pre- and post-composing things with it. In this case, we modify the function to swap its arguments before running, which clearly negates a Church numeral.
and
:((!)~^)*
( ) Define function:
~^ Execute its first argument with:
(!) false
{and implicitly, our second argument}
* Edit the newly defined function by pre-composing it with:
: {the most recently defined function}, without destroying it
Pytanie pozwala zdefiniować funkcje w kategoriach innych funkcji. Definiujemy „i” dalej, ponieważ im niedawno „nie” zostało zdefiniowane, tym łatwiej jest z niego korzystać. (Nie odejmuje się to od naszego wyniku, ponieważ w ogóle nie nazywamy „nie”, ale oszczędza bajty po ponownym zapisaniu definicji. Jest to jedyny raz, gdy jedna funkcja odnosi się do innej, ponieważ odnosi się do dowolnej funkcji ale ostatnio zdefiniowany kosztowałby zbyt wiele bajtów).
Oto definicja and x y = (not x) false y
. Innymi słowy, jeśli not x
, to wrócimy false
; w przeciwnym razie wracamy y
.
lub
(:^)
( ) Define function:
: Copy the first argument
^ Execute the copy, with arguments
{implicitly, the original first argument}
{and implicitly, our second argument}
@Nitrodon wskazał w komentarzach, które or x y = x x y
są zwykle krótsze niż or x y = x true y
, i okazuje się, że jest to poprawne również w przypadku niedociążenia. Naiwna implementacja tego byłaby (:~^)
, ale możemy oderwać się od dodatkowego bajtu, zauważając, że nie ma znaczenia, czy uruchomimy oryginalny pierwszy argument, czy jego kopię, wynik jest taki sam w obu przypadkach.
Niedociążenie nie obsługuje curry w zwykłym znaczeniu tego słowa, ale takie definicje sprawiają, że wygląda tak! (Sztuczka polega na tym, że niewykorzystane argumenty po prostu się trzymają, więc wywołana funkcja zinterpretuje je jako własne argumenty.)
implikuje
(~(!)~^(~)~*)
( ) Define function:
~ Swap arguments
~^ Execute the new first (original second) argument, with argument:
(!) false
{and implicitly, our second argument}
(~)~* Run "not" on the result
Stosowana tutaj definicja to implies x y = not (y false x)
. Jeśli y jest prawdziwe, upraszcza to not false
, tj true
. Jeśli y jest fałszywe, upraszcza to not x
, dając nam tabelę prawdy, której chcemy.
W tym przypadku używamy not
ponownie, tym razem przepisując kod zamiast odwoływać się do niego. Jest napisany bezpośrednio, jak (~)~*
bez nawiasów wokół, więc jest wywoływany, a nie definiowany.
Xor
(()~(~)~^~*)
( ) Define function:
~ ~^ Execute the first argument, with arguments:
(~) "swap arguments"
() identity function
~* Precompose the second argument with {the result}
Tym razem oceniamy tylko jeden z naszych dwóch argumentów i używamy go do ustalenia, co skomponować na drugim argumencie. Niedociążenie pozwala ci grać szybko i luźno z arity, więc używamy pierwszego argumentu, aby wybierać między dwiema dwuargumentowymi funkcjami z dwoma zwrotami; zamiana argumentów, która zwraca je obie, ale w odwrotnej kolejności, oraz funkcja tożsamości, która zwraca je obie w tej samej kolejności.
Gdy pierwszy argument jest prawdziwy, tworzymy zredagowaną wersję drugiego argumentu, który zamienia swoje argumenty przed uruchomieniem, tj. Wstępnie składa się z „argumentami zamiany”, tj not
. Tak więc prawdziwy pierwszy argument oznacza, że zwracamy not
drugi argument. Z drugiej strony fałszywy pierwszy argument oznacza, że komponujemy z funkcją tożsamości, tzn. Nie robimy nic. Rezultatem jest wdrożenie xor
.