YS External Modules
Reusing code is the cornerstone of good programming.
If you see duplicate logic, refactor it into a function.
If you see a function that's used in multiple places, move it to a module.
If you need the module in multiple repos, publish it online.
Today let's see how we'd go about refactoring a YS program's useful functions into an external module for reuse by anyone.
A Counting cat🔗
Let's take this little poem and put it in a file called romans.txt
:
If you want to count up to ten,
And Roman numerals are your friend,
Let's write some code here
That makes it quite clear
How numbers to letters transcend!
If you want to display this file with line numbers, you can use the cat
command with the -n
option:
$ cat -n romans.txt
1 If you want to count up to ten,
2 And Roman numerals are your friend,
3 Let's write some code here
4 That makes it quite clear
5 How numbers to letters transcend!
Let's write a YS program called cat-n.ys
that does the same thing:
!YS-v0
defn main(file):
doall:
map-indexed _ file:read:lines:
fn(i line):
say:
format '%6s %s':
i.++ line
And run it:
$ ys cat-n.ys romans.txt
1 If you want to count up to ten,
2 And Roman numerals are your friend,
3 Let's write some code here
4 That makes it quite clear
5 How numbers to letters transcend!
Perfect!
Well, maybe not.
This file is begging for a different kind of number!
A Roman cat🔗
Let's change the program to use Roman numerals for the line numbers:
!YS-v0
defn main(file):
doall:
map-indexed _ file:read:lines:
fn(i line):
say:
format '%6s %s':
i.++:to-roman line
numeral-pairs =::
1000 M 900 CM 500 D 400 CD
100 C 90 XC 50 L 40 XL
10 X 9 IX 5 V 4 IV
1 I
numeral-pairs =:
numeral-pairs.words().partition(2)
.map(fn([[n s]] [n:I s]))
defn to-roman(n):
when n >= 1:
val rom =: !:first
filter _ numeral-pairs:
\(_.0 <= n)
str: rom to-roman(n - val)
And run it:
$ ys cat-roman.ys romans.txt
I If you want to count up to ten,
II And Roman numerals are your friend,
III Let's write some code here
IV That makes it quite clear
V How numbers to letters transcend!
That's more like it!
Now I want to use roman numerals in all my programs! But that's a lot of code to copy and paste into every program...
Let's turn it into a module.
Refactoring a Roman cat🔗
First let's turn the roman numeral conversion code into a module, but we'll keep in the same file for now.
!YS-v0
ns: to-roman
numeral-pairs =::
1000 M 900 CM 500 D 400 CD
100 C 90 XC 50 L 40 XL
10 X 9 IX 5 V 4 IV
1 I
numeral-pairs =:
numeral-pairs.words().partition(2)
.map(fn([[n s]] [n:I s]))
defn to-roman(n):
when n >= 1:
val rom =: !:first
filter _ numeral-pairs:
\(_.0 <= n)
str: rom to-roman(n - val)
--- !code
use to-roman: :all
defn main(file):
doall:
map-indexed _ file:read:lines:
fn(i line):
say:
format '%6s %s':
i.++:to-roman line
We just moved the roman code into its own YAML document, and placed it before the main program.
We also gave the module a name, to-roman
, and used ns
to make it a
namespace.
The :all
option for use
means that we're importing all the functions in the
to-roman
namespace into the main program.
Let's run it:
$ ys cat-roman.ys romans.txt
I If you want to count up to ten,
II And Roman numerals are your friend,
III Let's write some code here
IV That makes it quite clear
V How numbers to letters transcend!
Now let's move the first part of the program into a file called to-roman.ys
,
and our main program will look like this:
!YS-v0
use to-roman: :all
defn main(file):
doall:
map-indexed _ file:read:lines:
fn(i line):
say:
format '%6s %s':
i.++:to-roman line
It works just as before.
Publishing a Roman cat🔗
We can publish the module by simply placing it in a GitHub repo.
For today, let's just use a GitHub Gist.
Note: it turns out that GitHub Gists are actually repos themselves.
Here's the Gist URL:
https://gist.github.com/ingydotnet/76662926bc811eecdf6c0d09089a737f
To use it in our YS program, we just do:
!YS-v0
use to-roman: :all
:url 'gist:ingydotnet/76662926bc811eecdf6c0d09089a737f'
defn main(file):
doall:
map-indexed _ file:read:lines:
fn(i line):
say:
format '%6s %s':
i.++:to-roman line
Let's run it on something a little bigger.
How about the module itself?!
$ ys cat-roman.ys <(curl -sL https://gist.github.com/ingydotnet/76662926bc811eecdf6c0d09089a737f/raw)
I !YS-v0
II
III ns: to-roman
IV
V numeral-pairs =::
VI 1000 M 900 CM 500 D 400 CD
VII 100 C 90 XC 50 L 40 XL
VIII 10 X 9 IX 5 V 4 IV
IX 1 I
X
XI numeral-pairs =:
XII numeral-pairs.words().partition(2)
XIII .map(fn([[n s]] [n:I s]))
XIV
XV defn to-roman(n):
XVI when n >= 1:
XVII val rom =: !:first
XVIII filter _ numeral-pairs:
XIX \(_.0 <= n)
XX str: rom to-roman(n - val)
If you need to convert some numbers to Roman numerals in your YS code, feel free to use my new module.
That's why I published it!
Later on we'll learn about better places to publish modules.