1 of 69

Beyond Bash

Shell scripting in a typed, OO language

Scala by the Bay, 15 August 2015

Slides: http://tinyurl.com/beyondbash

2 of 69

0.1 Who am i

Li Haoyi

Paid $ to work on dev tools @ Dropbox

Not paid $ to work on Scala.js

Using Scala professionally since… never

3 of 69

0.2 Agenda

  • 0.x: Agenda
  • 1.x: Bash
  • 2.x: Ammonite-Ops
  • 3.x: Ammonite-REPL
  • 4.x: Conclusion
  • 5.x: Q&A

4 of 69

0.3 Problem Statement

“How can we stop using the worst languages in the world to build our most important infrastructure?

5 of 69

1.1 Application Architecture

Server

Client

Client

Database

Server

Safety

Maybe Safety?

DANGER

DANGER

DANGER

DANGER

6 of 69

1.2 Application Architecture

Server

Client

Client

Database

Server

Safety

Maybe Safety?

Safety

Safety

7 of 69

1.3 Scala.js!

Javascript: Problem solved

Scala.js works

Check it out if you haven't

http://www.scala-js.org/

8 of 69

1.3 Scala.js!

  • Casting is great elem.asInstanceOf[html.Input]
    • In Javascript, every expression is a cast!

  • Weird, unsound behavior is fine
    • As long as it’s less weird/unsound than Javascript

  • Best-effort error-handling is outstanding
    • Javascript doesn’t put in effort at all

9 of 69

Bad when better than worse is excellent

10 of 69

1.4 Application Architecture

Server

Client

Client

Database

Server

Safety

Maybe Safety?

Safety

Bash, Python, Puppet, Ruby, Vagrant...

Safety

DANGER

11 of 69

1.5 Danger Below!

High-performance, type-safe application code

High-performance, type-safe web front-end

Underpinned by a mix of Bash, Python, Ruby, Puppet, Vagrant, ...

12 of 69

1.5 Danger Below!

Hard to test!

Not typechecked!

Worst consequences for errors

13 of 69

1.5 Danger Below!

Server

Client

Client

Database

Server

Ok

Ok

Bash, Python, Puppet, Ruby, Vagrant...

Ok

Ok

Down

14 of 69

1.5 Danger Below!

Server

Client

Client

Database

Server

Ok

Ok

Ok

Ok

Down

Down

Down

Bash, Python, Puppet, Ruby, Vagrant...

Ok

15 of 69

1.5 Danger Below!

Server

Client

Client

Database

Server

Ok

Ok

Ok

Ok

Down

Down

Down

Down

Bash, Python, Puppet, Ruby, Vagrant...

16 of 69

bash$

17 of 69

1.6 What's wrong with Bash?

  • Obscure syntax if [[$? -eq 0 ]] if [[ $? -eq 0 ]]
    • Even though you use it every day for 10 yrs

  • Everything is global
    • Everything is spooky!

  • Everything is a String

  • Even basic math/logic is incredibly difficult

18 of 69

1.7 What's wrong with Bash?

# Run a script on all files with some extension

find . -name '*.ext' | while IFS=$'\n' read -r FILE; do� process "$(readlink -f "$FILE")" || echo "error processing: $FILE"done

find . -name '*.ext' \( -exec ./some_other_script "$PWD"/{} \; -o -print \)

find . -name '*.ext' -exec ./some_other_script "$PWD"/{} \;

http://stackoverflow.com/questions/4410412/bash-processing-recursively-through-all-files-in-a-directory

It seems to work

???

Incorrect

???

Good Solution!

???

19 of 69

“It seems to work”

Such a high degree of confidence!

20 of 69

Why do people use Bash

Can we use something else?

21 of 69

Sample use case

  • List the things in my current folder
  • Look at my current git
  • Make a folder with a file inside
  • Delete the folder

22 of 69

Why do people use Bash

Can we use something else?

23 of 69

No

24 of 69

Bash is Better

25 of 69

1.9 Bash vs Scala

rm -rf folder/inner_dir

def removeAll(path: String) = {� def rec(f: File): Seq[File] =� f.listFiles� .filter(_.isDirectory)� .flatMap(rec) � .++(f.listFiles)� for(f <- rec(new File(path))){� if (!f.delete())� throw new RuntimeException()� }�}�removeAll("folder/inner_dir")

1 line 24 chars

12 lines 279 chars

26 of 69

1.10 Bash vs Python

rm -rf folder/inner_dir

import shutil�shutil.rmtree('folder/my_file.jpg')

1 line 24 chars

2 lines 50 chars

27 of 69

1.11 Bash vs Python: Round 2

import subprocess�subprocess.check_call(["git", "status"])

git status

1 line 10 chars

2 lines 60 chars

Important Bits

Dumb Noise

28 of 69

1.12 Bash is Better

Less syntactic ceremony cp fileB fileB

