Introducing Aptus: a utility library to improve the Scala experience

Hi everyone,

This is an announcement for Aptus, a new open source utility library for Scala (Apache 2 license).

Here’s a hodgepodge of examples showcasing where it typically applies and how to use it:

// assuming: libraryDependencies += 
//   "io.github.aptusproject" %% "aptus-core" % "0.3.0"
import aptus._

"hello"                    .p // prints "hello"
"hello".assert(_.size <= 5).p // prints "hello"
"hello".assert(_.size >  5).p // assertion error
"hello".p.toUpperCase.p       // prints: "hello", then "HELLO"

"hello".append(" you!").p // prints "hello you!"
"hello".quote          .p // prints "\"hello\""
"hello".padLeft(8, ' ').p // prints "   hello"

"hello|world".splitBy("|").p // prints Seq(hello, world)

"hello".pipeIf(_.size <= 5)(_.toUpperCase).p // prints "HELLO"
"hello".pipeIf(_.size >  5)(_.toUpperCase).p // prints "hello"

"hello".in.someIf(_.size <= 5).p // prints Some("hello")
"hello".in.someIf(_.size >  5).p // prints None

Seq()    .force.option.p // prints "None"
Seq(1)   .force.option.p // prints "Some(1)"
Seq(1)   .force.one   .p // prints "1"
Seq(1, 2).force.one   .p // error
Seq(1, 2).force.option.p // error

"foo".in.some.p // Some("foo")
"foo".in.seq .p // Seq ("foo")

(1, 2).mapSecond(_ + 1) // prints (1, 3)

(Some(1), Some(2)).toOptionalTuple.p // prints Some((1, 2))

Seq(1, 2, 3).zipSameSize(Seq(4, 5, 6)).p // Seq((1,4), (2,5), (3,6))

implicit val ord: Ordering[Seq[Int]] = aptus.seqOrdering
Seq(Seq(4, 5, 6), Seq(1, 2, 3)).sorted.p // Seq(Seq(1, 2, 3), Seq(4, 5, 6))

Seq(1 -> "a", 2 -> "b")          .force.map.p // Map(1 -> "a", 2 -> "b")
Seq(1 -> "a", 2 -> "b", 2 -> "c").force.map.p // error

Seq(1, 2, 3, 4, 5).slidingPairs // Seq((1, 2), (2, 3), (3, 4), (4, 5))

().fs.homeDirectoryPath().p // prints "/home/tony" (for me)
"echo hello".systemCall() // prints: "hello"

Seq("hello", "world").writeFileLines("/tmp/lines.gz")
"/tmp/lines.gz".readFileLines().p // prints: Seq("hello", "world")

More thorough examples can be found on the main README page, along with how to set it up, and the motivation for creating it.

I would love to hear your thoughts on it! In particular I’m curious to hear about some of the examples I describe in the README page, such as this one or that one. Do you:

  1. Never encounter such situations, or too rarely to care
  2. Encounter them but prefer to:
    • a. Use the manual way
    • b. Not code defensively
    • c. Create your own utility methods
    • d. Use another library to handle them (if so which?)

Thank you!

Anthony (http://anthonycros.com)

2 Likes

Hi all,

Version 0.4.0 of Aptus has been published! It’s available for Scala 2.12, 2.13 and 3.0.

Because I know everyone’s holding their breath on that one :stuck_out_tongue:

PS: new release of Gallia - which uses Aptus extensively - is imminent as well

1 Like

For the first one the multi-line version is usually fine for me. For the second I use Option.when since 2.13, and when I’m one 2.12 I get mad.

By the way for assertions the std library gives you "hello".ensuring(_.size > 5).toUpperCase (which I never use).

Interesting, I knew neither actually, thanks for sharing that! I think .ensuring is indeed a better replacement than my .assert, but Option.when still seems pretty boilerplatey:

import scala.util.chaining._
"foo".pipe(x => Option.when(x.size < 5)(x.toUpperCase)) // Some("FOO")
"foo".pipe(x => Option.when(x.size > 5)(x.toUpperCase)) // None

VS:
"foo".in.someIf(_.size < 5)                             // Some("FOO")
"foo".in.someIf(_.size > 5)                             // None

Any thoughts on some of the other use cases such as:

  1. "hello".p.toUpperCase.p // prints: "hello", then "HELLO" (i use it a lot for quick+dirty debugging)
  2. Seq(1).force.one // 1
  3. Seq(1, 2, 3).zipSameSize(Seq(4, 5, 6)) // Seq((1,4), (2,5), (3,6))
  4. "foo|bar".splitBy("|") vs this:
        import org.apache.commons.lang3.StringUtils
        val str = "foo|bar" // or "", or "foo|" or "foo||", ...
        if (str.isEmpty()) List(str)
        else               StringUtils.splitByWholeSeparatorPreserveAllTokens(str, "|").toList

Note that I code particularly defensively in general, I try to enforce invariants as much as I reasonnably can

1 Like

Actually for "foo".in.someIf(_.size < 5) specifically I’ve been known to use Some("foo").filter(_.size < 5).

For splitting strings I usually use str.split('|'). It’s an extension method in StringOps that takes a character instead of a string. Or depending on the required semantics str.split("\\|", -1). I think the only difference will be for str.isEmpty but I don’t think I’ve ever needed that to give me Array("").

The Some+filter is a good trick actually!

Unfortunately the split(Char) also suffers from the odd behavior of dropping any trailing empty items (painful if splitting eg a TSV row manually), and lacks a split(Char, -1) counterpart. Over the years I’ve accepted that using splitByWholeSeparatorPreserveAllTokens was the better alternative, despite being a mouthful (though hence my .splitBy(String) shorthand)

In Scala it’s idiomatic for side-effecting methods to take at least one argument list, even if empty. So perhaps it should be .p().

That’s a good point, I suppose I should at least add it to the more explicit prt() method (called by p), and put a comment on p regarding why it doesn’t have the parens. I’d rather not put them on p because it’s really meant to be for quick-and-dirty debugging (and I believe scala 3 would complain when missing?). I use e.g. foo.p.bar.p.baz all the time locally, but try never to leave it in production.

Aptus 0.6.0 has been released! Binaries for are available on Maven Central for Scala 3.4.0, 2.13.13, and 2.12.19.

Of note is my attempt to improve the motivation section, which will undoubtedly have people flock to the library now. So make sure to grab your github star before they’re all gone!

Also see release page for more details and don’t hesitate to reach out with questions :slight_smile:

1 Like

I wonder about the upgrade to Scala 3.4.0. Was there anything lacking in 3.3 LTS that forced you to use Scala Next versions? In general it’s recommended for library authors to say on LTS unless they need some cutting edge features, so the users or other libraries are not forced to upgrade if they don’t want to.
It might be also interesting in the light of discussions should new users default to LTS or Next in Scala homepage should make LTS clearer? - Documentation - Scala Contributors

I’ll admit I had not realized that 3.3 was LTS: if I did, I would have stuck with it indeed. I was just excited for a new version of Scala… In light of this, I will publish a v0.6.1 with 3.3 instead and deprecate v0.6.0.

I would indeed suggest to make it clearer on the home page, although I think I’m mostly to blame here for not being more thorough in the first place.

3 Likes