Built-in verbs

K (depending on generation) has around 20 built-in verbs. A complication is that they typically have multiple meanings, depending on both context and arity. Indeed, a significant part of the effort required to learn to read k is to be able to recognise the overloaded meanings of verbs and adverbs depending on context.

What is a verb? In other languages, much of the functionality that hides behind k’s verbs (and adverbs) tend to be functions residing in various libraries. Indeed, in Python you’d probably need at least half a dozen import statements to match k’s built-in verbs and adverbs. It follows that instead of thinking of k as a language with no libraries, you can think of it as a tiny language providing an extremely compact way of accessing a very complete standard library.

ngn/k has 22 built-in verbs: : + - * % ! & | < > = ~ , ^ # _ $ ? @ . 0: 1:, most of which have both monadic and dyadic versions, and occasionally different meanings depending on the types of arguments. This chapter runs through the majority of the built-in verbs in turn, briefly. Think of it as the annotated ref-card (\+).

K has a strict right-to-left order of evaluation, like all Iversonian languages. If you’re not used to this, it’s sure to bite you sooner or later, but it’s really intended as a simplification, and as such it soon comes naturally. However, behold:

1+5*2-2
1

Had you tapped that out on a calculator you would have gotten a different result:

1+(5*2)-2
9

With k’s strict right-to-left evaluation order and no differences in precedence, you need to place explicit parentheses if you want the multiplication to “come first”. It follows that you really need to consider a snippet of k-code from right to left.

As k’s verbs usually comes in both monadic and dyadic forms, we must also consider the scope of argument binding. To the right it’s usually obvious: a verb’s right argument is the result of everything to its right. To the left, the binding extends as far as possible, whilst staying true to the right-to-left rule. For example, in the expression

4 3 2 5 6 + 3 2 1 2 1 - 8 7 4 1 2
-1 -2 -1 6 5

But first, the promised run-down of the built-ins.

dex, assign :

Dex, dyadic :, also called right, returns its right argument:

"hello":"world"
"world"

If used monadically, it still returns its right argument:

:57
57

If the left side is a variable name, it becomes assignment

a:1 2 3 4    / assignment
a
1 2 3 4

Flip, add +

Monadic + is flip or transpose. It flips the axes of an array:

:a:(1 2 3 4 5;6 7 8 9 10)   / leading : for display purposes
+a
(1 2 3 4 5
 6 7 8 9 10)
(1 6
 2 7
 3 8
 4 9
 5 10)

Flip will “broadcast” atoms, but otherwise the matrix cannot be “ragged”, which incurs a length error:

+(1 2 3 4 5 6;0)     / broadcast atom
+(1 2 3 4 5 6;0 1)   / length error
(1 0
 2 0
 3 0
 4 0
 5 0
 6 0)
'length
 +(1 2 3 4 5 6;0 1)   / length error
 ^

Dyadic + is add. No surprises there:

1 2 3 4 + 7
8 9 10 11

Add is atomic; it will drill down and apply at the atom level in nested structures.

Negate, subtract -

Monadic - negates its argument, and in its dyadic form, it subtracts. Note the ambiguity. Negation binds tight, so that we can specify negative literals:

-1 2 3 4 5 6     / negate only the first element
- 1 2 3 4 5 6    / negate list
1 2 3 -4 5 6     / negative literal
1 2 3 - 4 5 6    / subtraction
-1 2 3 4 5 6
-1 -2 -3 -4 -5 -6
1 2 3 -4 5 6
-3 -3 -3

First, multiply *

Monadic * is first. It returns the first item of the right argument, or the argument itself if it’s an atom:

*1 2 3
*"hello, world"
*("hello";"world")
*`sym
1
"h"
"hello"
`sym

If the argument is a dict, it returns the first of the values (recall that a dict is ordered):

