I'm curious about the objections to implicit parameters discussed in the Functional Pearl: Implicit Configurations article by Kiselyov and Shan.
It is not sound to inline code (β-reduce) in the presence of implicit parameters.
Really? I'd expect that GHC should inline into the same scope as the passed implicit parameter, no?
I believe I understand their objection that :
A term’s behavior can change if its signature is added, removed, or changed.
GHC's user documentation explains that programers must take care around polymorphic recursion and monomorphism restriction. Is this somehow what they mean by a problem for inlining?
I presume this polymorphic recursion example covers what they mean by "generalizing over implicit parameters" as well? Anything else?
Is the ReifiesStorable type class from Data.Reflection really a sensible solution to these difficulties? It seemingly deserializes the entire implicit data structure every time it's accessed, which sounds be disastrous for performance. We might for-example want our implicit information to be Cayley table or character table that occupies a gig of ram and must be access during millions of algebraic operations.
Is there perhaps some better solution that employs implicit parameters, or another technique the compiler can easily optimize, behind the scenes, while still guaranteeing more via the type system using state threads or whatever?
Yes, the example from the GHC manual shows how adding a type signature can change the semantics of code with implicit parameters, and I believe this is what they mean by breaking inlining; inlining an application of len_acc1 vs. an application of len_acc2 produces the same code, despite the two having different semantics.
As far as generalising over implicit parameters, it means that you can't write a function that can operate on multiple implicit parameters; there is no mechanism to abstract over them, as the implicit parameter a function uses is fixed by its type. With reflection, you can easily write a function like doSomethingWith :: (Reifies s a, Num a) => Proxy s -> a, which can operate over any type that reifies a numeric value.
As for ReifiesStorable, you're looking at an old version of the reflection package; the latest version has a very efficient implementation in which reify only costs as much as a function call.1 Note that, even with the old implementation, you would generally not use the ReifiesStorable class directly, but instead Reifies, which uses ReifiesStorable to reify a StablePtr, so only a few bytes end up being copied, not the entire object. (This is what the original implementation in the paper does, too.) Both implementations are definitely fast enough for practical use, with the old, "slow" implementation taking about 100 ms to reify and reflect 100000 values, and the new implementation under 10 ms.
(Full disclosure: I worked on the new implementation.)
1 The fast implementation depends on Haskell implementation details. The older, slower implementation is automatically used for Haskell implementations that the fast implementation has not yet been tested with; so far, GHC and Hugs have been shown to work with the fast implementation. You can request the slow implementation with -fslow, but it's unlikely to stop working unless GHC significantly overhauls its implementation of typeclasses. (Even if it does, you'd only have to recompile the packages that use reflection to get it working again.)
Related
In Matthew Pickering's blog post about inlining, he mentions a trick to force GHC to inline recursive definitions applied to a statically known argument:
However, there is a trick that can be used in order to tell GHC that an argument is truly static. We replace the value argument with a type argument. Then by defining suitable type class instances, we can recurse on the structure of the type as we would on a normal value. This time however, GHC will happily inline each “recursive” call as each call to sum is at a different type.
Then the blog post proceeds to show a very simplistic example using type-level Nats. My question is about forcing GHC to fully inline functions over actually interesting types.
In my case, I have a computation that is via a monadic DSL. Under the hood, the DSL is an RWS. Looking at the generated Core with lots of INLINE pragmas, I can see calls to the recursive functions that implement <> for the writer output. Since these functions are recursive, GHC doesn't inline them. But if it did, since the full RWS computation is static enough that the spine of the writer output could be fully unrolled during compilation, it would, after finitely many steps, arrive at Core with no recursive definitions left.
Taking the idea from Matt's blog post, we would need to have a type-level representation of the monadic value; I'd love to see a version of this (especially with good ergonomics!), but I am not too optimistic: unlike the Servant approach, we would need a way to bind variables to results (a la monadic >>=). Alternatively, are there any other mechanisms to tell GHC that its input size is statically known, so it shouldn't shy away from aggressively inlining recrusive definitions? For example, is it possible to emulate Agda-style sized types by using a graded monad with some appropriate size measure in the index?
P.s.: My aim is not mere optimization, but working around a Clash bug, so this elimination of recursive definitions by aggressive inlining would need to be something dependable, not a nice-to-have performance improvement.
I have been playing around with LiquidHaskell and Idris lately and i got a pretty specific question, that i could not find a definitive answer anywhere.
Idris is a dependently typed language which is great for the most part. However i read that some type terms during type checking can "leak" from compile-time to run-time, even tough Idris does its best to eliminate those terms (this is a special feature even..). However, this elimination is not perfect and sometimes it does happen. If, why and when this happens is not immediately clear from the code and sometimes has an effect on run-time performance.
I have seen people prefering Haskells' type system, because it cannot happen there. When type checking is done, it is done. Types are "thrown away" and not used during run-time.
What is the story for LiquidHaskell? It enhances the type system capabilities quite a bit over traditional Haskell. Does LiquidHaskell also inject run-time bits for certain type "constellations" or (as i suspect) just adds another layer of "better" types over Haskell that do not affect run-time in any shape or form.
Meaning, if one removes the special LiquidHaskell type annotations and compiles it with standard GHC, is the code produced always the same? In other words: Is the LiquidHaskell extension compile-time only?
If yes, this seems like the best of both worlds, or is LiquidHaskell just not as expressive in the type system as Idris and therefore manages without run-time terms?
To answer your question as asked: Liquid Haskell allows you to provide annotations that a separate tool from the compiler verifies. Code is still compiled exactly the same way.
However, I have quibbles with your question as asked. It can be argued that there are cases in which some residue of the type must survive at run time in Haskell - especially when polymorphic recursion is involved. Consider this function:
lots :: Show a => Int -> a -> String
lots 0 x = show x
lots n x = lots (n-1) (x,x)
There is no way to statically determine the exact type involved in the use of show. Something derived from the types must survive to runtime. In practice, this is easy to do by using type class dictionaries. The theoretically important detail is that there's still type-directed behavior being chosen at runtime.
In the Haskell community, we are slowly adding features of dependent types. Dependent types is an advanced typing feature by which types can depend on values. Some languages like Agda and Idris already have them. It appears to be a very advanced feature requiring an advanced type system, until you realize that python has had dependent types has had the dynamic typing version of dependent types, which may or may not be actual dependent types, from the beginning.
For most any program in a functional programming language, there is a way to reperesent it as an untyped lambda calculus term, no matter how advanced the typing. That's because typing only eliminates programs, not enable new ones.
Strong Typing wins us safety. How classes of errors that happened at run time can no longer happen at run time. This safety is rather nice. Besides this safety though, what does strong typing give you?
Are there an additional benefits of a strong type system besides safety?
(Note that I'm not saying that strong typing is worthless. Safety is a huge benefit in and of itself. I'm just wondering if there are additional benefits.)
First, we need to talk a bit about the history of the simply typed lambda calculus.
There are two historical developments of the simply typed lambda calculus.
When Alonzo Church described the lambda calculus the types were baked in as part of the meaning / operational behavior of the terms.
When Haskell Curry described the lambda calculus the types were annotations put on the terms.
So we have the lambda calculus a la Church and the lambda calculus a la Curry. See https://en.wikipedia.org/wiki/Simply_typed_lambda_calculus#Intrinsic_vs._extrinsic_interpretations for more.
Ironically, the language Haskell, which is named after Curry is based on a lambda calculus a la Church!
What this means is the types aren't simply annotations that rule out bad programs for you. They can "do stuff" too. Such types don't erase without leaving residue.
This shows up in Haskell's notion of type classes, which are really why Haskell is a language a la Church.
In Haskell, when I make a function
sort :: Ord a => [a] -> [a]
We're passing an object or dictionary for Ord a as the first argument.
But you aren't forced to plumb that argument around yourself in the code, it is the job of the compiler to build that up and use it.
instance Ord Char
instance Ord Int
instance Ord a => Ord [a]
So if you go and use sort on a list of strings, which are themselves lists of chars, then this will build up the dictionary by passing the Ord Char instance through the instance for Ord a => Ord [a] to get Ord [Char], which is the same as Ord String, then you can sort a list of strings.
Calling sort above, is a lot less verbose than manually building a LexicographicComparator<List<Char>> by passing it an IComparator<Char> to its constructor and calling the function with an extra second argument, if I were to compare the complexity of calling such a sort function in Haskell to calling it in C# or Java.
This shows us that programming with types can be significantly less verbose, because mechanisms like implicits and typeclasses can infer a large part of the code for your program during type checking.
On a simpler basis, even the sizes of arguments can depend on types, unless you want to pay fairly massive costs for boxing everything in your language up so that it has a homogeneous representation.
This shows us that programming with types can be significantly more efficient, because it can use dedicated representations, rather than paying for boxed structures everywhere in your code. An int can't just be a machine integer, because it has to somehow look like everything else in the system. If you're willing to give up an order of magnitude or more worth of performance at runtime, then this may not matter to you.
Finally, once we have types "doing stuff" for us, it is often beneficial to consider the refactoring benefits that mere safety provides.
If I refactor the smaller set of code that remains, it'll rewrite all that type-class plumbing for me. It'll figure out the new ways it can rewrite the code to unbox more arguments. I'm not stuck elaborating all of this stuff by hand, I can leave these mundane tasks to the type-checker.
But even when I do change the types, I can move arguments around fairly willy-nilly, comfortable that the compiler will very likely catch my errors. Types give you "free theorems" which are like unit tests for whole classes of such errors.
On the other hand, once I lock down an API in a language like Python I'm deathly afraid of changing it, because it'll silently break at runtime for all my downstream dependencies! This leads to baroque APIs that lean heavily on easily bit-rotted keyword-arguments, and the API of something that evolves over time rarely resembles what you'd build out of the box if you had it to do over again. Consequently, even the mere safety concern has long-term impact in API design once you ever want people to build on top of your work, rather than simply replace it when it gets too unwieldy.
That's because typing only eliminates programs, not enable new ones.
This is not a correct statement. Type-classes make it possible to generate parts of your program from type-level information.
Consider two expressions:
readMaybe "15" :: Maybe Integer
readMaybe "15" :: Maybe Bool
Here I'm using the readMaybe function from the Text.Read module. At term level those expressions are identical, only their type annotations are different. However, the results they produce at runtime differ (Just 15 in the first case, Nothing in the second case).
This is because the compiler generates code for you from the static type information you have. To be more precise, it selects a suitable type class instance and passes its dictionary to the polymorphic function (readMaybe in our case).
This example is simple, but there are way more complex use cases. Using the mtl library you can write computations that run in different computational contexts (aka Monads). The compiler will automatically insert a lot of code that manages the computational contexts. In a dynamically typed language, you would have no static information to make this possible.
As you can see, static typing not only cuts off incorrect programs but also writes correct ones for you.
You need "safety" when you already know what and how you want to write. It's a very small part of what types are useful for. The most important thing about types is that they make your reasoning structured. When someone writes in Python a + b he doesn't see a and b as some abstract variables — he sees them as some numbers. Types are already there in the internal language of humans, Python just doesn't have a type system to talk about them. The actual question in the "typed vs untyped (unityped) programming" dispute is "do we want to reflect our internal structured concepts in a safe and explicit or unsafe and implicit way?". Types don't introduce new concepts — it's untyped reasoning forgets the existing ones.
When someone looks at a tree (I mean a real green one) he doesn't see every single leaf on it, but he doesn't treat it as an abstract nameless object as well. "A tree" — is an approximation that is good enough for most cases and that's why we have Hindley-Milner type systems, but sometimes you want to talk about a specific tree and you do want to look at leaves. And that's what dependent types give you: the ability to zoom. "A tree without leaves", "a tree in the forest", "a tree of a particular form"... Dependently typed programming is just another step towards how humans think.
On a less abstract note, I have a type checker for a toy dependently typed language, where all typing rules are expressed as constructors of a data type. You don't need to dive into the type checking procedure to understand the rules of the system. That's the power of "zooming": you can introduce as complex invariants as you want, thus distinguishing essential parts from not important ones.
Another example of the power dependent types give you is various forms of reflection. Look e.g. at the Pierre-Évariste Dagand thesis, which proves that
generic programming is just programming
And of course types are hints, many functions and abstractions I defined I would define in a far more clumsy way in a weakly typed language, but types suggested better alternatives.
There is just no question "What to choose: simple types or dependent types?". Dependent types are always better and they of course subsume simple types. The question is "What to choose: no types or dependent types?", but that question doesn't stand for me.
Refactoring. By having a strong type system you can safely refactor code and have the compiler tell you whether what you are doing now even makes sense. The stronger the typing system, the more refactor errors are avoided. This of course means your code is a lot more maintainable.
When trying to learn Haskell, one of the difficulties that arise is the ability when something requires special magic from the compiler. One exemple that comes in mind is the seq function which can't be defined i.e. you can't make a seq2 function behaving exactly as the built-in seq. Consequently, when teaching someone about seq, you need to mention that seq is special because it's a special symbol for the compiler.
Another example would be the do-notation which only works with instances of the Monad class.
Sometimes, it's not always obvious. For instance, continuations. Does the compiler knows about Control.Monad.Cont or is it plain old Haskell that you could have invented yourself? In this case, I think nothing special is required from the compiler even if continuations are a very strange kind of beast.
Language extensions set aside, what other compiler magic Haskell learners should be aware of?
Nearly all the ghc primitives that cannot be implemented in userland are in the ghc-prim package. (it even has a module called GHC.Magic there!)
So browsing it will give a good sense.
Note that you should not use this module in userland code unless you know exactly what you are doing. Most of the usable stuff from it is exported in downstream modules in base, sometimes in modified form. Those downstream locations and APIs are considered more stable, while ghc-prim makes no guarantees as to how it will act from version to version.
The GHC-specific stuff is reexported in GHC.Exts, but plenty of other things go into the Prelude (such as basic data types, as well as seq) or the concurrency libraries, etc.
Polymorphic seq is definitely magic. You can implement seq for any specific type, but only the compiler can implement one function for all possible types [and avoid optimising it away even though it looks no-op].
Obviously the entire IO monad is deeply magic, as is everything to with concurrency and parallelism (par, forkIO, MVar), mutable storage, exception throwing and catching, querying the garbage collector and run-time stats, etc.
The IO monad can be considered a special case of the ST monad, which is also magic. (It allows truly mutable storage, which requires low-level stuff.)
The State monad, on the other hand, is completely ordinary user-level code that anybody can write. So is the Cont monad. So are the various exception / error monads.
Anything to do with syntax (do-blocks, list comprehensions) is hard-wired into the language definition. (Note, though, that some of these respond to LANGUAGE RebindableSyntax, which lets you change what functions it binds to.) Also the deriving stuff; the compiler "knows about" a handful of special classes and how to auto-generate instances for them. Deriving for newtype works for any class though. (It's just copying an instance from one type to another identical copy of that type.)
Arrays are hard-wired. Much like every other programming language.
All of the foreign function interface is clearly hard-wired.
STM can be implemented in user code (I've done it), but it's currently hard-wired. (I imagine this gives a significant performance benefit. I haven't tried actually measuring it.) But, conceptually, that's just an optimisation; you can implement it using the existing lower-level concurrency primitives.
Does Haskell erase types, and if so, in what ways is this similar/dissimilar to the type erasure that occurs in Java?
Warning: experience+inference. Consult someone who works on both compilers for The Truth.
In the sense that type checking is done at compile time, and several complex features of the type system are reduced to much simpler language constructs, yes, but in a rather different way to Java.
A type signature creates no runtime overhead. The Haskell compiler is good at program transformation (it has more leeway, because the running order is in many cases not specified by the programmer), and automatically inlines appropriate definitions and specialises haskell-polymorhpic (=java-generic) functions to a particular type etc, as it sees fit, if it helps. That's a similar to Java type erasure, but more-so aspect.
There are in essence no type casts needed in Haskell to ensure type safety, because Haskell is designed to be type-safe from the ground up. We don't resort to turning everything into an Object, and we don't cast them back, because a polymorphic(generic) function genuinely does work on any data type, no matter what, pointer types or unboxed integers, it just works, without trickery. So unlike Java, casting is not a feature of compiling polymorphic(generic) code. Haskell folk tend to feel that if you're doing type casting, you said goodbye to type safety anyway.
For a lovely example of how ensuring the code's static type-correctness at compile time can avoid runtime overhead, there's a newtype construct in Haskell which is a type-safe wrapper for an existing type, and it's completely compiled away - all the construction and destruction simply doesn't happen at runtime. The type system ensures at compile time it's used correctly, it can't be got at at runtime except using (type-checked) accessor functions.
Polymorphic(generic) functions don't have polymorphic overheads. Haskell-overloaded functions (Java-interface-instance methods) have a data overhead in the sense that there's an implicit dictionary of functions used for what appears to be late binding to Java programmers, but is in fact, again, determined at compile time.
Summary: yes, even more so than in Java, and no, they were never there at runtime to erase anyway.
C and Pascal have type erasure. Java lets you inspect classes at run-time - even dynamically loaded ones!
What Haskell does is much closer to Pascal than to Java.