Tips for golfing in Husk

15

3

Husk is a quite new golfing language, created by PPCG users Leo and Zgarb. It has begun being more and more competitive, often staying close or even beating languages known to be very terse, such as Jelly and 05AB1E.

Let's list some of the golfing techniques that are somewhat specific to Husk. As always, please post one tip per answer.

Mr. Xcoder

Posted 2018-01-06T13:31:44.433

Reputation: 39 774

5I wouldn't say it is new... – totallyhuman – 2018-01-06T16:09:46.907

1

@totallyhuman first Husk answer Still not that new

– H.PWiz – 2018-01-06T16:17:30.143

Answers

10

Use the return value from predicates

In Husk, functions that test their inputs for some property will usually return a meaningful result in truthy cases, as any positive integer is truthy.

Examples:

≠  Numbers: Absolute difference
   Chars:   Absolute difference of code points
   Lists:   First Index where the differ

Comparisons <, >, ≤, ≥:

For strict comparisons:
Numbers,Chars:  max 0 (the appropriate difference¹)
Lists: The first index where the comparison between the two lists is true

For non-strict comparisons:
Numbers,Chars: max 0 (the appropriate difference + 1)
Lists: Either the result of the strict comparison or, if they are equal,
       the length of the list + 1

ṗ  Index into the list of prime numbers

V  The index of the first element for which the condition is true

€  The first index of that element/substring in the list

£  Works like €

&  Given two arguments of the same type will return the second argument if false,
   otherwise will return the first argument

|  Given two arguments of the same type will return the second argument if true,
   otherwise will return the first argument

¦  Return the quotient if divisibility holds

Λ,E,Ë  Will all return length+1 in truthy cases

Char predicates:
□,±,√,D,½  will each return the codepoint of its argument on truthy cases

¹ appropriate difference means difference of code points for chars. It also refers to the argument order. i.e For <x y, would be x-y

H.PWiz

Posted 2018-01-06T13:31:44.433

Reputation: 10 962

7

Use overflowing line labels

As you may already know, [₀-₉]+|[₀-₉] is regex for the syntax to call a different line than the one you're currently in.

This tip is especially useful if you want a function defined on a specific line to be called as an argument of more than one of the functions in the table below, or as an argument to one or more of the functions below and by itself.

Function table:

