24
\$\begingroup\$

Brachylog is a language that's beginning to rise in prominence in code-golfing recently (and just received a major update with a terser syntax). Like Prolog, it has the advantage that it can often solve a problem (typically via brute force) merely from a sufficiently accurate description of what a problem looks like, a feature that means that on the right sort of challenge, it's often comparable to the top golfing languages (and has been known to beat Jelly from time to time).

What tips do you have for golfing (i.e. writing the shortest possible programs in) Brachylog? This is mostly looking for advice that's specific to Brachylog in particular, rather than advice that's applicable to a wide range of languages. (Tips about golfing in declarative languages in general might potentially be appropriate here, depending on how much application they'll have to languages other than Brachylog, although see also Tips for golfing in Prolog.)

\$\endgroup\$

7 Answers 7

7
\$\begingroup\$

Exploit nested predicates to create new variables

Brachylog has lots of special syntax cases to make its two special variables, ? (input / left parameter) and . (output / right parameter), terser to use. This means that if you don't need to access your predicate's ? and ., but do need to use variables, you can often save bytes via creating a nested predicate to use its ? and ..

As a simple example, consider a program that looks like this:

… A … ∧A … B … B …

This is a pretty common shape for a longer program; after all, there are lots of gaps that could contain anything. Suppose we have no need for ? or . inside the centre three gaps. Then we could rewrite it like this:

… { … & … . … } …

Here, the nested predicate's ? is serving the role of A, and its . is serving the role of B. We can observe that this is a byte shorter than the original code; changing AABB to {?.} has no change in terms of bytes, but this allowed us to simplify ∧? to the abbreviation &.

A related trick is to change

∧. … ?∧

to

~{ … }

(which is one byte shorter), although note that it's nearly always cheaper to get the caller to exchange the arguments instead (unless the predicate is called from at least three different places in the program, which is rare in Brachylog).

\$\endgroup\$
5
\$\begingroup\$

Split up length-2 predicates inside metapredicates

This is best explained by example. To remove the first and last elements of a list, we behead and knife it:

bk

If we wanted to perform this operation on every element of a list, we can use a map operation:

{bk}ᵐ

However, it's a byte shorter to split the predicate into two, and map each part separately:

bᵐkᵐ

The same trick can be used with quite a few metapredicates:

{bk}ᵐ  →  bᵐkᵐ
{bk}ˢ  →  bˢkˢ
{bk}ᶠ  →  bᶠkˢ
~{bk}  →  ~k~b

Note that for some metapredicates, like , there's no general-purpose way to split it into two parts, but it may nonetheless be possible to find a decomposition that works for the specific task you're working on.

\$\endgroup\$
4
\$\begingroup\$

We do have an increment builtin... sometimes!

+₁ is the only reliable way to add 1 to something. However, under many circumstances, < does the job for a byte less--its output must be strictly greater than its input, so in the absence of external complications, its output will be the integer strictly greater than its input with the least absolute value.

Of course, this substitution can and will fall apart should further logic be employed, as well as in the case of non-integers (which are ceiled) and negative integers (which produce 0).

\$\endgroup\$
4
\$\begingroup\$

s isn't longest first, but is

I wanted to title this "(Ab)use all choice orders at your disposal", but I couldn't think of any other cases of note.

Sometimes choice order is pretty important. Sometimes the choice order your program already has is precisely what you need, just as often it's not; in the case of s generating substrings in the order of largest-first prefixes of largest-first suffixes, it's not infrequent that you might wish it was simply largest first instead. Fortunately, generates its sublists longest first, where every possible substring is included--there's just some other stuff you also don't want. However, s's choice order is no problem at all if you're using it to check substring existence, in a program structured something like ⊇. [...] &s.

\$\endgroup\$
3
\$\begingroup\$

Casting the empty list to the empty string

Sometimes, when working with strings, the algorithm we use might unify what we want with the empty list [], when we would rather want the empty string "".

We can cast the empty list to the empty string using ,Ẹ, which appends the empty string to its left variable (this is an exploit of the way , is implemented).

This also has the benefit that it does not do anything if the left variable is a string. So, if your program is

{  
   some predicate that should always output a string, 
   but actually outputs [] instead of "" in specific cases
}

Then

{
  some predicate that should always output a string, 
  but actually outputs [] instead of "" in specific cases
},Ẹ

will work the way you want.

\$\endgroup\$
3
\$\begingroup\$

Single-element runs in a list

Consider this snippet:

ḅ∋≠

If the input is a list or string, the output is unified with a sublist/substring of length 1 that's not part of a longer run of equal elements. It splits the list into blocks of equal elements and finds a block whose elements are all different. To get the elements themselves instead of singleton lists, tack h to the end. I used this construct here with o to find a character that occurs only once in the input string.

\$\endgroup\$
0
3
\$\begingroup\$

Failing on empty lists

Empty lists can come up often, and sometimes you need to fail when you see one. The obvious means to do so may be ¬Ė, or even l>0, but there exist quite a few one-byte alternatives:

  • b
  • k
  • h
  • t
  • z

just off the top of my head. What inspired me to write this out is actually the discovery of a less obvious failure on an empty list: the metapredicate , which one might expect to be vacuously successful on an empty input, fails instead. Metapredicates which produce failure on an empty list:

  • (any list of length other than 2)
  • ʰ

The general idea to take away from this is that a lot of Brachylog's builtins that seem like they're just to produce an output from an input don't necessarily have an output for every conceivable input, which is more to our advantage than it is detrimental, and is (...usually) easy to think about once you're aware of it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.