Things that Idris improves things over Haskell
deque.blog
deque.blog
A few examples that I find very intriguing:
1. type safe printf: https://github.com/mukeshtiwari/Idris/blob/master/Printf.idr
2. compile-time evidence that a runtime check will be performed: https://github.com/idris-lang/Idris-dev/blob/master/libs/bas...
3. Most of all, compile-time checking of state machine properties http://docs.idris-lang.org/en/latest/st/introduction.html
Think about being able to specify protocols in the type system and ensuring that your client and server meet the specifications. The example in that link is about user authentication. Imagine having a proof that the program can only get into a "LoggedIn" state by going through the authentication protocol.
[1] https://www.manning.com/books/type-driven-development-with-i...
Edit (links):
OCaml: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.ht...
F#: https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual...
Haskell: https://hackage.haskell.org/package/base-4.9.1.0/docs/Text-P...
Prelude Text.Printf> printf "%d\n" "invalid"
*** Exception: printf: bad formatting char 'd'
The behaviour of F#'s printfn is hard-coded in the compiler and could not be written in F# iteself.Not so, in that Haskell package:
Prelude Text.Printf> printf "%d\n" 1
1
Prelude Text.Printf> printf "%d\n"
*** Exception: printf: argument list ended prematurely
Prelude Text.Printf> printf "%d\n" 0.5
*** Exception: printf: bad formatting char 'd'
Prelude Text.Printf> printf "%d\n" 1 2
1
*** Exception: printf: formatting string ended prematurely
It could certainly be done with TH/QQ. I'm not sure, offhand, if there is already a package to that effect.https://ocaml.org/meetings/ocaml/2013/proposals/formats-as-g...
https://caml.inria.fr/mantis/view.php?id=6017
The compiler only does syntactic desugaring of format strings now.
In Idris, the pattern used to implement this is available to anyone that knows the language. It feels a lot more natural than using a macro system or typeclasses -- it is just functions.
It had a browser based demo.
1+2 * 3 == 9
Sick! I wish Haskell notices this.The Eve core is based on a distributed logic programming semantics (although SQL-like is a good accessible description). It was inspired by a datalog-based language called Dedalus (amongst other things).
Type systems are macro systems, it's just not obvious because type languages are very different from expression languages. The main "innovation" of Idris is hammering harder on this front. And in any case, the fact that most type languages are logic languages gives people with a formalist bent a warm feeling in their stomachs.
I linked it because its implementation is amazing comparatively and it is just one clean example of what is possible.
void Object.printf(String, Object)
and it evaluates types at runtime.In fact, I question most of the value of formal verifications for non critical software. Most of the problematic bugs that make it through in my experience are either a complex combination of multuple parts of the system interacting together in a way that doesn't end up behaving the way it was intended, or programmers implementing the wrong thing on edge cases.
To go beyond "protocols", UI form data to server response back to UI display in a web app is a source of major bugs. So many things are broken. Recently I had to add a spurious lap infant to book a flight due to JavaScript template being broken when rendering 0 children.
I'd love to QuickCheck the diapers out of this method.
Re: formal verification in general, most of the research has been moving towards making it easier to use and giving programmers some of the benefits of verification for free. I don't have any problem with the compiler telling me that my code is going to crash before the crash happens in production. That's unquestionably a valuable thing. Idris (and other languages) are trying to give you those sorts of benefits without reducing productivity or breaking your brain. It might not be there yet, but the progress is exciting.
Finally, both of the problems you identified (complex interactions and edge cases) are things that formal techniques are well suited to address... so I'm not sure how they support your case. Again, the only problem seems to be that the cost outweighs the benefits. But the cost is decreasing quickly.
The idea of a protocol in this sense encompasses pretty much any communication between things. This includes web service calls, user input via a UI, transactions between multiple systems, or even modules within the same program.
Also, if you have an arbitrary string that isn't a constant that can be analyzed statically — for example, a string read from a config file — how do you prove to the compiler that it's a valid format-code string?
it's just showing if you get through these 50 checks, by the time you get to printf the bytes will have been converted to what you need, or the program will have already errored out.
Anyway, is that really true? "%-1s" should probably give you an error, for example.
Sounds like you are describing Session Types!
(http://groups.inf.ed.ac.uk/abcd/) (http://summerschool2016.behavioural-types.eu/programme/Dardh...)
The blog post's real title ("10 things Idris improved over Haskell") is better; unless that's a problem due to being a list (which are often spammy). Seems fine/serious to me, though.
There are usually more than one way to convert from one type to another. How is Float to Int rounded? Shouldn't String to Int return conversion errors? Just looking at `cast` means I have to learn what the language decided to default to.
Looking at the interfaces tutorial[0], it gives an example of something similar:
readNumber : IO (Maybe Nat)
readNumber = do
input <- getLine
if all isDigit (unpack input)
then pure (Just (cast input))
else pure Nothing
[0] http://docs.idris-lang.org/en/latest/tutorial/interfaces.htm... > :t read
read :: Read a => String -> a
> read "10" :: Int
10
> read "x" :: Int
*** Exception: Prelude.read: no parse
Haskell functions are pure, but not necessarily total. cast : Cast from to => from -> to
So if the types `from` and `to` are part of the `Cast` typeclass, then cast will always convert one into the other, without "failing". The standard library defines a handful of "sensible" casts like Int to Float, but you can't even attempt to cast a function to a String, or a List to an Int, since those aren't part of the `Cast` typeclass.For an operation that can fail, like a parse method, a more appropriate return type is `Maybe Int`, or `Either String Int` (where the `String` is an error message).
Also Idris provides a dependently-typed way to connect the check (`all isDigit (unpack input)`) to the conversion itself (`cast input`), so that you can't write an invalid check or forget to include the check at all. See my reply to vosper above.
`cast` is a function `String -> Integer`, instead of the (more sensible) `String -> Either Integer CastError`.
For that specific case, you could use an Idris "view" over the string that either gives you a proof that the string is numeric, or a proof that it isn't. Then you have a "stringToInt" method that requires such a proof as one of its parameters. This allows you to write the parsing code as efficiently as possible since you know you'll only work with valid inputs. It's now a compile-time error to forget to check if a string is numeric before attempting to parse it. Also once you obtain a proof of a certain property (in this case by running a runtime check, elsewhere by proving it statically), you can avoid duplicate checks by just passing around the proof object. And of course these proofs are erased at compile time, so the run time code will be similar to an ordinary 'int.TryParse()' method in c# or java, except without the ergonomic issues of using 'out' references or the possibility of accidentally using an uninitialized variable.
are you certain about that? my understanding was that witness terms are indeed passed at runtime especially for non-total functions
https://developer.apple.com/library/content/documentation/Co...
It's probably one of the better approaches - but it's still not clear if it (alone) allows a developer that speaks only English to develop a text indexing or editing system that works well across English, Japanese, Arabic, Hangul and Dutch for example.
Or equivalently: there is more than one way to turn a string into a list. It can e.g. be a sequence of bytes, unicode chars or grapheme clusters. Being explicit about the conversion is therefore a good idea.
And you still do that with atomic strings -- you just either add a helper method like charAt(i) that gives you access to each character (or, rather, each rune), or you have some way to turn a string of length N into a list of N strings of length one.
Correctness: you often don't want to operate on individual Unicode scalar values. Extended grapheme clusters can combine multiple scalar values to form a single human-readable character, and that's usually the unit you care about. Representing a string directly as a list of extended grapheme clusters would use even more memory.
Fundamentally, a string has more structure than a list representation gives you (encoded bytes vs. scalar values vs. grapheme clusters). I think it's better to expose this structure than it is to pretend a string is just a list of characters.
No free lunches!
UTF-8 is one to four bytes, UTF-16 is two or four bytes, and UTF-32 is always four bytes. For some code points, UTF-8 is 50% longer than UTF-16 (3 vs 2), but UTF-8 is never longer than UTF-32.
List of characters works fine for the string 'Hello, world!'. It doesn't work fine for the string representing, for example, a whole webpage that you're returning, of for the string that you need to pass to some external code e.g. a regex engine implemented in C (which requires to transform it to a memory-contiguous array of chars, and then transform the results back), or for a 100 megabyte plaintext/xml/json/csv/whatever file you're processing.
One thing that I find strange, though, is that some of the most prolific Scala developers who are critical of the language seem to stick to languages such as Haskell/Eta and PureScript. Maybe it's the immaturity of the Idris ecosystem.
Post-Scala is already under way with the new compiler, Dotty[1], which will replace present day Scala.
You'll have to admit though: the greatest strength and weakness of Scala is Java.
Another cool feature of Idris is elaborator reflection https://www.youtube.com/watch?v=pqFgYCdiYz4 which I believe has no direct Haskell analogue (template Haskell perhaps?)
https://www.functionalgeekery.com/
There's at least one episode that's devoted to Idris:
I hear "dependent types" and I think "theorem prover", but it seems like Idris is a cleaned up Haskell with some light proving features built in?
Haskell is good for compilers and parsers. What's a good excuse to try Idris?
It's a full-strength dependent type theory, but is intended primarily for use as a programming language. So the design focus is on using full dependent types to make ordinary programming easier.
> What's a good excuse to try Idris?
The best excuse of all: it is super fun.
While true, the same applies to any other language in the ML family.
I think one of the biggest advantages of dependent typing is that it lets you implement a lot of things that other languages need to add as ad-hoc language features. A good example (if you're familiar with Haskell) is typeclasses. These can be done entirely at the value level in Idris, and IIRC interfaces are just syntactic sugar over this implementation. Other examples are generics that depend on values instead of types (without resorting to tedious things like type-level integers) and cleaner replacements for a lot of macro functionality.
[1]: http://docs.idris-lang.org/en/latest/tutorial/interp.html
import qualified Data.ByteString as BS
import qualified Data.Text.Lazy as TL
import qualified Data.Text as TS
and somehow the library you need always use a different ByteString variant from the one you already chose so you have to pack/unpack here and there.
There should be a way to make `length`, `map` and all work on every string type. Maybe a type class or some better idea is needed.By the way the link to the caesar cipher is broken.
A list of ASCI chars obviously does (the length of the list), but I'm not sure about Data.Text.Text.
https://hackage.haskell.org/package/text-1.2.2.2/docs/Data-T...
https://hackage.haskell.org/package/bytestring-0.10.8.1/docs...
If it's just counting how many u8's in a slice, that's trivial. But storing the total number of variable-length graphemes isn't. I assume that's why Data.Text's length function is O(n), and other languages that have UTF-16 or UTF-8 strings also have length functions that are linear.
This in theory. It is readily apparent from the Data.Text page at https://hackage.haskell.org/package/text-1.2.2.2/docs/Data-T... that the authors care about performance only selectively ("fusion" of buffer allocations is fun, relatively messy data structures to cache important data such as string length are not fun), and that they don't care enough about Unicode to add serious string-level abstractions over the Unicode tables exposed in the Char type.
Data.Text seems to recognize symbols made of several codepoints (like emoji) as one but still counts diacritics and combining characters as different symbols.
Or are you suggesting that we specialize the TCP receive function to only receive UTF-8 strings, so we don't have to convert when displaying to the user?
See: http://blog.ezyang.com/2016/09/the-base-of-a-string-theory-f...
length :: forall f a n. Foldable f => Semiring n => (f a) -> n
`length` is not specific to any one type and it's a complete waste to think of it as something to do with strings in general. They should all just be foldable and that'll give way more.Likewise, they could all just be functors and `map` is free.
What happens if you feel the urge to define several Semirings (or Monoids) on the data type?
An extremely heavily-commented approach to defining equality for things like Double (where there are multiple sensible ways to compare things) will probably be a better introduction to these ideas:
https://github.com/mrkgnao/noether/blob/master/library/Noeth...
https://github.com/mrkgnao/noether/blob/master/library/Noeth...
The rest of the repo implements abstract algebraic structures along similar lines.
Here is what monoids look like in this formalism at present. It's a bit involved: first I use a fine-grained separation into highly polymorphic Magma and Neutral classes:
https://github.com/mrkgnao/noether/blob/master/library/Noeth...
And there are "strategies" for combining those pieces of structure, whose use looks like
type instance MonoidS (op :: BinaryNumeric) Rational
= DeriveMonoid_Semigroup_Neutral op Rational
Which means "derive the preferred monoid structure with operation op" (MonoidS op) on Rational from the preferred semigroup structure and preferred neutral element on it with that operation.https://github.com/mrkgnao/noether/blob/master/library/Noeth...
You can always use some other monoid structure for the same combination of type and operation (!) if you like. For instance, here's a demo of using the self-action of a ring on itself (i.e. considering a ring as a module over itself) to compute something, even if your preferred choice of action has been declared to be something else (or hasn't been defined at all).
https://github.com/mrkgnao/noether/blob/master/library/Noeth...
This says "here, use the self-action derived from the multiplicative magma structure on R".
For the second question, traditional (+,×,...) is a Semiring for integers; so is (&,|,...) (bitwise boolean operations). How can you define both Semiring implementations without getting them confused?
There is another reply to my comment that I haven't digested yet. Maybe it answers the second.
And I'm wrong about that. You need a zero and a one, right. What would that structure be?
F* is based on type refinements and smt solver.
in theory all DT are equivalent but type refinements are less expressive in practice than CoC.
sometimes the smt solver gives up and you are in trouble.
alternatively the smt solver can provide simple refinements with less effort than CoC when it does work.
Why not have an Iterable/Enumerable typeclass/trait/interface which is implemented by each dataset that is listly? Seems a lot more efficient and easier to understand then having to convert between representations just to iterate or change elements.
data Human = Human {name :: String}
data Dog = Dog {name :: String}
is illegal, because they can't both have a field accessor called `name`.That's... crazy.
Pragmatically, it's really annoying. There's a solution in the space between pure (a la Haskell) and magical (a la Scala) that makes sense. I think Idris might have found it.
Any downsides (in the core language) besides the smaller community?
Any chances for Haskell to get some of the same things?
In general the Idris type inference is not nearly as good as Haskells. I don't mean this in the "dependent type inference is undecidable" sense, but instead just generally.
Idris is also strict instead of lazy like Haskell. This is good or bad depending on who you ask. Very unlikely for it to change in Haskell though.
Many of the other issues are the issues with every small language. Community size and libraries.
Do you know if this is just because Idris is a much less mature language than Haskell, or its it something fundamental about the design?
[1]
caesar_cipher : Int -> String -> String
caesar_cipher shift input =
let cipher = chr . (+ shift) . ord
in pack $ map cipher (unpack input)
[2] caesar_cipher : Int -> String -> String
caesar_cipher shift input =
let cipher = chr . (+ shift) . ord
in pack $ map cipher $ unpack input
To me the second one seems more readable. Are there semantic differences? Performance differences?'Interfaces in Idris are similar to Haskell type classes, but with support for named overlapping instances. This means that we can give multiple different interpretations to an interface in different implementations, and in this sense they are similar in power to ML modules (Dreyer 2005). Interfaces in Idris are 1st class, meaning that implementations can be calculated by a function, and thus provide the same power as 1st-class modules in 1ML (Rossberg 2015).'
Quoting from https://www.idris-lang.org/drafts/sms.pdf
The author is using it to take advantage of syntactic sugar that is now reliant only on names and types, instead of implementation of the relevant interface.
A similar idea is to use `::` and `Nil` as constructors, which let you use the `[a, b, c]` list syntax (desugared to `a :: b :: c :: Nil`) for things that aren't actually lists. It can be convenient, but can also result in confusion.
The simplest form of tractable recursion is where some argument in the recursive call is, by some metric, "smaller" than the outer call, and is heading towards a base case that terminates recursion when the argument reaches "zero." Two examples are: decrementing integers towards zero, and recursing on the tail of a list. You can prove termination and many other properties of such a function by induction. This concept is fairly easy to generalize to any algebraic data type, and to many other domains.
Edit: I realized I didn't really answer your question. The folks who are working on dependently typed languages (like Idris) see them as broadly useful, and they have a good point. Consider Java generics: they let you have types that depend on other types, like a "List of Integers." The compiler can make guarantees based on that type information and a lot of people consider that useful. With dependent types you can have types that depend on _values_, like "List of exactly 5 Integers that are all between 0 and 100." Again, the compiler can make guarantees based on that type information, and it could improve code safety, modularity, etc.
The challenge with dependently typed languages has been coming up with a "surface syntax" that's easy to use. Idris (and Agda) are pretty much state of the art there.
As to why it's disallowed: stack space is limited, and tail call optimisation isn't taken for granted. Here's what the Joint Strike Fighter coding standards (thataway -> http://www.stroustrup.com/JSF-AV-rules.pdf) say:
AV Rule 119 (MISRA Rule 70)
Functions shall not call themselves, either directly or indirectly
(i.e. recursion shall not be allowed).
Rationale: Since stack space is not unlimited, stack overflows are possible.
Exception: Recursion will be permitted under the following circumstances:
1. development of SEAL 3 or general purpose software, or
2. it can be proven that adequate resources exist to support the maximum level of
recursion possible.
So yes, you're allowed it if you do that extra work, but given that you can replace tractable recursion with a loop anyway, the win you'd have to get from expressing the problem recursively has to compensate.In theory a compiler can do this optimization for you and can use simple syntactic checks to determine whether recursion terminates and whether the optimization applies (at least given a language designed with these things in mind, I'm sure this is much harder or maybe impossible in C++). With something more modern than C++ I'd imagine you could adopt a rule that says "you can use recursion but we compile with --dont-allow-nonterminating-or-unoptimizable-recursion".
Practically, certain operations end up being slow when you use the standard "strings are lists" mechanism provided by Haskell's standard library. Stuff like building a list is hard or slow for stupid reasons: Haskell uses cons lists with fast append-to-head and slow concatenation, so the common case of building a list by appending characters to the end is slow. Or you have to prepend and reverse. Stuff like that. It's dumb, but it matters.
The Haskell community has reacted by creating other ways to represent strings, like `Data.Text` and `ByteString`. These representations have certain benefits, so they're widely used. This adds _another_ problem: different libraries use different representations, so you end up having to convert back and forth between them all the time. Again, this is annoying and inefficient.
So yea. I think the lesson for language designers is clear. Strings are a distinct concept. Yes, they do list-like things, but they're not lists.
I love it when that happens.
Can Idris turn Haskell runtime errors into compile-time errors and, if so, which ones?
[1] - https://github.com/typelevel/cats/blob/master/core/src/main/...
Here's a more advanced example: https://news.ycombinator.com/item?id=14569605.
In idris, you can lift the non-empty list check at type level, making such operation a compilation error.
https://tutorial.ponylang.org/capabilities/reference-capabil...
https://tutorial.ponylang.org/appendices/garbage-collection....