*`a`b`c!1 2 3
1

Dyadic * is normal multiplication:

5438.8*75623.9
411303267.32

Like add, multiply is also atomic.

Square root, divide %

Monadic % is square root, and the dyadic form is division, both atomic:

%2     / √2
10%3   / 10÷3
1.4142135623730951
3.3333333333333335
%(2 7 4 10 9;6 5 9 65 41)
(1.4142135623730951 2.6457513110645907 2.0 3.1622776601683795 3.0
 2.449489742783178 2.23606797749979 3.0 8.06225774829855 6.4031242374328485)

Enum, odometer, keys, etc !

The exclamation mark is hideously overloaded. In its monadic form, it’s either interval, odometer, or keys, depending on the argument’s type. If the argument is a scalar integer, we have interval (or enum, range, index generator etc). It generates the range 0 to n-1:

!10
0 1 2 3 4 5 6 7 8 9

If the argument is a numeric vector, we get odometer:

!3 3 3
(0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2
 0 0 0 1 1 1 2 2 2 0 0 0 1 1 1 2 2 2 0 0 0 1 1 1 2 2 2
 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2)

It basically counts up like a car’s mileage meter, if you look at the columns. The indexes generated by odometer defines the different ways you can pick items from a set with replacement.

If the argument is a dict, we get the keys:

d:`eric`bob`amy`joan!234 67 52 98
!d
`eric`bob`amy`joan

It will also return keys from a namespace, but we won’t cover those.

In the dyadic form we either have the dict constructor we saw in the cell above, or div/mod:

-2!28      / div
2!29       / mod
14
1

That probably feels weird, with the numerator and denominator “back to front”, and using a negative number to distinguish between the two operations.

Where, min, and &

Monadic & is called where and is aptly named: if the argument is a Boolean vector, it gives the indexes of non-zero values of a vector:

&0 0 1 1 0 1 0        / where are the 1s?
2 3 5

If the argument is a non-Boolean integer vector, it returns a repeated instance of the index of each non-zero element:

&0 0 0 0 5 0 0 0 2    / 5 of index 4, 2 of index 8
4 4 4 4 4 8 8

As a dyad, it’s either and or min:

5&7                   / min 
5&1 2 3 4 5 6 7 8 9
5
1 2 3 4 5 5 5 5 5

If the arguments are Boolean, we have Boolean AND:

(2>8)&7<10
1 0 1 1 0 1 0 0 1 1&1 1 0 1 1 0 0 0 1 0
0
1 0 0 1 0 0 0 0 1 0

Reverse, max, or |

Monadic | is reverse:

|1 2 3 4 5                 / simple list
|(1 2 3;(4 5 6;7 8 9))     / nested list
|`sym                      / atom
|`a`b`c!1 2 3              / dict
5 4 3 2 1
((4 5 6;7 8 9)
 1 2 3)
`sym
`c`b`a!3 2 1

Dyadically, it is analogous to dyadic & above. For non-Boolean arguments, it’s max:

5|7                        / max
5|1 2 3 4 5 6 7 8 9
7
5 5 5 5 5 6 7 8 9

and for Boolean arguments, it’s or:

(2>8)|7<10
1 0 1 1 0 1 0 0 1 1|1 1 0 1 1 0 0 0 1 0
1
1 1 1 1 1 1 0 0 1 1

Grade up/down, less than/greater than <>

Dyadically, < and > do what you expect. Monadically, they’re k’s grade up/down. The grades give an index permutation, that, if used for selection, would order the argument either in ascending or descending order:

v:7 5 10 12 10 3 8 18 4 16 0 5 3 6 15 3 11 16 2 17

<v                / grade up
v[<v]             / select on grade up, a.k.a sort in ascending order
10 18 5 12 15 8 1 11 13 0 6 2 4 16 3 14 9 17 19 7
0 2 3 3 3 4 5 5 6 7 8 10 10 11 12 15 16 16 17 18

It works for dicts, too. Grading a dict will give a key permutation vector that, if used for selection, would order the values accordingly:

k:`a`b`c`d`e`f`g`h`i`j`k`l`m`n`o`p`q`r`s`t
v:7 5 10 12 10 3 8 18 4 16 0 5 3 6 15 3 11 16 2 17
d:k!v

<d               / grade up
d[<d]            / select on grade up, a.k.a sort in ascending order
`k`s`f`m`p`i`b`l`n`a`g`c`e`q`d`o`j`r`t`h
0 2 3 3 3 4 5 5 6 7 8 10 10 11 12 15 16 16 17 18

Group, unit mat, equals =

Mondadic = is either the super-useful group (if the argument is a vector), or unitmat – make an identity matrix, if the argument is an integer atom. Group, first, takes a vector and returns a dict, mapping each unique element to their corresponding indexes of occurrence. This can be used to build frequency tables or histograms:

="mississippi"
"misp"!(,0;1 4 7 10;2 3 5 6;8 9)

The other case is making the identity matrix of any size:

=3
=5
(1 0 0
 0 1 0
 0 0 1)
(1 0 0 0 0
 0 1 0 0 0
 0 0 1 0 0
 0 0 0 1 0
 0 0 0 0 1)

Dyadically, it’s the familiar equality operator, which is fully atomic:

5=9 7 5 2 3 5 6 9 8 5 7    / hands up if you're a 5
0 0 1 0 0 1 0 0 0 1 0

Match, not ~

Monadic ~ is Boolean NOT. It flips 0 to 1, and 1 (for some definition of 1) to 0:

~1 0 0 1 1 0 0 0 0 1
0 1 1 0 0 1 1 1 1 0

For emphasis, not just Boolean vectors – it treats anything non-zero as truthy:

~2 0 8 0 0 9 0 8 0 8 6 6 5
0 1 0 1 1 0 1 0 1 0 0 0 0

Dyadically, it’s match – deep equals. True if both shape and content are equal

(1 0 0 1;0 1 0 1)~(1 0 0 1;0 1 0 1)
(1 0 0 1;(0 1;0 1))~(1 0 0 1;0 1 0 1)
1
0

Enlist, concat ,

Monadic , turns its argument into a vector with a depth of +1 of what it was:

,1
,1 2 3
,1
,1 2 3

K’s display makes depth a bit harder to see, but we can show it by counting the elements:

#1 2 3
#,1 2 3
3
1

Dyadically, it’s concat – glue vectors together:

a:1 2 3 4
b:7 8 9 10

a,b
1 2 3 4 7 8 9 10

Note that k, unlike APL, has no concept of stranding. In other words, we can’t combine vectors simply by separating them with spaces:

a:1 2 3 4
b:7 8 9 10

a b            / note: no stranding! this is interpreted as a@b
0N 0N 0N 0N

That last expression is equivalent to a@b, which is the same as a[b].

Fill, without, null ^

Mondadic ^ is null: it returns true if something is null.

^0N
1

It’s most useful when applied to vectors that may have nulls interspersed, to “spot the holes”:

^9 8 3 5 0N 54 98 0N 67 53    / where are the nulls?
0 0 0 0 1 0 0 1 0 0

Dyadically, if the left argument is an atom, we can use it as fill – find nulls, and replace them with this value:

99^9 8 3 5 0N 54 98 0N 67 53  / replace nulls with 99
9 8 3 5 99 54 98 99 67 53

If both arguments are vectors, we have without – multi-set difference:

"mississippi"^"spm"
"iiii"

Count, reshape, take, replicate #

Monadic # is count; it returns the cardinality of the right argument. If the argument is a vector, it returns the number of elements in the vector. If the argument is an atom, it returns 1:

#"hello, world"
#`sym
#42
#,33
12
1
1
1

