Idris for Pedestrians

Alexander Tchitchigin

Typeable.io

Big picture

Java
[Not supported by viewer]
Clojure
[Not supported by viewer]
OCaml
[Not supported by viewer]
Haskell
[Not supported by viewer]
Idris
[Not supported by viewer]
Coq
[Not supported by viewer]
Agda
[Not supported by viewer]
Lean
[Not supported by viewer]

Idris characteristics

  • Research language
    • Changes rapidly
    • Not always backwards-compatible
    • Parts lack documentation
  • Not for production
    • Basic runtime
    • Bugs
  • Self-disruptive :D
    • Blodwen

Idris features

  • Pure functional language
    • Totality-checker
    • Interfaces
    • MONADS
  • Dependent types
    • Types depend on values
    • Π-, Σ- and equality types
  • Proofs
    • Manual
    • Elaborator Reflection

Functional programming

data MyList : (a : Type) -> Type where
  Nil  : MyList a
  Cons : a -> MyList a -> MyList a

myAppend : MyList a -> MyList a -> MyList a
myAppend   Nil         ys = ys
myAppend   (Cons x xs) ys = Cons x (myAppend xs ys)

Actual definitions

data List a = [] | a :: (List a)

append : List a -> List a -> List a
append   []        ys = ys
append   (x :: xs) ys = x :: (append xs ys)

Dependent types

data Vect : (n : Nat) -> (a : Type) -> Type where
  Nil  : Vect 0 a
  Cons : a -> Vect n a -> Vect (S n) a

vAppend : Vect n a -> Vect m a -> Vect (n+m) a
vAppend   Nil         ys = ys
vAppend   (Cons x xs) ys = Cons x (vAppend xs ys)

More dependent types

myZip : List a -> List b -> List (a, b)
myZip [] _ = []
myZip _ [] = []
myZip (x :: xs) (y :: ys) = (x, y) :: myZip xs ys
vZip : Vect n a -> Vect n b -> Vect n (a, b)
vZip [] [] = []
vZip (Cons x y) (Cons z w) = Cons (x, z) (vZip y w)

Step by step

vZip : Vect n a -> Vect n b -> Vect n (a, b)
vZip [] [] = []
vZip (Cons x y) (Cons z w) = Cons (x, z) (vZip y w)
vZip : Vect n a -> Vect n b -> Vect n (a, b)
vZip [] [] = []
vZip (Cons x y) (Cons z w) = ?n_1
vZip : Vect n a -> Vect n b -> Vect n (a, b)
vZip [] [] = ?n_1
vZip (Cons x y) ys = ?n_2
vZip : Vect n a -> Vect n b -> Vect n (a, b)
vZip [] ys = ?n_1
vZip (Cons x y) ys = ?n_2
vZip : Vect n a -> Vect n b -> Vect n (a, b)
vZip xs ys = ?n

Tricky case

vFilter : (a -> Bool) -> Vect n a -> (m : Nat ** Vect m a)
vFilter pred [] = (0 ** [])
vFilter pred (Cons x xs) = if pred x
  then let (n ** xs') = vFilter pred xs
       in (S n ** (Cons x xs'))
  else vFilter pred xs
myFilter : (a -> Bool) -> List a -> List a
myFilter pred [] = []
myFilter pred (x :: xs) = if pred x
  then x :: myFilter pred xs
  else myFilter pred xs

Data store

$ ./datastore
Command: new String Int
OK.
Command: new Int String
OK.
Command: add 2 "two"
ID 0.
Command: add 3 "3"
ID 1.
Command: add 4 "1111"
ID 2.
Command: get 1
3, "3"
Command: get 2
4, "1111"
Command: get 0
2, "two"
Command: quit

Data store

module Main

import Data.Vect

infixr 5 .+.

data Schema = SInt | SString | (.+.) Schema Schema

SchemaType : Schema -> Type
SchemaType SInt = Int
SchemaType SString = String
SchemaType (x .+. y) = (SchemaType x, SchemaType y)

display : SchemaType schema -> String
display {schema = SInt} item = show item
display {schema = SString} item = show item
display {schema = (x .+. y)} (a, b) = display a ++ ", " ++ display b

Data store

record DataStore where
  constructor MkData
  schema : Schema
  size : Nat
  items : Vect size (SchemaType schema)

-- Returns new store with given element added as the last one
addToStore : (store : DataStore) -> SchemaType (schema store) -> DataStore
addToStore (MkData schema size items) newItem = MkData schema _ (addToData items)
  where
    addToData : Vect n (SchemaType schema) -> Vect (S n) (SchemaType schema)
    addToData [] = [newItem]
    addToData (x :: xs) = x :: addToData xs

-- Return an item by index if index is within bounds
-- `Maybe (String, DataStore)` to match the type of a caller
getEntry : (pos : Integer) -> (store : DataStore) -> Maybe (String, DataStore)
getEntry pos store = case integerToFin pos (size store) of
    Nothing => Just ("Out of range.\n", store)
    Just id => Just (display (index id (items store)) ++ "\n", store)

Data store

data Command : Schema -> Type where
  New  : (newSchema : Schema) -> Command schema
  Add  : SchemaType schema -> Command schema
  Get  : Integer -> Command schema
  Quit : Command schema

parseCommand : (schema : Schema) -> String -> String -> Maybe (Command schema)
parseCommand schema "add" rest = case parseBySchema schema rest of
  Just val => Just (Add val)
  Nothing  => Nothing
parseCommand schema "get" val  = if all isDigit (unpack val)
  then Just (Get (cast val))
  else Nothing
parseCommand schema "quit" ""  = Just Quit
parseCommand schema "new" rest = case parseSchema (words rest) of
  Nothing => Nothing
  Just newSchema => Just (New newSchema)
parseCommand _ _ _ = Nothing

parse : (schema : Schema) -> String -> Maybe (Command schema)
parse schema input = let (cmd, args) = span (/= ' ') input in
  parseCommand schema cmd (ltrim args)

Data store

parsePrefix : (schema : Schema) -> String -> Maybe (SchemaType schema, String)
parsePrefix SInt input = case span isDigit input of
  ("", rest)  => Nothing
  (num, rest) => Just (cast num, ltrim rest)
parsePrefix SString input = getQuoted (unpack input)
  where
    getQuoted : List Char -> Maybe (String, String)
    getQuoted ('"' :: xs) = case span (/= '"') xs of
      (quoted, '"' :: rest) => Just (pack quoted, ltrim (pack rest))
      _ => Nothing
    getQuoted _ = Nothing
parsePrefix (x .+. y) input = case parsePrefix x input of
  Nothing => Nothing
  Just (val1, input') => case parsePrefix y input' of
    Nothing => Nothing
    Just (val2, input'') => Just ((val1, val2), ltrim input'')

parseBySchema : (schema : Schema) -> String -> Maybe (SchemaType schema)
parseBySchema schema input = case parsePrefix schema input of
  Just (res, "") => Just res -- there should be no leftovers
  Just _  => Nothing
  Nothing => Nothing

Data store

parseSchema : List String -> Maybe Schema
parseSchema ("String" :: xs) = if isNil xs
  then Just SString
  else case parseSchema xs of
    Nothing => Nothing
    Just schema => Just (SString .+. schema)
parseSchema ("Int" :: xs) = if isNil xs
  then Just SInt
  else case parseSchema xs of
    Nothing => Nothing
    Just schema => Just (SInt .+. schema)
parseSchema _ = Nothing

setSchema : DataStore -> Schema -> Maybe DataStore
setSchema store schema = case size store of
  Z => Just (MkData schema _ [])
  _ => Nothing -- do not change schema when the store is not empty

Data store

processInput : DataStore -> String -> Maybe (String, DataStore)
processInput store input = case parse (schema store) input of
  Nothing => Just ("Invalid command.\n", store)
  Just (New schema') => case setSchema store schema' of
    Nothing => Just ("Can't change schema.\n", store)
    Just store' => Just ("OK.\n", store')
  Just (Add item) =>
    Just ("ID " ++ show (size store) ++ ".\n", addToStore store item)
  Just (Get pos)  => getEntry pos store
  Just Quit => Nothing

main : IO ()
main = replWith (MkData SString _ []) "Command: " processInput

What else?

  • Interfaces and Implementations
  • State and State Machines
  • Codata and Streams
  • Effects
  • Linear Types
  • Theorems and Proofs
  • Packages, FFI, interactive mode, etc.

References

Thanks for your attention!

Questions please. :)

Idris for Pedestrians

By Alexander Letov

Idris for Pedestrians

Birdeye's view on Idris.

  • 104