iogiiDocsSourceQuick RefOnline InterpreterContact
IOSyntax ⸱ Types ⸱ VectorizationCircular ProgrammingOpsExamplesWhy

Types

Truthiness

This is used by ops like not, takeWhile, etc.

Default Value

Ops like consDefault use the concept of a default value, but so do things like head (for empty list).

If you force a scalar out of the empty type it will default to being an int (0).

Coercion

Any time you use an op that expects a char and you give it an int it simply first converts the int to a string.

For example:

1,2,3 " "* → "1 2 3"
"abc" 3 append → "abc3"

Promotion

You can give ops that require lists a value whose rank is too low and it will automatically enclose that value in a list of one element, equivalent to using just.

For example:

"asdf" 'z append → "asdfz"
↗️

The char is first turned into a string of one character.

This would never be useful for some ops, like head since it would just return the original value, so it will error in cases like these.

Special Repetitions

There are a a few ops (% cut, & reshape, and * join) whose type signature might not have been what you expected. For example join, the arg types are: [[char]] {[char]}, shouldn't they be [[char]] [char] ?

The {} is just like [] except if the rank is too low, then repeat the arg instead of promote it. This will act like how you expect, but provide additional ability to use a different value at each step of the op.

So for string, something like:

"i","am","a","sentence" " " * → "i am a sentence"
↗️

Works as expected. But we could have changed the second thing inserted (and even terminated earlier too).

"i","am","a","sentence" " ","-" * → "i am-a"
↗️

Here's just another example for inspiration on how to use them: we could get all of the non numbers of a string by cutting out all of the numbers:

"a1,2--33b" dup readAll str cut → ["a",",","--","b"]
↗️

Or a reshape example:

10 countTo 1,2,3,4 reshape → [[1],[2,3],[4,5,6],[7,8,9,10]]
↗️

Ops With Two Generic Types

For ops that take two different generic types, such as sortBy, ifElse, etc. Capitalization / commas still control the vectorization level. But if one arg's excess rank is smaller than the other, then instead of broadcasting it will just reduce that small args required rank.

For example if you wanted to sort some strings by length:

"abc","xy" : len SortBy → ["xy","abc"]
↗️

The effective type of SortBy becomes [[a]] [b] to [[a]]

You can still manually broadcast that second arg using repeat if that is actually what you wanted.

Here len reduced the rank of thing, but this also works if we are increasing the rank we wish to compare by. For example suppose we wanted to filter for composite numbers.

We can calculate all of a numbers divisors with:

6 ; countTo : countTo mod not filter → [1,2,3,6]
↗️

So we can get composite numbers by filtering when the list of composites is not empty after dropping 2:

10 countTo : ; countTo : countTo mod not filter 2 drop Filter → [4,6,8,9,10]
↗️

Note that Filter was used instead of filter since the second arg's rank was a 2d list and we didn't want to vectorize into it.

If both args have a type too low this it would only raise the type of the b type, for example:

1,2,3 0,0,0 Filter → [1]
↗️

I would find it more intuitive if capitalization only controlled the type of the a type since that is what is returned. But this wouldn't be optimal since cases like this would require explicit repetition of the 2nd or 3rd arg to do what you want:

"" 1 2 IfElse → 2
↗️

Aside from this, it should do what you want so you shouldn't have to think about it (just make sure to capitalize based on the higher rank of the two args). If it gets confusing you should look at the implicit ops and types of everything using the graphical representation of your program generated by the Online Interpreter.

Low Rank Overrides

There are some ops such as T instead of tail where we actually do a different op (rangeTo) rather than error or promote. It would be useless to take the tail of a scalar since even if we promoted it to a singleton list, the tail would just return an empty list. So we allow T to mean a different op for a scalar.

4T → [0,1,2,3]
"hi","there" T → ["there"]

All of the low rank overrides (except lines) can also be used vectorized once, but not twice since then ranks would be high enough to mean the non-overriden version of the character. If you do want to use rangeTo on a 2d list, you still can using T, (or a sufficient number of ,s to make it invalid for even higher ranks).

1,2,3T → [[0],[0,1],[0,1,2]]
1,2,3,,4T, → [[[0],[0,1],[0,1,2]],[[0,1,2,3]]]

This also works with lower case overrides like sqrt (u)

4,9,,100,1000 u, → [[2,3],[10,31]]
↗️

It is theoretically possible to create more low rank overrides than there currently are, but the goal of iogii is to be simple, not fully optimized for code golf. It also gets very complicated to do this since they need to obey certain invariants for static typing of circular programs to work correctly.

Static Typing

Believe it or not iogii is statically typed. Static typing is very useful for lazy programs and I would argue that homogeneous lists are more useful than heterogenous ones for code golf, especially since we can overload and do promotion/coercion this way. It also eliminates the problem of the type of an empty list which sometimes should be known!

There's not much downside since iogii is built so that you never need to specify a type and it can always deduce the correct types. In order to get inference to work with circular programs, all ops where carefully selected so that they obey certain invariants (including overloads since you don't know which op will be selected until types are inferred).

That invariant is:

Types are defined in this order empty, int, char. So by increasing I mean walking up in that list.

Most of the time this is how I would want ops to be defined anyways. However, this is why the read op symbol isn't the same as the str op symbol despite them being inverses. Because doing so would violate the first invariant.

Nil Type

The type of nil or the first value in an Expand / Meld is a list of the empty type. It has its own type so that it can become any time without coercion. Its rank will automatically become high enough for any operation.

Errors

Iogii typically tries to avoid giving you an error and will instead return the default falsey value for that type.

For example:

0 0/ → 0
1,2,3 5 get → 0

Negative values don't make sense for many ops like take, drop, etc. In this case they will just behave like 0.