Apologies if this is a common question, I couldn't find anything similar, but I may just be too inexperienced to know the proper vocabulary.
Here's an example of the fundamental problem in GHCi:
-- foo is something relatively expensive to compute, but it's lazy so it's instantaneous
*Main> foo = (foldl (++) [] (take 5000 $ repeat [10, 123, 323, 33, 11, 345, 23, 33, 23, 11, 987]))
(0.00 secs, 62,800 bytes)
-- bar is a result that uses foo, but it's also lazy so it's not computed yet
*Main> bar = last foo
(0.00 secs, 62,800 bytes)
-- Now let's use bar
*Main> bar
987
(1.82 secs, 11,343,660,560 bytes)
-- It took a couple seconds to compute everything, but that's fine. Now let's use it again:
*Main> bar
987
(1.88 secs, 11,343,660,560 bytes)
-- It took 1.88 seconds to presumably recompute everything whereas it
-- could have been instantaneous if GHCi had remembered the value.
I looked into strictness, but nothing I do seems to help. As I understand it, evaluating an argument strictly should cause all computation to be done immediately, yet neither seq nor $! seem to enable this behavior. For instance:
-- expectation: Perhaps evaluating bar will cause 'last foo' to be run systematically,
-- but foo should be evaluated strictly, which should take most of the computation time
*Main> bar = last $! foo
(0.00 secs, 62,800 bytes)
-- it's actually instantaneous. Sure enough, bar takes time to compute.
*Main> bar
987
(1.88 secs, 11,343,660,632 bytes)
-- what if we use seq? As I understand it, it returns its second argument
-- with the constraint that its first argument must be strict. We want
-- 'bar = last foo' with foo being strict, therefore:
*Main> bar = foo `seq` (last foo)
(0.00 secs, 62,800 bytes)
*Main> bar
987
(1.93 secs, 11,344,585,344 bytes)
-- No dice. What if we evaluate bar strictly and assign that value to a variable 'baz'?
*Main> bar = last foo
(0.00 secs, 62,800 bytes)
*Main> baz = bar `seq` bar
(0.00 secs, 62,800 bytes)
*Main> baz
987
(3.80 secs, 22,689,057,424 bytes)
-- Now it's computing it twice! Once for the `seq` and then a second time to display the value?
-- What if we use BangPatterns? Let's just put a variable through a dummy
-- function that returns its own input (hypothetically) after evaluation.
*Main> eval !x = x
(0.00 secs, 62,800 bytes)
*Main> baz = eval bar
(0.00 secs, 62,800 bytes)
*Main> baz
987
(1.81 secs, 11,345,102,464 bytes)
-- still not doing the computation ahead of time.
So It looks like I fundamentally don't understand what 'strictness' is, and I still can't get GHCi to store anything but thunks into variables. Is this just a quirk of GHCi, or does this apply to GHC? How do I get my code to just store a normal-ass value like 987 in a variable?
There are a few different things going on here, unfortunately.
Type-class polymorphism
Joseph Sible's answer pointed this out, but I think there are a few more details it's worth covering. If you type
x = 1 + 1into GHCi,xwill have typeNum a => a; it's polymorphic, and the type variable has a constraint on it. At runtime such values are represented as a function, with a parameter that is basically theNuminstance for the type you want to use (the runtime instance is called a "dicitonary").This is necessary because you could type
print (x :: Integer)and GHCi has to figure out what value1 + 1is for theIntegertype, orprint (x :: Complex Double)and the compiler has to figure out what1 + 1is for theComplex Doubletype; both of those types use a different definition of+(and of thefromIntegerfunction that says what integer literals mean). You can even create a wholly new type after definingx, give it aNuminstance, and then GHCi still has to be able toprint (x :: MyNewNumType). All of that basically means thatx :: Num a => acannot be evaluated once-and-for-all when you define it; instead it is stored as a function, and every time you use it as some particular type it gets evaluated again, so that its definition can use the+andfromIntegerfunctions appropriate to the type at this usage.This is typically not a huge problem in "normal" Haskell, written in modules rather than entered into the GHCi prompt. In normal Haskell the compiler will be able to analyse the full module to see where
xis used, so it will often be able to infer a particular type forx(i.e. if somewhere else in your module you usexto index into a list with the!!operator, that only acceptsIntas the index, soxwill be inferred as typeInt, not as the more genericNum a => a). And if that fails because none of the usage sites in the module requirexto be a specific type, then there is also the monomorphism restriction that will forcexto be assigned a concrete type anyway, invoking the defaulting rules, which would pickIntegerin this case. So normally a definition will only end up with a type likeNum a => aif it specifically has a type signature requesting that type. (The monomorphism restriction applies to bindings of bare variables without any function arguments, so it will apply tox = 1 + 1, andf = \x -> x + 1, but not tof x = x + 1)But the monomorphism restriction is disabled in GHCi. It was in fact specifically added to the language to avoid performance problems due to type-class polymorphic values being recomputed on every use instead of cached. But it only produces sane results when applied to entire modules where the compiler can see usage of the variables to assign a correct monomorphic type. In GHCi where you enter things one line at a time, forcing the compiler to pick a monomorphic type for bindings like
xafter seeing only the definition worked extremely poorly; it would very often assign the wrong type for the usage you intended, then generating spurious type errors when you tried to use it. So eventually the monomorphism restriction was disabled by default in GHCi, making it much more usable but having these performance issues of values being recomputed more than necessary.So this issue is mostly a problem for you because you're working in GHCi. In "normal" Haskell it still can happen, but default settings and good practice (like giving type signatures to all your top-level bindings) make it a very minor concern in practice.
Haskell is timeless
This is an extremely common mistake when first learning about applying strictness in Haskell, so you're in good company! I'm going to talk about
seqhere, but the same concepts apply to things like$!and bang patterns; for examplef !x = (x, x)is basically just a convenient shorthand forf x = x `seq` (x, x).baz = bar `seq` barisn't doing what you think it's doing. In fact it isn't doing anything at all, it's equivalent to simplybaz = bar.The reason is that
seqdoesn't cause evaluation now, and the reason for that is not some quirky technical thing, it's that there is no such thing as "now" in Haskell. This is the fundamental difference between a pure declarative language and an imperative language. Imperative code is a sequence of steps that are executed in order, so the notion of time is inherent to the language; there is a "now" that "moves through" the code. Pure declarative code isn't a sequence of steps, it's set of definitions. There's no "now" because there's no "time"; everything just is.baz = bar `seq` baris a definition for whatbazis, not a step to execute; there's no "before" and "after" to define the "now" whenseqcould causebarto be evaluated.Moving away from this concept of code being inherently tied to a notion of time is one of the trickiest mental leaps imperative programmers need to make when they come to a pure language like Haskell for the first time (and in a pure but strict language you don't necessarily even need to; it's still reasonably easy to mentally model such code as a sequence of steps, even if I would argue it's still not the best conceptual model). And it's complicated by GHCi because the interpreter obviously does have a notion of time; you enter bindings one at a time, altering the set of definitions that the interpreter is aware of. In normal module Haskell that set of definitions is static but in GHCi it changes over time, inherently defining a "now". But
seqis a feature of Haskell, not of GHCi, so it has to make sense without GHCi's notion of time. Even though the set of definitions changes over time in a GHCi session, those definitions themselves still don't have any notion of time; they are written in Haskell, which is timeless.So without any notion of time
seqcan't actually control when something is evaluated. What it does instead is control what gets evaluated. Evaluation in Haskell is driven by need; ifxis being evaluated andxis defined byx = a + b, then the evaluation ofximposes a need to evaluateaandb(and+; technically+is what immediately is needed and the definition of+will decide whetheraand/orbis needed, but for most types' definitions of+both will end up needed). Allseqdoes is add additional "need". Inx = a `seq` b,xis defined to be equal tob(because that's whatseqreturns), so evaluatingxwill obviously need to evaluateb; the special thing aboutseqis that it says that evaluatingxwill also need to evaluatea(and it can do this without knowing anything aboutaorb). But if we never needed to evaluatexitself then the fact thatseqis used in the definition ofxwon't do anything.In GHCi's notion of time,
x = a `seq` bdoesn't evaluatea"now" when that binding is entered. It will evaluateaifxis ever used (in a manner that requires it to be evaluated), which will be after some later entry at the command prompt.This is how we come to
baz = bar `seq` barjust being equivalent tobaz = bar.baz = bar `seq` barmeans that whenbazis evaluated, we will also need to evaluatebar. Butbazis equal tobar, so of course we're going to need to evaluatebarby definition!Similarly
bar = foo `seq` (last foo)doesn't help because evaluatingbarrequired evaluatinglast foo, which is going to have to evaluatefooanyway.So this is also sort-of an issue that is more of a problem in GHCi than in normal Haskell. In GHCi it's very easy to think in terms of the "now" that is you entering things into the command prompt, and thus have a clear idea in your head of what "evaluate this now" would mean and expect that adding strictness would do that. In normal Haskell it's still easy for beginners to make that mistake, but it's less unclear that the notion of "now" doesn't make much sense and thus hopefully easier to develop better expectations on what
seqis able to do.But we can use time when we have it
There is a quick hack you can use to exploit GHCi's notion of time, though.
If I enter
b = not Trueinto GHCi (an example avoiding numbers to not trip over the type class polymorphism complication), thenbis defined but it's not evaluated yet. Using strictness (via whether viaseq,$!, or anything else) inside the definition ofbwon't help, because it can only make extra things be evaluated whenbis evaluated, not change the fundamental reality that merely definingbdoesn't evaluatebto trigger any of that extra strictness.But after I've defined
bI can immediately enter a new command that will causebto be evaluated. In this case a simplebwould work fine, since GHCi printing it will inherently force evaluation. But if I didn't want to actually printb, I could enter this:This tells GHCi to print
(), but to do so it first has to evaluateb(without doing anything else with it).The other thing that can give us a notion of time is
IO.IOcode is still Haskell code, which means it's technically "timeless". But the interface ofIOis designed to model (in timeless Haskell), the things that can be done by interacting with the real world. The real world is definitely not timeless, so that notion of time inherently enters the picture ofIOcode; effectively the data dependencies of an abstract monad construct a notion of time out of Haskell's timeless semantics, andIOjust hooks up execution with the real world so that the constructed time matches up with real world time. This means that a function that means "evaluate this now" does make sense inIO, and indeed there is the functionevaluate :: a -> IO a(you have to import it fromControl.Exceptionthough). This is usingIO's notion of time rather than GHCi's, so this even can be used in module Haskell! But it of course only works inIOcode.So using
evaluate baras a command in GHCi will also work as a way to evaluatebar"right now". (evaluatereturns the value though, so GHCi will print it; if you don't want that thenbar `seq` ()would still be preferable)You can even incorporate
evaluateinto the definition of a variable in GHCi to make it evaluated as it is defined, but you have to use the monadic binding<-instead of normal definition with=(exploiting the fact that the GHCi prompt is more-or-less "insideIO").(Note here I'm using the
:sprintGHCi special command; instead of converting a whole value to a string withshow,:printand:sprintprint out a string intended to show the structure of the value without forcing any evaluation;:sprintjust shows_where an unevaluated value is found, while:printalso includes information about its type. These can be very handy for checking whether other commands have caused evaluation; it's more precise than relying on the time it takes for a command to be processed. You have to use them on a variable though, not an expression)Similarly some other monads like
Stateetc can also be viewed as building a notion of time on top of Haskell's timeless semantics, but they don't allow truly arbitrary side effects likeIOdoes, and their constructed timelines don't necessarily match up with the real world's time since they are evaluated whenever timeless lazy Haskell needs (and thus might be run zero or multiple times); they're more of a simulation of time. So an "evaluate this now" function in a "now" derived from those timelines wouldn't be as useful.Weak head normal form
Finally, there is still a problem with trying to strictly evaluate
foo. Say you use a type annotation to avoid the polymorphism and use an extrafoo `seq` ()command to evaluate it, like this:This hasn't actually evaluated "all" of
foo:We've only evaluated up to the first element of the list! That's probably not what you wanted.
So far in this post we've just been talking about a value being evaluated without any clarification, like that's a single atomic step. That's true for very simple values like
Bools,Ints,Chars, etc. But most values (like lists) have more structure than that, potentially containing many sub-values that can be evaluated or not. So when we say that such a value is being evaluated, there are many possibilities for what we could mean.In Haskell the "standard" notion of evaluation is always to weak head normal form. That has a fancy technical definition, but basically it means evaluated until we have the outermost data constructor (like the
:for lists). This is because most of the "need" in Haskell's need-driven evaluation comes from pattern matching; at minimum the system will need to know what the outermost data constructor is to decide which branch to take in pattern matching (e.g. many list functions will have a case for[]and a case for:). All the values contained in that outermost constructor (e.g. the head and tail of the list) will be left unevaluated if they haven't already been evaluated by something else, until more pattern matching needs to examine those contained values.So when we say that
foo `seq` ()is equivalent to a()but with additional need to evaluatefoo, evaluating the()only causesfooto be evaluated as far as the very first list cell (it doesn't even evaluate the value inside that list cell, but because it ultimately came from a literal in your source code it was already evaluated, which is why:sprintshowed10 : _and not_ : _).DeepSeq
The module
Control.DeepSeqhas tools for forcing deep evaluation. For example there isdeepseqthat can be used in the same way asseq, but it forces the left argument to be completely evaluated. There is alsoforcethat can be used in combination withevaluateto force deep evaluation "now" (usingIO's notion of "now").This is all based on a type class
NFDatathough (the NF in the name is short for "normal form"; normal form is fully evaluated, while weak head normal form is evaluated to the outermost constructor). You can'tdeepseqa value of a type that doesn't implement the class. Most standard types already have instances, so this works fine forfoo :: [Integer]. But if you're using your own types you will need to give them instances ofNFData.So this will actually define
fooand then fully evaluate it (without printing it):But deep evaluation inherently involves a full traversal of the data (you can't evaluate everything without looking at everything). So using
deepseqa lot can be very inefficient; if you were going to evaluate it later anyway you're wasting time doing an additional pass over the data (and if you weren't going to use it later then you're forcing the system to spend time running code it could have skipped entirely).It's useful when you have a computation depending on large structures that will result in only a small structure (but bigger than a single number, or else
seqwould have been enough to force it); if it would otherwise be left only partially evaluated for a long time,deepseqing it might avoid having to keep the large structure in memory at all, which can be more efficient (even though you've added an extra small pass over the small result structure).It's probably more commonly used though when the computation might throw an exception (or has side effects smuggled in with
unsafePerformIOet al);deepseqing it will force those exceptions or side effects to manifest at a known point, instead of whenever later code happens to need a specific part of the result. This is where theevaluate . forcecombo comes in particularly useful, since you're forcing it inIOwhere you have a well-defined "now" and also can handle exceptions.But generally
deepseqis better avoided if you can. It's an important tool to have available for sparing use, not a way of making Haskell behave exactly like strict languages.