What is GHC.Stack.Types.HasCallStack

Hi,

I recently was looking at the type for head:

ghci> :t head
head :: GHC.Stack.Types.HasCallStack => [a] -> a

And realised I have no idea what GHC.Stack.Types.HasCallStack is.

Can anyone explain what it is?

1 Like

Hi! you’ll find the documentation here: GHC.Stack

4 Likes

That’s great, thanks. But it doesn’t explain why it is used in the definition of head. Could you expand on why it’s used in head and other functions?

Ah sure, I can answer that question: head is a partial function: which means that for a valid input value (the empty list), it will throw an exception. HasCallStack is a mechanism to produce a stack trace upon exceptions, so that you may get a use site for the exception that is thrown:

ghci> head []
*** Exception: Prelude.head: empty list
CallStack (from HasCallStack):
  error, called at libraries/base/GHC/List.hs:1646:3 in base:GHC.List
  errorEmptyList, called at libraries/base/GHC/List.hs:85:11 in base:GHC.List
  badHead, called at libraries/base/GHC/List.hs:81:28 in base:GHC.List
  head, called at <interactive>:1:1 in interactive:Ghci1

Take a look at the documentation: Prelude

3 Likes

It’s a wart. You shouldn’t have to change the type signature to get a proper error message.

It’s also not really an exhaustive mechanism to mark partial functions. A partial function may or may not have it. You can assign it to a total function too.

What a mess.

2 Likes

What’s the alternative? Implicitly add a HasCallStack to every definition?

It’s also not really an exhaustive mechanism to mark partial functions. A partial function may or may not have it. You can assign it to a total function too.

That’s the same with pretty much every Haskell construct isn’t it? A function with a HasCallStack constraint may or may not use it (by throwing an exception). A function may or may not use its argument. A value of type IO a may or may not do IO.

I don’t know. But it seems to me like implementation details leaking. As an end user I don’t really care what magic the compiler conjures to present me with reasonable call stack in the error message.

Why is this offloaded to core libraries?

Yeah, although “maybe partial or not” is really not useful to end users or is it? “This function has access to IO, but may not actually do IO” isn’t that confusing to me.

There was a discussion once to add a Partial constraint to base, following a HasCallStack discussion. So yeah.

3 Likes

Yeah, I like the idea of the Partial constraint.

2 Likes

HasCallstack was introduced as a mechanism to allow authors to precisely control the generation of callstacks when an exception happens.

As you can see, it’s a bit finicky and not everyone is happy with the current design. But at least we get callstacks now!

2 Likes

That would seem to be a reasonable summary of this issue:

It’s also appeared here:

The documentation says it is a lightweight method of obtaining a partial call-stack which probably means that it is not zero cost. Can GHC optimize the book-keeping away for functions that do not explicitly use this constraint? If not, I agree with @hasufell that it is pointless to make it explicit in the type signature.

In all cases, no, I doubt it. It is implemented as an implicit parameter, that is, it really is an argument to each function that it annotates.

Yeah, look at the docs: GHC.Stack

More specifically:

NOTE: The implicit parameter ?callStack :: CallStack is an implementation detail and should not be considered part of the CallStack API, we may decide to change the implementation in the future.

Not sure how much Iike that wording, given how prominently HasCallStack is exposed.

Thankfully we are getting out of this with this recently accepted GHC Proposal: https://github.com/bgamari/ghc-proposals/blob/stacktraces/proposals/0000-exception-backtraces.rst

6 Likes

Why is head not defined as a safe function instead?

This may help to explain that:

(Note: it’s a long read…)

It was specified 35 years ago, drawing on decades of precedent, probably going back all the way to Lisp, when programmers and language designers had bigger things to worry about. It’s debatable whether it would be designed this way if Haskell were started today. For example, Purescript’s version of head has a Partial constraint that can only be dispatched with a use of unsafePartial.

1 Like

As Tom says, that decision is out of our hands, so we have to make do with the tools we have now: using uncons if List must be the interface type, or using NonEmpty in order to enforce the invariants earlier in the program. :slight_smile:

3 Likes

PureScript has this, and we’re probably stuck with it at this point, but I think it was a bad decision.

The problem with encoding partiality as a constraint is that unsafePartial head (where unsafePartial :: (Partial => a) -> a) resolves the constraint to the compiler’s satisfaction without actually reducing the thing that is partial (the intent is that you’d always call unsafePartial (head foo) instead, but mistakes happen and type checkers are for catching them). You’re left with something having the type of a pure function, and you can pass that pure function anywhere else and it may blow up on you there.

And you can say, well, you used something called unsafe somewhere in your code; any subsequent blow-ups are on you. But there’s no way to use Partial-constrained types without using unsafePartial somewhere in your code, so this amounts to always knowing which functions are partial and manually keeping track of when they’re fully reduced—which is exactly what we’d have to do if partiality wasn’t encoded in the type system at all, so what have we gained?

3 Likes

A “noise word” ?

https://web.archive.org/web/20221205155031/https://mail.mozilla.org/pipermail/rust-dev/2013-April/003926.html

But this is getting somewhat off-topic (perhaps for a new thread in the New Year…) - the question:

has been answered here: