Idris for Scala developers

{slides,github}.com/cb372/idris-for-scala-devs
Install Idris
Mac:
brew install idris
Please install the editor plugin for vim, emacs, Sublime, Atom, VS Code, ... (but not IntelliJ!)
Test your installation
$ idris
     ____    __     _
    /  _/___/ /____(_)____
    / // __  / ___/ / ___/     Version 1.1.1
  _/ // /_/ / /  / (__  )      http://www.idris-lang.org/
 /___/\__,_/_/  /_/____/       Type :? for help
Idris is free software with ABSOLUTELY NO WARRANTY.
For details type :warranty.
Idris> 1 + 1
2 : IntegerTest your editor
- touch foo.idr
 - open foo.idr in your editor
 - write one line:
 - press <leader>-d (in vim)
 - another line should magically appear
 
foo : String -> Intfoo : String -> Int
foo x = ?foo_rhsIdris
- Pure-functional Haskell-like language
 - First-class, dependent types
 - Supports multiple codegen backends
	
- Executable binary via C
 - JS
 - JVM
 - ...
 
 
Interesting features
- Interactive editing
 - First class, dependent types
 - Proofs
 - Codata and infinite data structures
 - Totality checking
 - Strictness and laziness
 
Interactive editing
| Key binding (vim) | Description | 
|---|---|
| <leader>-d | Write a skeleton definition for the type signature under the cursor | 
| <leader>-t | Show type of the thing under the cursor | 
| <leader>-c | Write a case split on the variable under the cursor | 
| <leader>-o | Proof search, i.e. guess a value to replace a hole | 
(there are others, but I use these the most)
Interactive editing
In the editor, write a function type signature:
tailOrEmptyList : List a -> Bool -> List aWith the cursor over the function name, use <leader>-d to add a skeleton definition:
tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList xs x = ?tailOrEmptyList_rhsInteractive editing
With the cursor over the hole,
use <leader>-t to see some types:
tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList xs x = ?tailOrEmptyList_rhs
                             ^  a : Type
  xs : List a
  x : Bool
--------------------------------------
tailOrEmptyList_rhs : List aInteractive editing
With the cursor over xs,
use <leader>-c to case split on that variable:
tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList xs x = ?tailOrEmptyList_rhs
                 ^tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] x = ?tailOrEmptyList_rhs_1
tailOrEmptyList (y :: xs) x = ?tailOrEmptyList_rhs_2Interactive editing
With the cursor over the first hole,
use <leader>-o to guess an implementation:
tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] x = ?tailOrEmptyList_rhs_1
                            ^
tailOrEmptyList (y :: xs) x = ?tailOrEmptyList_rhs_2tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] x = []
tailOrEmptyList (y :: xs) x = ?tailOrEmptyList_rhs_2Wow, the compiler just wrote some code for us!
Interactive editing
Now do a case-split on x using <leader>-c:
tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] x = []
tailOrEmptyList (y :: xs) x = ?tailOrEmptyList_rhs_2
                          ^tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] x = []
tailOrEmptyList (y :: xs) False = ?tailOrEmptyList_rhs_1
tailOrEmptyList (y :: xs) True = ?tailOrEmptyList_rhs_3Interactive editing
Finally we write the rest of the implementation manually and tidy up the patterns a bit:
tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] x = []
tailOrEmptyList (y :: xs) False = ?tailOrEmptyList_rhs_1
tailOrEmptyList (y :: xs) True = ?tailOrEmptyList_rhs_3tailOrEmptyList : List a -> Bool -> List a
tailOrEmptyList [] _ = []
tailOrEmptyList (x :: xs) False = []
tailOrEmptyList (x :: xs) True = xsDependent types
Refresher: path-dependent types in Scala
case class Flight(id: String) {
  case class Seat(id: String)
  def seat(id: String) = Seat(id)
  def book(seat: Seat): Unit = 
    println(s"OK, booked seat ${seat.id}")
}
val outwardFlight = Flight("BA007")
val returnFlight  = Flight("BA008")
val outwardSeat = outwardFlight.seat("37A")
val returnSeat  = returnFlight.seat("37A")
println(outwardSeat == returnSeat) // false
outwardFlight.book(outwardSeat) // OK
//outwardFlight.book(returnSeat) // doesn't compileRefresher: path-dependent types in Scala
trait ResourceFactory {
  type T
  def open(name: String): T
}
object URLResources extends ResourceFactory {
  type T = java.net.URL
  def open(name: String) = new java.net.URL(name)
}
object FileResources extends ResourceFactory {
  type T = java.io.File
  def open(name: String) = new java.io.File(name)
}
def openResource(name: String, factory: ResourceFactory): factory.T = 
  factory.open(name)