In the dyadic version, we have several options, depending on the type of the arguments. If the left argument is an integer vector or an integer atom, we have reshape: it takes whatever is to the right, and squeezes that into the shape defined by the left argument, either dropping or repeating data as required [try it]

With a negative left we take from the end, rather than from the beginning:

The right can be nested, too:

If the right argument is a dict, and the left argument is a vector where the elements are the same type as the dict’s keys, AND that type is either symbol or character, it becomes take, essentially picking key-value pairs from the dict, filling with nulls if necessary:

`bob`eric`sam#`eve`frank`rita`bob`sue`sam!32 34 52 56 83 99
`bob`eric`sam!56 0N 99

If the left argument is a function we have filter/replicate. The function is assumed to return an integer vector of the same length as the right argument vector. This means that we can select using a Boolean vector,

{1 0 0 1 1 0 1 0 1}#"hellobob!"
"hloo!"

or, indeed, replicate elements:

{5 0 0 0 5 0 5 0 5}#"hellobob!"
"hhhhhoooooooooo!!!!!"

Drop, cut, floor etc etc etc _

The _ verb is overly overloaded. It can be one of drop, cut, floor, downcase, delete, filter-out. Monadically, it’s either floor or downcase, depending on the type of the argument:

_43.76             / floor
_"Hello, World!"   / downcase
43
"hello, world!"