Common operations are short ls

Fewer keystrokes overall

Commands do what you want rm -rf folder

Very Important!

29 of 69

Ammonite-Ops

Rock-solid filesystem ops in Scala

"com.lihaoyi" %% "ammonite-ops" % "0.4.5"

30 of 69

2.1 Ammonite-Ops

  • Goals:
    • No more than 2x as verbose as Bash
    • Safer than working with Python or java.{io, nio}

  • Non-Goals!
    • Monadic pure dependent-typed safety
    • Reactive manifesto accreditation
    • 50-year enterprise maintainability

31 of 69

2.2 Ammonite-Ops

git status

rm folder/my_file.jpg

%git 'status

rm! 'folder/"my_file.jpg"

1 line 10 chars

1 line 21 chars

1 line 12 chars

1 line 25 chars

32 of 69

2.3 A Taste of Ammonite

import ammonite.ops._

// Delete a file or folder�rm! cwd/'folder��// Make a folder named "folder"�mkdir! cwd/'folder��// Copy a file or folder�cp(cwd/'folder, cwd/'folder1)��// List the current directoryval listed = ls! cwd

Short commands that mirror Bash

That do what you want!

No ambiguity in parsing arguments

33 of 69

2.4 A Taste of Ammonite

// List the current directoryval listed: Seq[Path] = ls! cwd��// Commands return normal values// you can process normallyfor(path <- listed){� println(path)� // paths are proper data-structures// with attributes, methods, etc.if (path.ext == "tmp") rm! path�}

Values are typed, structured data

No string munging trying to do simple tasks!

34 of 69

2.5 Piping

things | f -> things map fthings || f -> things flatMap fthings |? f -> things filter fthings |& f -> things reduce fthings |! f -> things foreach fthings |> f -> f(things)

f! thing -> f(thing)

Traversable

Any

T => V

35 of 69

2.6 Putting it Together

  • Concise filesystem operations
    • ls! cwd

  • Structured, concise path operations
    • ls! cwd/'src/'main

  • Pipes as aliases for collection methods
    • ls! cwd/'src/'main |? (_.ext == "scala") | (_.size) sum

36 of 69

2.7 Putting it Together

# Recursive line count of Javascript files

find ./dir -name '*.js' | xargs wc -l

�ls.rec! cwd/'dir |? (_.ext == "js") | read.lines | (_.size) sum

38 chars

64 chars

37 of 69

2.8 Putting it Together

# List dot-files *only*

ls -a | grep "^\."

�ls! cwd |? (_.last(0) == '.')

19 chars

30 chars

38 of 69

2.9 Putting it Together

# Largest 7 files in the current directory

find . -ls | sort -nrk 7 | head -7

ls.rec! cwd | (x => x.size -> x) sortBy (-_._1) take 7

35 chars

55 chars

39 of 69

2.10 Ammonite-Ops

  • Easy, convenient filesystem ops in Scala!

  • (Almost) as concise as Bash ls! cwd
    • Definitely less typing than java.io/nio

  • Clean, structured data-model
    • Paths. Are. Not. Strings! cwd/'src/'main/"file.txt"
    • Results from commands aren’t strings either

40 of 69

2.11 This begs the question...

Can we use Ammonite-Ops + Scala-REPL as our default shell?

Let’s try contributing some changes to https://github.com/lihaoyi/demo

41 of 69

No

42 of 69

2.12 No

  • Echo-ed output is unreadable
  • Ctrl-C kills everything; bye bye work!
  • Can’t subprocess out w/o borking JLine
  • http://lihaoyi.github.io/Ammonite/#OtherFixes

43 of 69

Ammonite-REPL

Re-inventing the Scala REPL

44 of 69

3.1 Ammonite-REPL

  • Goal
    • You should not need to exit the REPL

  • How often do you need to restart Bash?

45 of 69

3.2 Using the Ammonite REPL

# Standalone Executable�curl -L -o amm https://git.io/v3E3V; chmod +x amm; ./amm

// SBT project�libraryDependencies += (

"com.lihaoyi" % "ammonite-repl" % "0.4.5" % "test"

cross CrossVersion.full

)�initialCommands in (Test, console) :=

"""ammonite.repl.Repl.run("")""" // sbt test/console

46 of 69

Live Demo

Whee!

47 of 69

3.3 Fun Features

  • Great pretty-printing

  • Syntax-highlighted everything!

  • Ctrl-C Interruptible

  • Live-loading modules from maven central

  • Multi-line editing!

48 of 69

3.4 Ammonite-REPL

  • A strictly-better Scala REPL

  • Usable in any SBT project

  • Or standalone

49 of 69

3.5 This begs the question...

Can we use Ammonite-Ops + Ammonite-REPL as our default shell?

Let’s try contributing some changes to https://github.com/lihaoyi/wootjs

50 of 69