+----------+----------+
|Index     |Function  |
+----------+----------+
|1         |´ (argdup)|
+----------+----------+
|2         |` (flip)  |
+----------+----------+
|3         |m (map)   |
+----------+----------+
|4         |z (zip)   |
+----------+----------+
|5         |S (hook)  |
+----------+----------+

Lines in your code are labeled with their respective 0-based indices, from top to bottom. If M < N, where M is the label and N is the number of lines in your code, the label just represents the function defined at line M. If N ≤ M < N * 6, it represents the function from the table above at index ⌊M ÷ N⌋ with the function defined at line M mod N as its first argument. If N * 6 ≤ M, an index error is raised.

Erik the Outgolfer

Posted 2018-01-06T13:31:44.433

Reputation: 38 134

5

Lambdas can be shorter than new functions

As you probably know if you have a multi-line program, you can refer to the lines with the subscripts ₀…₉, for example in the case of

f
g

will refer to the function g. Now if you always apply the inputs to the function g (and use it multiple times); something like this:

f₁⁰[...]g₁⁰[...]
h

You should introduce a lambda because it saves you 1 byte for each additional use:

λf⁰[...]g⁰[...])h

The reverse might be true as well

In the case of self-referential lambdas (φχψ), there's the special case where you apply the inputs directly to the recursive function, in these cases you're better off by using the subscript instead of defining a new lambda and using .

ბიმო

Posted 2018-01-06T13:31:44.433

Reputation: 15 345

5

Uses of Γ

The main use of the built-in Γ, known as pattern matching on lists or list deconstruction, is to split a list into a head and tail, and apply a binary function on them. This corresponds to the Haskell pattern matching idiom

f (x : xs) = <something>
f [] = <something else>

where <something> is an expression containing x, xs and possibly f. There are 4 overloadings of Γ, each of which works a little differently.

list

The first overloading, list, takes a value a and a binary function f. It returns a new function that takes a list, returns a if it's empty, and calls f on the head and tail if it's nonempty. For example, Γ_1€ takes a list, returns -1 if it's empty, and the index of first occurrence of the first element in the tail if not.

listN

The second overloading, listN, is similar to list, except that a is omitted and the default value of the return type is used instead. For example, Γ€ is equivalent to Γ0€, since the default numeric value is 0.

In practice, listN is used more often than list, since the default value is either irrelevant or exactly what you need. A common pattern is Γ~αβγ, where αβγ are three functions; this applies β to the first element and γ to the tail, and combines the results with α. It was used e.g. in this answer. Other patterns include Γo:α for applying α only to the first element, and Γ·:mα for applying α to all elements except the first. The latter was used in this answer.

listF

The third overloading is a bit more involved. Like list, it takes a value a and a function f, and returns a new function g that takes a list. However, this time f takes an extra function argument, which is g itself, and can call it on any value (including, but not limited to, the tail of the input list). This means that listF implements a general recursion scheme on lists. listF is not used very often, since explicit recursion with list/listN is usually of the same length or shorter, as in this answer.

listNF

listNF is to listF what listN is to list: the input a is omitted, and the default value of the return type is used instead. In rare circumstances, it can be shorter than a right fold, for example in this answer.

As an example of the recursive versions of Γ, the function Γλ·:o⁰↔ shuffles a list in the order first, last, second, second-to-last, third, third-to-last, and so on. Try it online! The function f is the explicit lambda λ·:o⁰↔, whose argument is the entire function. What f does is reverse the tail with , then call the main function recursively with o⁰, and finally tack the head back with ·:. Of course, Γ·:o₀↔ is a byte shorter, but doesn't work if the line contains something else than this function.

Zgarb

Posted 2018-01-06T13:31:44.433

Reputation: 39 083

3

Combinators can be applied to higher order functions

Suppose you have a list of integers X, and you want to count the total number of elements of X that are larger than length(X). Counting elements that satisfy a predicate is done with the higher order function #, but here the predicate (being larger than length(X)) depends on X. The solution is to apply the combinator to # and the function o>L that checks whether a list is shorter than a number. In the function Ṡ#o>L, the list X is passed to o>L, the partially applied function is passed to #, and X is given to # as the second argument.

In general, if α is a higher-order function, β a binary function and γ a unary function, Ṡαβ is equivalent to the Haskell pseudocode

\x -> α (\y -> β x y) x

§αβγ is equivalent to

\x -> α (\y -> β x y) (γ x)

and ~αβγ is equivalent to

\x y -> α (\z -> β x z) (γ y)

as long as the types match.

As another concrete example, §►δṁ≠P finds a permutation of a list X that maximizes the sum of absolute differences to corresponding values of X (δṁ≠ zips two lists using absolute difference and takes the sum).

Zgarb

Posted 2018-01-06T13:31:44.433

Reputation: 39 083

3

Husk's default values

Husk is not as strict as Haskell where you run into trouble when for example you try to get the last element of an empty list. To achieve this it uses predefined values, here's a list of the default values, maxima and minima:

.------------------------------------.---------------.----------.-------.
|   Type (X and Y are placeholders)  | default (def) |    max   |  min  |
|------------------------------------|---------------|----------|-------|
|       Character (C)                |      ' '      | \1114111 | \NUL  |
|       Numbers   (N)                |       0       |   Inf    | -Inf  |
|       List of X (LX)               |      []       |  ∞ max   |   []  | *
|       Function :: X -> Y           | const (def Y) |   n/a    |  n/a  |
'------------------------------------'---------------'----------'-------'

* Here ∞ should represent an infinite list of the corresponding maximum (see below for an example)

Note: For tuples (X,Y) it will use the values for each component separately.


When they are used

While the maxima and minima are only used for ▲▼ on empty lists (for example husk -u "▼" "[]:LLN" will return an infinite list of Inf) the default values are used in a few places:

  • folding over empty lists without providing a value yourself (F and )
  • prepending default value (with Θ)
  • when read (r) fails
  • getting the first/last element (←→) or indexing into one (!)
  • pattern matching (Γ) on empty lists
  • using or on empty lists

ბიმო

Posted 2018-01-06T13:31:44.433

Reputation: 15 345