I highly recommend the book. Typing in examples and getting experience collaborating with the compiler on edits is worthwhile. Even if you never use Idris(2) again after reading it, the demos on creating state machine DSLs with contextual invariants will leave a lasting impression.
[1] https://idris2.readthedocs.io/en/latest/typedd/typedd.html
[2] https://github.com/idris-lang/Idris2/tree/master/tests/typed...
It produces/translates to Chez Scheme code.
Wouldn't that be at odds with static typing?
Types are values, and types can depend on values - so if your protocol tells you you should be getting a string, a string shows up in the type signature.
Sounds like it's statically kinded, and dynamically typed. Or is it a place between kinds and types at which Idris operates?
I don't think it makes sense to think of it as specifically "statically kinded", since kinds (which are just types higher on the universe hierarchy) can also be present at runtime.
i'm being pedantic, but i don't think that's always the case. afaik
Type : Type
leads to "inconsistency" (sounds like Russell's paradox, but my knowledge of the theory gets flaky here). so e.g. in Coq there's actually a hierarchy of "universes":> "[The type of Int is Set, ] the type of Set is Type(0), the type of Type(0) is Type(1), the type of Type(1) is Type(2), and so on."
http://adam.chlipala.net/cpdt/html/Universes.html
(though a dependently-typed system may hide universes from the user by default and offer "universe polymorphism" to allow them to gloss over it)
I believe that static typing here means that there is no need for runtime dynamics type checks (outside of things like dynamic dispatch as with haskell typeclasses).
That is you can have a body of code where a variable can assume multiple incompatible types, but you always know that it will be the right one.
I would say it is a change in paradigm. Similarly to how rust' borrow checker and lifetimes change the paradigm of manual vs GC memory management
You are required to disambiguate the type before anything useful can be done. What has happened is that you have types being passed around by functions and evaluations which reflect dynamic behaviour at runtime - but there is a type statically known at all times for each part of the program.
data Vec : Nat -> Type -> Type
Each Vec has its length in its type. You get a type error if you try the following: f : Vec 3 Int -> Int
f [1, 2, 3, 4] -- error: wanted a Vec 3 Int, found a Vec 4 Int
and with that, consider the following program: import Data.Vect
readInts : (n : Nat) -> IO (Vect n Int)
readInts 0 = pure []
readInts (S n) = do
x <- getLine
rest <- readInts n
pure $ (cast x) :: rest
main : IO ()
main = do
length <- getLine
ints <- readInts (fromInteger . cast $ length)
pure ()
The above program doesn't know what the type of `ints` is in the do-block precisely. It has a length specified by the user! But we can reason about its length - and the type system would ensure that its length is propagated and handled properly everywhere we try to use it.