DNS is the Domain Name System and is the backbone of the internet:
Most prominently, it translates readily memorized domain names to the numerical IP addresses needed for locating and identifying computer services and devices with the underlying network protocols. The Domain Name System has been an essential component of the functionality of the Internet since 1985.
It is also an oft-cited reason for service outages, with a funny decade-old r/sysadmin meme:
Factor has a DNS vocabulary that supports querying and parsing responses from nameservers:
IN: scratchpad USE: tools.dns
IN: scratchpad "google.com" host
google.com has address 142.250.142.113
google.com has address 142.250.142.138
google.com has address 142.250.142.100
google.com has address 142.250.142.101
google.com has address 142.250.142.102
google.com has address 142.250.142.139
google.com has IPv6 address 2607:f8b0:4023:1c01:0:0:0:8b
google.com has IPv6 address 2607:f8b0:4023:1c01:0:0:0:8a
google.com has IPv6 address 2607:f8b0:4023:1c01:0:0:0:64
google.com has IPv6 address 2607:f8b0:4023:1c01:0:0:0:65
google.com mail is handled by 10 smtp.google.com
Recently, I bumped into an old post on the Cloudflare
blog about The weird and wonderful world of
DNS LOC
records
and realized that we did not properly support parsing RFC
1876 which specifies a format
for returning LOC or location record specifying the physical
location of a service.
At the time of the post, Cloudflare indicated they handle “millions of DNS records; of those just 743 are LOCs.”. I found a webpage that lists sites supporting DNS LOC and contains only nine examples.
It is not widely used, but it is very cool.
You can use the dig command
to query for a LOC record and see what is returned:
$ dig alink.net LOC
alink.net. 66 IN LOC 37 22 26.000 N 122 1 47.000 W 30.00m 30m 30m 10m
The fields that were returned include:
In Factor 0.101, the field is available and returned as bytes but not parsed:
IN: scratchpad "alink.net" dns-LOC-query answer-section>> ...
{
T{ rr
{ name "alink.net" }
{ type LOC }
{ class IN }
{ ttl 300 }
{ rdata
B{
0 51 51 19 136 5 2 80 101 208 181 8 0 152 162 56
}
}
}
}
Of course, I love odd uses of technology like Wikipedia over
DNS and I thought
Factor should probably add proper support for the
LOC record!
First, we define a tuple
class to hold the
LOC record fields:
TUPLE: loc size horizontal vertical lat lon alt ;
Next, we parse the LOC record, converting sizes (in centimeters),
lat/lon (in degrees), and altitude (in centimeters):
: parse-loc ( -- loc )
loc new
read1 0 assert=
read1 [ -4 shift ] [ 4 bits ] bi 10^ * >>size
read1 [ -4 shift ] [ 4 bits ] bi 10^ * >>horizontal
read1 [ -4 shift ] [ 4 bits ] bi 10^ * >>vertical
4 read be> 31 2^ - 3600000 / >>lat
4 read be> 31 2^ - 3600000 / >>lon
4 read be> 10000000 - >>alt ;
We hookup the LOC type to be parsed properly:
M: LOC parse-rdata 2drop parse-loc ;
And then build a word to print the location nicely:
: LOC. ( name -- )
dns-LOC-query answer-section>> [
rdata>> {
[ lat>> [ abs 1 /mod 60 * 1 /mod 60 * ] [ neg? "S" "N" ? ] bi ]
[ lon>> [ abs 1 /mod 60 * 1 /mod 60 * ] [ neg? "W" "E" ? ] bi ]
[ alt>> 100 / ]
[ size>> 100 /i ]
[ horizontal>> 100 /i ]
[ vertical>> 100 /i ]
} cleave "%d %d %.3f %s %d %d %.3f %s %.2fm %dm %dm %dm\n" printf
] each ;
And, finally, we can give it a try!
IN: scratchpad "alink.net" LOC.
37 22 26.000 N 122 1 47.000 W 30.00m 30m 30m 10m
Yay, it matches!
This is available in the latest development version.
“Keep thy airspeed up, lest the earth come from below and smite thee.” - William Kershner
I’m very pleased to announce the release of Factor 0.101!
| OS/CPU | Windows | Mac OS | Linux |
|---|---|---|---|
| x86 | 0.101 | 0.101 | |
| x86-64 | 0.101 | 0.101 | 0.101 |
Source code: 0.101
This release is brought to you with almost 700 commits by the following individuals:
Aleksander Sabak, Andy Kluger, Cat Stevens, Dmitry Matveyev, Doug Coleman, Giftpflanze, John Benediktsson, Jon Harper, Jonas Bernouli, Leo Mehraban, Mike Stevenson, Nicholas Chandoke, Niklas Larsson, Rebecca Kelly, Samuel Tardieu, Stefan Schmiedl, @Bruno-366, @bobisageek, @coltsingleactionarmyocelot, @inivekin, @knottio, @timor
Besides some bug fixes and library improvements, I want to highlight the following changes:
Some possible backwards compatibility issues:
ltake was swapped to be more consistent with words like head.environment vocabulary on Windows now supports disambiguating f and "" (empty) valuesmisc/atom folder was removed in favor of the factor/atom-language-factor repo.misc/Factor.tmbundle folder was removed in favor of the factor/factor.tmbundle repo.misc/vim folder was removed in favor of the factor/factor.vim repo.http vocabulary request tuple had a slot rename from post-data to data.furnace.asides vocabulary had a slot rename from post-data to data, and might require running ALTER TABLE asides RENAME COLUMN "post-data" TO data;.html.streams vocabulary was renamed to io.streams.htmlpdf.streams vocabulary was renamed to io.streams.pdfFactor is a concatenative, stack-based programming language with high-level features including dynamic types, extensible syntax, macros, and garbage collection. On a practical side, Factor has a full-featured library, supports many different platforms, and has been extensively documented.
The implementation is fully compiled for performance, while still supporting interactive development. Factor applications are portable between all common platforms. Factor can deploy stand-alone applications on all platforms. Full source code for the Factor project is available under a BSD license.
ENUMERATION: syntax wordwavelength>rgba@[, *[, and &[, and short-circuiting n&&[, n||[, &&[ and ||[with-datastacks and datastack-states.gitignore filesjson.http and added some useful wordsC-LIBRARY: syntax wordnzip and nunzip, map-zip and map-unzip macroszbase32> and >zbase32BRAINFUCK: syntax word and interpret-brainfuckM\ cache-assoc delete-atyear<, year<=, year>, year>= wordsdefiner implementationdefiner implementationtuple>slots, rename tuple>array to pack-tuple and >tuple to unpack-tuple.definer implementation- to use stdin-q quiet mode flagparallel-map and parallel-assoc-map using a count-down latchifccCQ country code for Sark*-branch stack effectsparse-crontab which ignores blank lines and commentsquery-each row-polymorphickeys and values to assoc-protocolos-env?<font> take a name, easier defaultspost-data slot on aside tuples to dataCDATA tags for unescaping outputpost-data slot on request tuples to datahttp-json that doesn’t return the response objectread-websocket-loop row-polymorphicini>file, file>ini, and use LH{ } to preserve configuration orderutf7 detectionutf8-bom to handle optional BOMrandom-line and random-linesuntil*, fix docs for and* and or*LS{ syntax wordltake,% for a push-at variantjaro-distance and jaro-winkler-distanceall-removals, support Recamán’s sequence, and Tribonacci Numberssubfactorialprime? safe from non-integer inputsjaro-similarity, jaro-winkler-similarity, and trigram-similaritymime.types fileqw: syntaxfind-path*list-of and list-of-manywith-progress-display, map-with-progress-bar, each-with-progress-bar, and reduce-with-progress-bartrace-log and set-trace-log-level, updated to Raylib 5.5"set4", "set5", and "set6" typesmidpoint@ to midpoint, faster each-from and map-reduce on slicesfind-nth, find-nth-last, subseq-indices, deep-nth, deep-nth-of, 2none?, filter-errors, reject-errors, all-same?, adjacent-differences, and partial-sum.?firstn and ?lastn for string inputs, removed (nsequence) which duplicates set-firstn-unsafe<prefixed> arguments to match prefix<cycles-from> and cycle-fromshuffle( syntax, added SHUFFLE: syntax, nreversesort-with to apply the quot with access to the stack belowlinked-thread which used to be for concurrency.mailboxes only>toml and write-toml<WATCH ... WATCH> syntax.out to fix conflict with vocab resourcesheap-stats-of arbitrary sequence of instances, and total-size size of everything pointed to by an objectadjust-font-sizevideo tagsdefine-temp-syntaxui.theme.imagesYears ago, Dropbox wrote about zxcvbn: realistic password strength estimation:
zxcvbnis a password strength estimator inspired by password crackers. Through pattern matching and conservative estimation, it recognizes and weighs 30k common passwords, common names and surnames according to US census data, popular English words from Wikipedia and US television and movies, and other common patterns like dates, repeats (aaa), sequences (abcd), keyboard patterns (qwertyuiop), and l33t speak.
And it appears to have been successful – the original implementation is in JavaScript, but there have been clones of the algorithm generated in many different languages:
At Dropbox we use zxcvbn (Release notes) on our web, desktop, iOS and Android clients. If JavaScript doesn’t work for you, others have graciously ported the library to these languages:
zxcvbn-python(Python)zxcvbn-cpp(C/C++/Python/JS)zxcvbn-c(C/C++)zxcvbn-rs(Rust)zxcvbn-go(Go)zxcvbn4j(Java)nbvcxz(Java)zxcvbn-ruby(Ruby)zxcvbn-js(Ruby [via ExecJS])zxcvbn-ios(Objective-C)zxcvbn-cs(C#/.NET)szxcvbn(Scala)zxcvbn-php(PHP)zxcvbn-api(REST)ocaml-zxcvbn(OCaml bindings forzxcvbn-c)
In today’s era of password managers, WebAuthn also known as passkeys, and many pwned accounts, passwords may seem like a funny sort of outdated concept. They have definitely provided good entertainment over the years from XKCD: Password Strength comics to the 20-year old hunter2 meme:
I have wanted a Factor implementation of this for a long time – and finally built zxcvbn in Factor!
We can use it to check out some potential passwords:
IN: scratchpad USE: zxcvbn
IN: scratchpad "F@ct0r!" zxcvbn.
Score:
1/4 (very guessable)
Crack times:
Online (throttled): 4 months
Online (unthrottled): 8 hours
Offline (slow hash): 30 seconds
Offline (fast hash): less than a second
Suggestions:
Add another word or two. Uncommon words are better.
Capitalization doesn't help very much.
Predictable substitutions like '@' instead of 'a' don't help very much.
IN: scratchpad "john2025" zxcvbn.
Score:
1/4 (very guessable)
Crack times:
Online (throttled): 3 months
Online (unthrottled): 6 hours
Offline (slow hash): 23 seconds
Offline (fast hash): less than a second
Warning:
Common names and surnames are easy to guess.
Suggestions:
Add another word or two. Uncommon words are better.
That’s not so good, maybe we should use the random.passwords vocabulary instead!
This is available on my GitHub.
Factor has green threads and a long-standing feature request to be able to utilize native threads more efficiently for concurrent tasks. In the meantime, the cooperative threading model allows for asynchronous tasks which is particularly useful when waiting for I/O such as used by sockets over a computer network.
And while it might be true that asynchrony is not concurrency, there are a lot of other things one could say about concurrency and multi-threaded or multi-process performance. Today I want to discuss an article that Will McGugan wrote about the overhead of Python asyncio tasks and the good discussion that followed on Hacker News.
Let’s go over the benchmark in a few programming languages – including Factor!
The article presents this benchmark in Python that does no work but measures the relative overhead of the asyncio task infrastructure when creating a large number of asynchronous tasks:
from asyncio import create_task, wait, run
from time import process_time as time
async def time_tasks(count=100) -> float:
"""Time creating and destroying tasks."""
async def nop_task() -> None:
"""Do nothing task."""
pass
start = time()
tasks = [create_task(nop_task()) for _ in range(count)]
await wait(tasks)
elapsed = time() - start
return elapsed
for count in range(100_000, 1_000_000 + 1, 100_000):
create_time = run(time_tasks(count))
create_per_second = 1 / (create_time / count)
print(f"{count:9,} tasks \t {create_per_second:0,.0f} tasks per/s")
Using the latest Python 3.14, this is reasonably fast on my laptop taking about 13 seconds:
$ time python3.14 foo.py
100,000 tasks 577,247 tasks per/s
200,000 tasks 533,911 tasks per/s
300,000 tasks 546,127 tasks per/s
400,000 tasks 488,219 tasks per/s
500,000 tasks 466,636 tasks per/s
600,000 tasks 469,972 tasks per/s
700,000 tasks 434,126 tasks per/s
800,000 tasks 428,456 tasks per/s
900,000 tasks 404,905 tasks per/s
1,000,000 tasks 376,167 tasks per/s
python3.14 foo.py 12.69s user 0.27s system 99% cpu 12.971 total
We could translate this directly to Factor using the concurrency.combinators vocabulary.
In particular, the parallel-map word starts a new thread applying a quotation to each element in the sequence and then waits for all the threads to finish:
USING: concurrency.combinators formatting io kernel math ranges sequences
tools.time ;
: time-tasks ( n -- )
<iota> [ ] parallel-map drop ;
: run-tasks ( -- )
100,000 1,000,000 100,000 <range> [
dup [ time-tasks ] benchmark 1e9 / dupd /
"%7d tasks \t %7d tasks per/s\n" printf flush
] each ;
After making an improvement to our parallel-map implementation that uses a count-down latch for more efficient waiting on a group of tasks, this runs 2.5x as fast as Python:
IN: scratchpad gc [ run-tasks ] time
100000 tasks 1246872 tasks per/s
200000 tasks 1209500 tasks per/s
300000 tasks 1141121 tasks per/s
400000 tasks 1121304 tasks per/s
500000 tasks 1119707 tasks per/s
600000 tasks 1135459 tasks per/s
700000 tasks 956541 tasks per/s
800000 tasks 1091807 tasks per/s
900000 tasks 944753 tasks per/s
1000000 tasks 1137681 tasks per/s
Running time: 5.142044833 seconds
That’s pretty good for a comparable dynamic language, and especially since we are still running in Rosetta 2 on Apple macOS translating Intel x86-64 to Apple Silicon aarch64 on the fly!
It also turns out that 75% of the benchmark time is spent in the garbage collector, so probably there are some big wins we can get if we look more closely into that.
We could translate that benchmark into Go 1.25:
package main
import (
"fmt"
"sync"
"time"
)
func timeTasks(count int) time.Duration {
nopTask := func(done func()) {
done()
}
start := time.Now()
wg := &sync.WaitGroup{}
wg.Add(count)
for i := 0; i < count; i++ {
go nopTask(wg.Done)
}
wg.Wait()
return time.Now().Sub(start)
}
func main() {
for n := 100_000; n <= 1_000_000; n += 100_000 {
createTime := timeTasks(n)
createPerSecond := (1.0 / (float64(createTime) / float64(n))) * float64(time.Second)
fmt.Printf("%7d tasks \t %7d tasks per/s\n", n, createPerSecond)
}
}
And show that it is about 11x times faster than Python using multiple CPUs.
$ time go run foo.go
100000 tasks 3889083 tasks per/s
200000 tasks 5748283 tasks per/s
300000 tasks 6324955 tasks per/s
400000 tasks 6265341 tasks per/s
500000 tasks 6301852 tasks per/s
600000 tasks 5572898 tasks per/s
700000 tasks 6239860 tasks per/s
800000 tasks 6276241 tasks per/s
900000 tasks 6226128 tasks per/s
1000000 tasks 6243859 tasks per/s
go run foo.go 2.44s user 0.71s system 270% cpu 1.165 total
If we limit GOMAXPROCS to one CPU, it runs only 7.5x times faster than Python:
$ time GOMAXPROCS=1 go run foo.go
100000 tasks 2240106 tasks per/s
200000 tasks 2869379 tasks per/s
300000 tasks 2745897 tasks per/s
400000 tasks 3759142 tasks per/s
500000 tasks 3090267 tasks per/s
600000 tasks 3489138 tasks per/s
700000 tasks 3608874 tasks per/s
800000 tasks 3200636 tasks per/s
900000 tasks 3682102 tasks per/s
1000000 tasks 3259778 tasks per/s
GOMAXPROCS=1 go run foo.go 1.65s user 0.08s system 99% cpu 1.735 total
We could build the same benchmark in JavaScript:
async function time_tasks(count=100) {
async function nop_task() {
return performance.now();
}
const start = performance.now()
let tasks = Array(count).map(nop_task)
await Promise.all(tasks)
const elapsed = performance.now() - start
return elapsed / 1e3
}
async function run_tasks() {
for (let count = 100000; count < 1000000 + 1; count += 100000) {
const ct = await time_tasks(count)
console.log(`${count}: ${Math.round(1 / (ct / count))} tasks/sec`)
}
}
run_tasks()
And it runs pretty fast on Node 25.2.1 – about 26x times faster than Python!
$ time node foo.js
100000: 9448038 tasks/sec
200000: 11555322 tasks/sec
300000: 18286318 tasks/sec
400000: 10017217 tasks/sec
500000: 12587060 tasks/sec
600000: 14198956 tasks/sec
700000: 13294620 tasks/sec
800000: 12045403 tasks/sec
900000: 11135513 tasks/sec
1000000: 13577663 tasks/sec
node foo.js 0.82s user 0.10s system 185% cpu 0.496 total
But it runs even faster on Bun 1.3.3 – about 36x times faster than Python!
$ time bun foo.js
100000: 9771222 tasks/sec
200000: 13388075 tasks/sec
300000: 13242548 tasks/sec
400000: 13130144 tasks/sec
500000: 16530496 tasks/sec
600000: 16979009 tasks/sec
700000: 16781272 tasks/sec
800000: 17098919 tasks/sec
900000: 17111784 tasks/sec
1000000: 18288515 tasks/sec
bun foo.js 0.37s user 0.02s system 111% cpu 0.353 total
I’m sure other languages perform both better and worse, but this gives us some nice ideas of where we stand relative to some useful production programming languages. There is clearly room to grow, some potential low-hanging fruit, and known features such as supporting native threads that could be a big improvement to the status quo!
PRs welcome!
After revisiting FizzBuzz yesterday to discuss a Lazy FizzBuzz using infinite lazy lists, I thought I would not return to the subject for awhile. Apparently, I was wrong!
Susam Pal just wrote a really fun article about Solving Fizz Buzz with Cosines:
We define a set of four functions {
s0,s1,s2,s3} for integersnby:
s0(n) = n
s1(n) = Fizz
s2(n) = Buzz
s3(n) = FizzBuzz
And from that, they derive a formula which is essentially a finite Fourier
series for computing the nth
value in the FizzBuzz sequence, showing a nice fixed periodic cycling across
n mod 15, resolving at each value of n to either the integers 0, 1, 2, 3:
I recommend reading the whole article, but I will jump to an implementation of the formula in Factor:
:: fizzbuzz ( n -- val )
11/15
2/3 n * pi * cos 2/3 * +
2/5 n * pi * cos 4/5 * +
4/5 n * pi * cos + round >integer
{ n "Fizz" "Buzz" "FizzBuzz" } nth ;
And we can use that to compute the first few values in the sequence:
IN: scratchpad 1 ..= 100 [ fizzbuzz . ] each
1
2
"Fizz"
4
"Buzz"
"Fizz"
7
8
"Fizz"
"Buzz"
11
"Fizz"
13
14
"FizzBuzz"
16
17
"Fizz"
19
"Buzz"
...
Or, even some arbitrary values in the sequence:
IN: scratchpad 67 fizzbuzz .
67
IN: scratchpad 9,999,999 fizzbuzz .
"Fizz"
IN: scratchpad 10,000,000 fizzbuzz .
"Buzz"
IN: scratchpad 1,234,567,890 fizzbuzz .
"FizzBuzz"
Thats even more fun than using lazy lists!
I wrote about FizzBuzz many years ago. It’s a silly programming task often cited and even included on RosettaCode. The task is described as:
Write a program that prints the integers from
1to100(inclusive).But:
- for multiples of three, print
"Fizz"instead of the number;- for multiples of five, print
"Buzz"instead of the number;- for multiples of both three and five, print
"FizzBuzz"instead of the number.
This has been solved ad nauseum, but a few days ago Evan Hawn wrote about solving Fizz Buzz without conditionals or booleans using Python and the itertools.cycle function to create an infinitely iterable solution.
Let’s build this in Factor!
There are several ways to implement this, including
generators,
but we will be using the lists.lazy
vocabulary to
provide a lazy and infinite stream of values. In particular, by
combining a stream of integers with a cycle of "Fizz" and a cycle of
"Buzz".
The lists.circular vocabulary extends circular sequences to support the lists protocol:
IN: scratchpad USE: lists.circular
IN: scratchpad { 1 2 3 } <circular> 10 ltake list>array .
{ 1 2 3 1 2 3 1 2 3 1 }
Using that, we can create an infinite FizzBuzz list:
: lfizzbuzz ( -- list )
1 lfrom
{ "" "" "Fizz" } <circular>
{ "" "" "" "" "Buzz" } <circular>
lzip [ concat ] lmap-lazy lzip ;
We can print out the first few values quite simply:
IN: scratchpad lfizzbuzz 20 ltake [ first2 "%2d: %s\n" printf ] leach
1:
2:
3: Fizz
4:
5: Buzz
6: Fizz
7:
8:
9: Fizz
10: Buzz
11:
12: Fizz
13:
14:
15: FizzBuzz
16:
17:
18: Fizz
19:
20: Buzz
And if we wanted a more traditional stream alternating between numbers and labels:
IN: scratchpad lfizzbuzz 100 ltake [ first2 [ nip ] unless-empty . ] leach
1
2
"Fizz"
4
"Buzz"
"Fizz"
7
8
"Fizz"
"Buzz"
11
"Fizz"
13
14
"FizzBuzz"
16
17
"Fizz"
19
"Buzz"
...
While not the ultimate FizzBuzz Enterprise Edition, this seems like a fun way to improve upon the simple meant for whiteboards implementation that is most often shared.
Lorem ipsum is a type of placeholder text that can be used in graphic design or web development. The most common form of it will often begin like this paragraph:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I wanted to make a program to generate believable lorem ipsum text using Factor.
We start by defining a bunch of possible words:
CONSTANT: words qw{
a ab accusamus accusantium ad adipisci alias aliquam aliquid
amet animi aperiam architecto asperiores aspernatur
assumenda at atque aut autem beatae blanditiis commodi
consectetur consequatur consequuntur corporis corrupti culpa
cum cumque cupiditate debitis delectus deleniti deserunt
dicta dignissimos distinctio dolor dolore dolorem doloremque
dolores doloribus dolorum ducimus ea eaque earum eius
eligendi enim eos error esse est et eum eveniet ex excepturi
exercitationem expedita explicabo facere facilis fuga fugiat
fugit harum hic id illo illum impedit in incidunt inventore
ipsa ipsam ipsum iste itaque iure iusto labore laboriosam
laborum laudantium libero magnam magni maiores maxime minima
minus modi molestiae molestias mollitia nam natus
necessitatibus nemo neque nesciunt nihil nisi nobis non
nostrum nulla numquam obcaecati odio odit officia officiis
omnis optio pariatur perferendis perspiciatis placeat porro
possimus praesentium provident quae quaerat quam quas quasi
qui quia quibusdam quidem quis quisquam quo quod quos
ratione recusandae reiciendis rem repellat repellendus
reprehenderit repudiandae rerum saepe sapiente sed sequi
similique sint sit soluta sunt suscipit tempora tempore
temporibus tenetur totam ullam unde ut vel velit veniam
veritatis vero vitae voluptas voluptate voluptatem
voluptates voluptatibus voluptatum
}
We then use these to build a random ipsum sentence:
: random-sentence ( -- str )
2 ..= 5 random [
words 3 ..= 12 random sample " " join
] replicate ", " join
0 over [ ch>upper ] change-nth "?." random suffix ;
Then build a random ipsum paragraph from sentences:
: random-paragraph ( -- str )
2 ..= 4 random [ random-sentence ] replicate " " join ;
We can define the initial paragraph above:
CONSTANT: initial-paragraph "\
Lorem ipsum dolor sit amet, consectetur adipisicing \
elit, sed do eiusmod tempor incididunt ut labore et \
dolore magna aliqua. Ut enim ad minim veniam, quis \
nostrud exercitation ullamco laboris nisi ut aliquip ex \
ea commodo consequat. Duis aute irure dolor in \
reprehenderit in voluptate velit esse cillum dolore eu \
fugiat nulla pariatur. Excepteur sint occaecat cupidatat \
non proident, sunt in culpa qui officia deserunt mollit \
anim id est laborum."
And use it to make random ipsum paragraphs, starting with the initial one:
: random-paragraphs ( n -- str )
<iota> [
zero? [ initial-paragraph ] [ random-paragraph ] if
] map "\n" join ;
Or even generate a list of random ipsum words, understanding that sample can’t generate more samples than the length of the sequence being sampled from:
:: random-words ( n -- str )
words length :> w
[
n [ words over w min sample % w [-] ] until-zero
] { } make ;
We can make a command-line interface using the argument parser to return words, sentence, or paragraph:
CONSTANT: OPTIONS {
T{ option
{ name "--w" }
{ help "Generate some lorem ipsum words" }
{ #args 1 }
{ type integer }
}
T{ option
{ name "--s" }
{ help "Generate a lorem ipsum sentence" }
{ const t }
{ default f }
}
T{ option
{ name "--p" }
{ help "Generate a lorem ipsum paragraph" }
{ const t }
{ default f }
}
}
MAIN: [
OPTIONS [
"w" get [ random-words print ] when*
"s" get [ random-sentence print ] when
"p" get [ random-paragraph print ] when
] with-options
]
And that gives you automatic help text showing the available options:
$ ./factor -run=lorem-ipsum --help
Usage:
factor -run=lorem-ipsum [--help] [--w W] [--s] [--p]
Options:
--help show this help and exit
--w W Generate some lorem ipsum words
--s Generate a lorem ipsum sentence
--p Generate a lorem ipsum paragraph
We can test it by generating some words, a sentence, and a paragraph:
$ ./factor -run=lorem-ipsum --w 10
vero eos quos optio magni soluta nulla delectus voluptas neque
$ ./factor -run=lorem-ipsum --s
Totam dicta laborum perferendis unde voluptas, culpa dignissimos odio
distinctio rem eius, tempora harum corporis accusamus.
$ ./factor -run=lorem-ipsum --p
Quaerat maiores veniam minus reprehenderit architecto numquam mollitia earum,
natus assumenda eius cumque minima sint magni accusantium facere, eius aperiam
explicabo molestias voluptatibus aspernatur maiores assumenda, nulla illo
doloremque voluptatum excepturi accusamus porro officiis tempore molestiae
saepe, iusto quibusdam explicabo obcaecati saepe quasi voluptate? Velit libero
tempore in nobis ratione nisi laborum rerum natus ipsam, aperiam placeat
laborum delectus dolor ab dolores itaque. Fuga maxime culpa quae adipisci, modi
quod distinctio ipsam, et vero natus consequuntur neque placeat saepe quam
perferendis, voluptate nemo ducimus ullam recusandae iusto laboriosam iure
temporibus sed saepe, optio dignissimos dolor modi accusamus quod culpa ab? Ad
dolore dignissimos, perferendis accusamus ducimus fuga eveniet a ut.
The code for this is on my GitHub.
Cardinal direction
describes points on a compass — North (N), East (E), South (S),
and West (W).
In addition to those, there are also 4 intercardinal directions (Northwest
or NW, NE, SW, SE), 8 secondary intercardinal directions
(North-Northwest or NNW, WNW, NNE, ENE, WSW, SSW,
ESE, SSE), as well as 16 tertiary intercardinal directions
(Northwest-by-North or NWbN, etc.).
In fact, you can see all the points of the compass divided into 32 textual directions:
I like puzzles and sometimes those come from code golfing. I stumbled across two symmetric challenges on the Code Golf and Coding Challenges Stack Exchange:
I thought it would be a good task to code in Factor.
We start by enumerating the descriptive names for all 32 points of the compass. For convenience we use the quoted words vocabulary.
CONSTANT: directions qw{
N NbE NNE NEbN NE NEbE ENE EbN
E EbS ESE SEbE SE SEbS SSE SbE
S SbW SSW SWbS SW SWbW WSW WbS
W WbN WNW NWbW NW NWbN NNW NbW
}
Then, parsing a compass degrees to a textual name involves rounding to the nearest 32-point:
: compass>string ( compass -- str )
360/32 / round >integer 32 mod directions nth ;
We can use some test cases from the challenges to check that it works:
{ "N" } [ 0 compass>string ] unit-test
{ "NNE" } [ 23.97 compass>string ] unit-test
{ "NEbN" } [ 33.7 compass>string ] unit-test
{ "ENE" } [ 73.12 compass>string ] unit-test
{ "EbN" } [ 73.13 compass>string ] unit-test
{ "SWbS" } [ 219 compass>string ] unit-test
{ "W" } [ 275 compass>string ] unit-test
{ "WbN" } [ 276 compass>string ] unit-test
{ "WNW" } [ 287 compass>string ] unit-test
And the reverse converts the name back to compass degrees by grabbing the index and multiplying:
: string>compass ( str -- compass )
directions index 360/32 * ;
It might not be the shortest solution, but it works and it was fun to build!
planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.