3.6 Ammonite-REPL

  • Scala-REPL is not a plausible systems shell

  • Ammonite-REPL is!

  • (Possibly)

  • You can do real work in it

51 of 69

3.7 Work In Progress

  • Extensible Autocomplete
    • Already autocomplete properties, names in scope
    • Need to autocomplete filesystem paths
    • Nice to have autocomplete for ivy coordinates, etc.

  • Fetch scaladoc, source to show in-terminal

  • Windows support for Ammonite-REPL
    • Ammonite-Ops already works

52 of 69

Conclusion

WTF did we just do?

53 of 69

4.1 Ammonite...

Ammonite-Ops

Really-nice Filesystem Library

Ammonite-REPL

Really-nice Scala REPL

Bash Replacement?

54 of 69

4.2 Ammonite...

  • Re-implemented much of Bash’s functionality in Scala

  • Twisted Scala’s syntax into a weird, bash-like form

  • Re-implemented the Scala REPL to make this work

55 of 69

Why?

Did we need to do so many things?

56 of 69

4.3 Why Not...

  • Make Bash less unsafe?

  • Make Python less verbose?

  • Improve on java.io or java.nio?

57 of 69

4.4 Space of Possible Systems APIs

Danger

Verbosity

java.io

java.nio

Bash

Python

FP, Inferred Types, ...

Ammonite

Plains of Diminished Returns

Cliffs of Insanity

58 of 69

4.5 Problems w/ Scala as your Shell

  • JVM takes time to boot up!
    • 3-4s startup time
    • Not just JVM boot but classloading, etc.

  • 3-4s first command compile
    • 0.2-0.3s compile overhead after warmup

  • Bash takes ~0.004s to boot, Python ~0.03s

  • Jar is 30mb, jar + JVM is >100mb

59 of 69

4.6 Hopefully free improvements

  • Java 9 w/ modules will help JDK size/speed
    • Can bundle minimal JVM for smaller executable
    • Fewer classes to load on boot

  • Dotty would (hopefully) speed compilation
    • At least it can’t get much slower, right? Right?...

  • Dotty Linker would help overall
    • Should cut down the amount of stuff to load/JIT

60 of 69

5.0 Application Architecture

Server

Client

Client

Database

Server

Safety

Maybe Safety?

Bash, Python, Puppet, Ruby, Vagrant...

DANGER

DANGER

DANGER

DANGER

DANGER

DANGER

61 of 69

5.1 Application Architecture

Server

Client

Client

Database

Server

Safety

Maybe Safety?

Safety

Bash, Python, Puppet, Ruby, Vagrant...

DANGER

Safety

62 of 69

5.2 Application Architecture

Server

Client

Client

Database

Server

Safety

Maybe Safety?

Safety

Ammonite-Ops Ammonite-REPL

Safety

Safety

63 of 69

5.3 Beyond Bash

  • "com.lihaoyi" %% "ammonite-ops" % "0.4.5"

  • curl -L -o amm https://git.io/v3E3V; chmod +x amm; ./amm

  • Questions?

64 of 69

Additional Slides

65 of 69

2.5 Absolute Paths & RelPaths

case class Path(segments: Seq[String])

case class RelPath(segments: Seq[String], ups: Int)

Absolute

Any ..s at the start of the path

66 of 69

2.6 Constructing Paths

> root�/��> root/'usr/'bin/usr/bin��> 'src/'main�src/main��> up/up/'src/'main�../../src/main

Paths are constructed using / and...

  • Segments
    • Strings
    • Symbols
  • Builtins
    • root: Path
    • cwd: Path
    • up: RelPath

67 of 69

2.7 Combining Paths

> val rel = 'src/'main�src/main��> val wd = root/'Users/'lihaoyi/Users/lihaoyi��> wd/rel�/Users/lihaoyi/src/main��> wd/rel/up�/Users/lihaoyi/src

Paths can be stitched together using /

Paths are normalized at every step!

not /Users/lihaoyi/src/..

68 of 69

2.8 Invalid Paths

> val rel: RelPath = 'src/'main

> val abs: Path = root/'usr/'bin��> abs/rel�/usr/bin/src/main�> rel/abs�<console>:15: error: type mismatch;�> rel/rel

src/main/src/main�> abs/abs�<console>:14: error: type mismatch;

Combining Paths & RelPaths improperly is a compilation error

69 of 69

2.9 Invalid Paths

> rel = "???"> abs = "???"��> abs + "/" + rel�> abs + rel�> "/" + abs + rel�> "/" + abs + "/" + rel�> rel + abs�// correct but annoying to write> os.path.join(abs, rel)

> val rel: RelPath = 'src/'main

> val abs: Path = root/'usr/'bin��> abs/rel�/usr/bin/src/main�> rel/abs�<console>:15: error: type mismatch;�> rel/rel

src/main/src/main�> abs/abs�<console>:14: error: type mismatch;