openResource("foo.txt", FileResources) // returns a File
First class types
val x = String
x : Type
x = StringScala
Idris
error: object java.lang.String is not a value
First class types
Functions can take types as arguments
(although you can't do much with them)
Functions can return types as results
(this is much more interesting)
foo : Type -> Bool
foo _ = Falsefoo : Bool -> Type
foo True  = String
foo False = IntDependent types
In other words, types can be computed.
Types can depend on values.
foo : Bool -> Type
foo True  = String
foo False = IntSilly example
Let's write a function that decides whether a given value is less than 3
But we'll make type safety optional:
data Preference = StronglyTyped | StringlyTypedsealed trait Preference
case object StronglyTyped extends Preference
case object StringlyTyped extends Preferenceequivalent in Scala
stringly-typed/stringly.idr
Silly example
write functions to calculate the input and output types based on the preference:
inputType : Preference -> Type
inputType StronglyTyped = Integer
inputType StringlyTyped = String
outputType : Preference -> Type
outputType StronglyTyped = Bool
outputType StringlyTyped = StringSilly example
type signature of our function:
isLessThanThree : (pref : Preference) -> 
                  (inputType pref) -> 
                  (outputType pref)Silly example
implement using pattern matching:
isLessThanThree StronglyTyped x = x < 3
isLessThanThree StringlyTyped x = boolToString verdict where
  verdict : Bool
  verdict = x == "zero" || 
            x == "one" || 
            x == "two" || 
            pack(take 5 (unpack x)) == "minus"
  boolToString : Bool -> String
  boolToString False = "false"
  boolToString True  = "true"
Proper example: Vector
data Nat =
  Z |
  S NatFirst we'll need natural numbers:
Idris> Z
0 : Nat
Idris> S(Z)
1 : Nat
Idris> S(S(Z))
2 : NatProper example: Vector
data Vect : (len : Nat) -> (elem : Type) -> Type where
  Nil : --TODO
 (::) : --TODOnumber of elements
type of elements
vect/vect.idr
Proper example: Vector
data Vect : (len : Nat) -> (elem : Type) -> Type where
  Nil : Vect Z elem
 (::) : (x : elem) -> 
        (xs : Vect len elem) -> 
        Vect (S len) elem*vect> the (Vect 2 Int) (3 :: 4 :: Nil)
[3, 4] : Vect 2 Int
*vect> the (Vect 1 Int) (3 :: 4 :: Nil)
(input):1:21-23:When checking argument xs to constructor Main.:::
        Type mismatch between
                Vect (S n) elem (Type of x :: xs)
        and
                Vect 0 Int (Expected type)
        Specifically:
                Type mismatch between
                        S n
                and
                        0
Proper example: Vector
Try implementing the following functions for working with Vectors:
- head/tail
 - take/drop
 - (++) to concat two vectors
 - index (gets the element at index i)
	
- Hint: import Data.Fin
 - Fin represents a finitely bounded natural number
 
 
Encoding state transitions in types
A typical Scala World attendee:
Sleeping
Hiking
Scala-ing
wake up
go hiking
go to bed
go to bed
How could we encode this situation in Scala?
- Pattern match on current state
 - Use phantom types
 
Sleeping
Hiking
Scala-ing
wake up
go hiking
go to bed
go to bed
scala-world-attendee/scala/
In Idris we have a similar choice
- Pattern match and return an Either
 - Use a proof of valid state transition
 
Sleeping
Hiking
Scala-ing
wake up
go hiking
go to bed
go to bed
scala-world-attendee/idris/
Proofs
Idris is not a full-blown theorem prover,
but has support for proving things about your programs
addingZeroChangesNothing : (n : Nat) -> n + 0 = n
addingZeroChangesNothing Z = Refl
addingZeroChangesNothing (S k) = cong {f=S} (addingZeroChangesNothing k)
Proofs are used to:
- Convince the compiler of something that's true but only obvious to a human
 - Add constraints to your types
 - Define pre/post-conditions for functions
 - Encode your assumptions
 
Exercise: write a function that inserts a value into a sorted list.
The function should require a proof that the list is sorted.
sorted/sorted.idr
Codata and infinity
Support for infinite data structures like Stream is built into the language with the keyword
codatalists : Int -> List Int -> Stream (List Int)
lists n xs = xs :: (lists (n+1) (xs ++ [n]))
take 5 (lists 1 [])
-- [[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] : List (List Int)codata Stream : Type -> Type where
  (::) : (e : a) -> Stream a -> Stream aExercise: construct an infinite binary tree
1
2 2
3 3 3 3
...
Totality checking
In Scala we get (limited) pattern match exhaustivity checking:
sealed trait A
case object B extends A
case object C extends A
def whatever(x: A): String = x match {
  case B => "yeah"
  //case C => "wow"
}warning: match may not be exhaustive. It would fail on the following input: C
Idris provides more comprehensive totality analysis
e.g. pattern matching on integers:
total
whatever : Int -> String
whatever 1 = "wow"
whatever 5 = "yeah"Main.whatever is not total as there are missing cases
Totality checking
e.g. checking for infinite recursion:
mutual
  total
  neverending : Int -> Int
  neverending x = story (x - 1)
  story : Int -> Int
  story x = neverending (x - 1)Main.neverending is possibly not total due to recursive path Main.neverending --> Main.story --> Main.story
Totality checking
Exercise: fix the code in total/fib.idr to make the function total
Strictness and laziness
Like Scala, and unlike Haskell, Idris is eager by default
Can mark an argument as Lazy,
similarly to in Scala
x: => IntifThenElse : Bool -> Lazy a -> Lazy a -> a
ifThenElse True  t e = t
ifThenElse False t e = eIdris for Scala developers
By Chris Birchall
Idris for Scala developers
- 4,262