Skip to content

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 External link

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.

Comments