If we have an integer atom to the left, and a vector to the right, we have drop: it truncates the vector by the specified number of items, either at the front, for a positive number, or at the back, for a negative number:

3_"hello world!"
-3_"hello world!"
"lo world!"
"hello wor"

However, to spice things up a bit, if we have a vector to the left, and an integer to the right, we have delete:

"hello world!"_5   / delete element at position 5
"helloworld!"

But wait! There’s more. An integer vector to the left and a vector to the right, you ask? Now we’re cutting. The cut vector defines the start of partitions if you like. Let’s say we want to break a string on spaces:

s:"a string with several spaces"
(0,&" "=s)_s
(,"a"
 " string"
 " with"
 " several"
 " spaces")

and – finally – with a function to the left we have filter-out – remove elements for which the left function returns true:

{0=2!x}_!20        / filter out all even numbers
1 3 5 7 9 11 13 15 17 19

String, pad, cast, int $

Monadic $ is string. It takes an arbitrary k-expression and turns it into a vector of strings

$(12;"ab";`cd;+)  / a vector containing a number, a string, a symbol and a function
("12"
 (,"a";,"b")
 "cd"
 ,"+")

Dyadically, with an integer atom to the left and a character vector to the right, we get pad. It pads a string with spaces, either at the beginning or the end, depending on the sign of the left argument:

15$"baloo"    / pad end of string with spaces to make up the length 15
-15$"baloo"   / pad beginning of string with spaces to make up the length 15
"baloo          "
"          baloo"

With a magic symbol to the left, we get cast, which lets us convert between various k types:

`c$97 98 99   / convert integers to the corresponding ascii-characters
`i$-1.2 2.3   / floats to int
`$"sym"       / string to sym
`I$"-12"      / string to int
"abc"
-1 2
`sym
-12

Unique, find, roll, deal ?

Monadic ? is unique, removing duplicates from a vector:

?"missisippi"
"misp"

In the dyadic case, if the left argument is a vector, we’re looking at find, which locates the first index of occurrence of the right in the left argument:

9 6 3 6 5 3 5?3 6 5     / where can I find 3, 6 and 5 first?
2 1 4

Should an element on the right not be present in the left, we get nulls:

9 6 3 6 5 3 5?3 2 5     / note: no 2 on the left!
2 0N 4

If the left and right arguments are both integer scalars we get either roll or deal. Roll is the roll of a set of dice; random numbers in the range specified by the right argument:

3?100                   / roll: 3 random integers less than 100
39 29 51

If we roll on a zero range, we get “minus infinity”, or at least a large negative integer:

1?0                     / -inf
,8894729323858522893

If the left argument is negative, we get deal. Deal returns random elements from a specified range, but without replacement, like dealing cards from a deck. If you deal, the numbers you get are guaranteed to be unique:

-20?100                 / deal: 20 unique random numbers less than 100
53 23 0 58 37 42 93 5 61 29 31 25 11 97 3 45 54 76 70 75

Split, join, encode, decode \/

The back and forward slashes, \ and /, are mostly used as adverbs, but with certain argument types they can also function as verbs. With a character vector (or atom) left argument and a character vector to the right, \ becomes the very handy split:

" "\"a string with embedded spaces"
"with"\"a string with embedded spaces"
(,"a"
 "string"
 "with"
 "embedded"
 "spaces")
("a string "
 " embedded spaces")

The forward slash, /, with a character vector or atom left, and a nested character vector to the right does the reverse; the equally handy join:

" "/("hello";"world")
"hello world"

If you pass integers to \ and / you get mixed radix encode/decode (APL’s ⊤⊥). These are real power tools you don’t get in many non-Iversonian languages (Mathematica is one exception). You use encode and decode to convert between different basis systems, with the canonical example working out how many days, hours, minutes and seconds are made up by a bunch of seconds.

For example, how many days, hours, seconds is 10000 seconds?

24 60 60\10000
2 46 40

and, in reverse,

24 60 60/2 46 40
10000

Borrowing the example given in the Mathematica docs, a Roman legion was made of 10 cohorts, a cohort of 6 centuries, a century of 10 contuberniae, and a contubernia of 8 soldiers. Given 16,894 soldiers, how are they organised?

units:`legion`cohort`century`contubernia`soldier
bases:10 10 6 10 8
units!bases\16894
`legion`cohort`century`contubernia`soldier!3 5 1 1 6

A final note on this: in ngn/k, this behaviour is different from APL (and indeed from other ks):

12\1 2 3 4 5 12 5 4 3 2 1
(0 0 0 0 0 1 0 0 0 0 0
 1 2 3 4 5 0 5 4 3 2 1)

In APL we get:

121 2 3 4 5 12 5 4 3 2 1

1 2 3 4 5 0 5 4 3 2 1

To get ngn/k’s behaviour in APL, we’d need to say

12 121 2 3 4 5 12 5 4 3 2 1

0 0 0 0 0 1 0 0 0 0 0
1 2 3 4 5 0 5 4 3 2 1

In other words, ngn/k will extend the left side to sufficiently represent every element on the right. In APL, you decide the shape of the result. oK doesn’t implement encode/decode, but Shakti-k seems to follow the APL approach.

Note that this is essentially just doing modulo:

*(,12)\1 2 3 4 5 12 5 4 3 2 1   / mod 12
1 2 3 4 5 0 5 4 3 2 1

Another thing that encode/decode is handy for is to convert to and from binary, for example

2\54
2/1 1 0 1 1 0
1 1 0 1 1 0
54

Call, index @[ ].

We’ve mentioned elsewhere that one of k’s many strengths is the fact that, syntactically, indexing is equivalent to function application. It’s good to figure this out early.

If you’re familiar with a c-like language, you can rightly expect that m[i] means “get the _i_th element of m”:

:m:3 3#!9    
m[1]         / pick row 1
(0 1 2
 3 4 5
 6 7 8)
3 4 5

In k, this is syntactically equivalent to calling a function with one argument,

m:{x+5}
m[1]
6

and (this being k) the @ form does exactly the same, only with one character fewer keystrokes:

m:{x+5}
m@1
6

We covered indexing and indexing at depth in detail in the chapter on vectors, but here’s a quick recap:

m:3 3#!9
m[1 0 2]   / pick rows 1, 0 and 2
m[1][0]    / pick row 1 - a vector - and from this, pick element 0
m[1;0]     / indexing at depth; can also be used for assignment
m@1 0 2    / equivalent to m[1 0 2]
(m@1)@0    / equivalent to m[1][0]
(3 4 5
 0 1 2
 6 7 8)
3
3
(3 4 5
 0 1 2
 6 7 8)
3

We can also call a function with .:

{x+y}[23;76]  / call a function with two arguments
{x+y}.23 76   / call a function with two arguments, taken as a list
99
99

This, for example, lets us call a function over a list of arguments using the each adverb, ':

{x+y}.'(23 76;23 65;76 54)
99 88 130

The rest

There are a few more, chiefly so-called “special forms”, and some I/O stuff. We’ll cover those separately.