Related
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
I'm curious about the relationship between static typing and lazy functional languages. Is it possible to have a dynamic lazy functional language, for instance? It seems as if all of the lazy functional languages out there are statically typed (Haskell, Miranda, etc), and all of the dynamic functional languages use strict evaluation (Clojure, Scheme, etc).
In particular, the Wikipedia article on Lazy evaluation reads:
However, with lazy evaluation, it is difficult to combine with
imperative features such as exception handling and input/output,
because the order of operations becomes indeterminate. Lazy evaluation
can introduce space leaks.
What is the role that static typing plays in preventing space leaks?
I don't believe that static types play a role at all. For example, consider the untyped lazy language, Lazy Racket. I haven't heard any indication that it leaks space in a way that Haskell (for example) does not.
Side effects, on the other hand, are a problem because humans find the order of evaluation of strict evaluation to be (relatively) natural, and call-by-need is much harder to mentally predict.
What is the role that static typing plays in preventing space leaks?
Types can be used to track the lifetime of objects, statically ensuring the absence of leaks.
An example would be region types and other effect types.
Lazy evaluation and static typing are independent concepts.
Untyped
Lazy evaluation The untyped lambda calculus with normal order reduction strategy (or call by need). Always the leftmost redex is evaluated first.
Eager (strict) evaluation The untype lambda calculus with applicative order reduction strategy. Lambda terms are always reduced before they're substituted to other terms.
Typed
Lazy evaluation Haskell is an example.
Eager evaluation OCaml is an example.
Roughly speaking, evaluation is something that happens when the program is run.
Typing is something that happens when the program is compiled.
But of course there is an important correspondence between a typing system and an evaluation strategy:
If a term M reduces to N and M:σ (M is of type σ) then N:σ.
This means that when we run a program that has some type σ then the value
will have the same type. Without this property, a typing system is really
useless (at least for programming). This also means that once we have typed a
program during compilation, we don't need to remember the typing information
when evaluating it, because we know that the result will have the correct type.
Concerning the Wikipedia article you're quoting. There are two different things:
Imperative features. This isn't really related to a typing system. The problem is that if evaluating certain expressions has side effects (like I/O in most languages) in lazy settings it's very hard to predict when (if at all) a side effect occurs. Therefore once you have lazy evaluation, it's hardly possible to have an impure language.
One exception to this is the Clean language. It uses a special type system to handle side effects in a lazy setting. So here there is some connection between the evaluation strategy and the typing system through handling side effects: The type system allows handling side effects in such a way that we can keep lazy evaluation.
Space leaks. This is a known drawback of lazy evaluation. See Building up unevaluated expressions or Ch. 25 Profiling and optimization in Real World Haskell. But again this has nothing to do with type systems - you'd get the same behavior in an untyped language.
I think if you look at things from a more general level, it is possible to observe a natural relationship between static typing and lazy functional languages. The main point of static types is to inform and advance the capabilities of the compiler; surveying the static-dynamic divide among languages, it generally tracks the schism between compiled and interpreted code.
And what is the point of lazy evaluation?
An infamous, retrospective article by Peyton-Jones et al describes lazy evaluation as "the hair shirt" which kept the language purely functional. His metaphor aptly conveys the Haskell community's deep-rooted idealism of denotational semantics. Non-strict evaluation's fundamental benefit is that it transforms the possibilities for structuring code in ways that facilitate this denotational paradigm. In the notorious lazy evaluation debate carried on by Bob Harper and the Haskell community, Prof. Harper demonstrates the challenges lazy evaluation poses for practical programs - and among Lennart Augustsson's defenses of laziness, this one best illustrates the point:
"I've saved my biggest gripe of strict evaluation for last. Strict evaluation is fundamentally flawed for function reuse. [...] With strict evaluation you can no longer with a straight face tell people: don't use recursion, reuse the recursion patterns in map, filter, foldr, etc. It simply doesn't work (in general). [...] strict evaluation really, fundamentally stops you from reusing functions the way you can with laziness."
And for his example of function reuse via lazy evaluation, Augustsson offers, "It's quite natural to express the any function by reusing the map and or functions." So lazy evaluation emerges from this picture as a rather costly language feature embraced in service of a more natural style of coding.
What else do we need to sustain an abstract, denotational style of coding? A powerful optimizing compiler might come in handy! Thus, even if there is no technical or necessary connection between static types and lazy evaluation, the two features are oriented toward the same goal. It's not so surprising they often appear together.
I am writing program that does alot of table lookups. As such, I was perusing the Haskell documentation when I stumbled upon Data.Map (of course), but also Data.HashMap and Data.Hashtable. I am no expert on hashing algorithms and after inspecting the packages they all seem really similar. As such I was wondering:
1: what are the major differences, if any?
2: Which would be the most performant with a high volume of lookups on maps/tables of ~4000 key-value pairs?
1: What are the major differences, if any?
Data.Map.Map is a balanced binary tree internally, so its time complexity for lookups is O(log n). I believe it's a "persistent" data structure, meaning it's implemented such that mutative operations yield a new copy with only the relevant parts of the structure updated.
Data.HashMap.Map is a Data.IntMap.IntMap internally, which in turn is implemented as Patricia tree; its time complexity for lookups is O(min(n, W)) where W is the number of bits in an integer. It is also "persistent.". New versions (>= 0.2) use hash array mapped tries. According to the documentation: "Many operations have a average-case complexity of O(log n). The implementation uses a large base (i.e. 16) so in practice these operations are constant time."
Data.HashTable.HashTable is an actual hash table, with time complexity O(1) for lookups. However, it is a mutable data structure -- operations are done in-place -- so you're stuck in the IO monad if you want to use it.
2: Which would be the most performant with a high volume of lookups on maps/tables of ~4000 key-value pairs?
The best answer I can give you, unfortunately, is "it depends." If you take the asymptotic complexities literally, you get O(log 4000) = about 12 for Data.Map, O(min(4000, 64)) = 64 for Data.HashMap and O(1) = 1 for Data.HashTable. But it doesn't really work that way... You have to try them in the context of your code.
The obvious difference between Data.Map and Data.HashMap is that the former needs keys in Ord, the latter Hashable keys. Most of the common keys are both, so that's not a deciding criterion. I have no experience whatsoever with Data.HashTable, so I can't comment on that.
The APIs of Data.HashMap and Data.Map are very similar, but Data.Map exports more functions, some, like alter are absent in Data.HashMap, others are provided in strict and non-strict variants, while Data.HashMap (I assume you meant the hashmap from unordered-containers) provides lazy and strict APIs in separate modules. If you are using only the common part of the API, switching is really painless.
Concerning performance, Data.HashMap of unordered-containers has pretty fast lookup, last I measured, it was clearly faster than Data.IntMap or Data.Map, that holds in particular for the (not yet released) HAMT branch of unordered-containers. I think for inserts, it was more or less on par with Data.IntMap and somewhat faster than Data.Map, but I'm a bit fuzzy on that.
Both are sufficiently performant for most tasks, for those tasks where they aren't, you'll probably need a tailor-made solution anyway. Considering that you ask specifically about lookups, I would give Data.HashMap the edge.
Data.HashTable's documentation now says "use the hashtables package". There's a nice blog post explaining why hashtables is a good package here. It uses the ST monad.
Does anyone know if it is possible to do lock-free programming in Haskell? I'm interested both in the question of whether the appropriate low-level primitives are available, and (if they are) on any information on what works in terms of using these to build working larger-scale systems in the pure functional context. (I've never done lock-free programming in a pure functional context before.) For instance, as I understand it the Control.Concurrent.Chan channels are built on top of MVars, which (as I understand things) use locks---could one in principle build versions of the Chan primitive which are lock free internally? How much performance gain might one hope to get?
I shoudl also say that I'm familiar with the existence of TVars, but don't understand their internal implementation---I've been given to understand that they are mostly lock free, but I'm not sure if they're entirely lock free. So any information on the internal implementation of TVars would also be helpful!
(This thread provides some discussion, but I wonder if there's anything more up to date/more comprehensive.)
Not only does an MVar use locks, it is a lock abstraction. And, as I recall, individual STM primitives are optimistic, but there are locks used in various places in the STM implementation. Just remember the handy rhyme: "If it can block, then beware of locks".
For real lock-free programming you want to use IORefs directly, along with atomicModifyIORef.
Edit: regarding black holes, as I recall the implementation is lock free, but I can't vouch for the details. The mechanism is described in "Runtime Support for Multicore Haskell": http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/multicore-ghc.pdf
But that implementation underwent some tweaks, I think, as described in Simon Marlow's 2010 Haskell Implementors Workshop talk "Scheduling Lazy Evaluation on Multicore": http://haskell.org/haskellwiki/HaskellImplementorsWorkshop/2010. The slides are unfortunately offline, but the video should still work.
Lock free programming is trivial in Haskell. The easiest way to have a shared piece of data that needs to be modified by many threads is to start with any normal haskell type (list, Map, Maybe, whatever you need), and place it in an IORef. Once you've done this, you have the ability to use atomicModifyIORef to perform modifications in place, which are guaranteed to take next to no time.
type MyDataStructure = [Int]
type ConcMyData = IORef MyDataStructure
main = do
sharedData <- newIORef []
...
atomicModifyIORef sharedData (\xs -> (1:xs,()))
The reason this works is that a pointer to the think that will eventually evaluate the result inside the IORef is stored, and whenever a thread reads from the IORef, they get the thunk, and evaluate as much of the structure as it needs. Since all threads could read this same thunk, it will only be evaluated once (and if it's evaluated more than once, it's guaranteed to always end up with the same result, so concurrent evaluations are ok). I believe this is correct, I'm happy to be corrected though.
The take home message from this is that this sort of abstraction is only easily implemented in a pure language, where the value of things never change (except of course when they do, with types like IORef, MVars, and the STM types). The copy on write nature of Haskell's data structures means that modified structures can share a lot of data with the original structure, while only allocating anything that's new to the structure.
I don't think i've done a very good explaining how this works, but I'll come back tomorrow and clarify my answer.
For more information, see the slides for the talk Multicore programming in Haskell by Simon Marlow of Microsoft Research (and one of the main GHC implementors).
Look into stm, specifically its TChan type.
Is it OK to have a language provide both call-by-need (CBN) and call-by-value (CBV) evaluation strategy? I mean without fixing it and simulating in one the other but let the user choose which when in need. For example, let the language has a eval function as in Scheme available which can accept one more argument from the user specifying which evaluation strategy he wants.
Combining call-by-need (laziness) and call-by-value (strictness) in one language implementation is certainly possible, as long as one takes care to avoid making computations with side effects lazy and making diverging computations strict.
Strictness analysis is used in lazy (CBN) functional languages to detect when functions can safely be evaluated using a CBV strategy. CBV evaluation is generally faster, but using this evaluation strategy for non-strict functions changes the semantics of the program.
Wadler describes how to combine lazy and strict computation in a functional language.
A lambda the ultimate thread also addresses the issue.
Scala has a keyword lazy for stating that certain computations are to be performed lazily. Other languages have similar constructs.
I am trying to find the differences between what Clojure calls an STM and what is implemented in Haskell as STM. Taking the actual language semantic differences aside I am a little confused as Rich Hickey says in his speech that Clojure's implementation of STM is very different from anything else out there, but I don't understand the differences apart from the language choice.
Clojure STM has 3 big unique features:
Implements MVCC snapshot avoiding transactions restarts on read invalidation.
Ensures references on read-writes provides a kind of manual control over resource acquisition order.
Has explicit commute which reduces retries on commutative writes.
For Haskell STM, see SPJ's papers: http://research.microsoft.com/en-us/um/people/simonpj/papers/stm/
Of particular use are "Composable memory transactions" and "Transactional memory with data invariants". GHC's implementation of STM indeed isn't MVCC. I don't recall all the implementation details, but my understanding is that the description in the papers isn't all that different from what currently exists in GHC.
(note that MVCC, in clojure or elsewhere, makes possible write-skew -- see, e.g., here: http://en.wikipedia.org/wiki/Snapshot_isolation)
Mark Volkmann did a very detailed presentation on STMs in general (and Clojure's STM in particular) at Strange Loop 2009 which you can find here (article and slides here). I don't really know of any other resource (other than the code) for understanding how Clojure's STM works.