It turns out that this is a bug in the process
package: https://github.com/haskell/process/issues/306
This bug appears to be platform-specific. On Linux, it already prints the correct error:
ghc-9.2.7: could not execute: not-a-real-command
Relevant bits of code:
For some reason, when createProcess_
tries to execute a non-existent file on OpenBSD, errno
ends up set to EBADF
(Bad file descriptor), which leads to the IOErrorType
being InvalidArgument
, which makes isDoesNotExistError
false, leading to the ugly internal error. The reason it prints the correct error on Linux is that there, errno
ends up set to ENOENT
(No such file or directory).
I do not think we should just print the nice "could not execute" error when errno
is EBADF
. I think something is wrong that's causing errno
to be that, and that we should find and fix that so we get ENOENT
instead even on OpenBSD. In particular, note that https://man.openbsd.org/OpenBSD-7.4/execv.3 and https://man.openbsd.org/OpenBSD-7.4/execve.2 don't even list EBADF
as a possible error from them.
I just did some more testing with a version with default
switched to True
, and it seems to have fixed the problem I originally reported without causing any new problems.
Would just making the flag default: True
instead work?
When a "Couldn't match kind" error occurs because of a difference in the position of a forall.
, the expected and actual kinds printed will be identical and provide no indication of what the problem is.
Try to compile this code:
import GHC.Exts (TYPE)
type Foo :: * -> forall r. TYPE r -> *
newtype Foo m a = MkFoo ()
type Bar = Foo :: forall r. * -> TYPE r -> *
You'll receive the following error:
error: [GHC-83865]
• Expected kind ‘* -> * -> *’, but ‘Foo’ has kind ‘* -> * -> *’
• In the type ‘Foo :: forall r. * -> TYPE r -> *’
In the type declaration for ‘Bar’
I expect the expected kind and actual kind printed in the error to be different, specifically to indicate the difference in where the forall.
is.
Optional:
The os-string
flag being default: False
interacts badly with --allow-newer
. Any builds with --allow-newer
that pull in hashable
will end up using the new filepath
but without os-string
, and so failing with the very error that this patch was meant to solve. Pinging @RyanGlScott as the author of that patch.
Looks like this got fixed in GHC 9.6.
Consider this invalid code:
badString = “hello”
badChar = ‘x’
The errors we give for it are lexical error at character 'h'
and lexical error at character 'x'
. Unless you can see that those are surrounded by smart quotes in your code, and already know that smart quotes are a problem, that error won't be helpful.
I propose that we give an error explicitly pointing out the smart quotes and saying they're not real quotes (similar to what Rust does: Unicode characters '“' (Left Double Quotation Mark) and '”' (Right Double Quotation Mark) look like '"' (Quotation Mark), but are not
).
Consider this invalid code:
badString = “hello”
badChar = ‘x’
The errors we give for it are lexical error at character 'h'
and lexical error at character 'x'
. Unless you can see that those are surrounded by smart quotes in your code, and already know that smart quotes are a problem, that error won't be helpful.
I propose that we give an error explicitly pointing out the smart quotes and saying they're not real quotes (similar to what Rust does: Unicode characters '“' (Left Double Quotation Mark) and '”' (Right Double Quotation Mark) look like '"' (Quotation Mark), but are not
).
One other interesting thing I noticed: if I copy and paste the inferred type as-is back into the source file, I get a -Wredundant-constraints
warning for the Functor
constraint. So GHC does already know these are redundant, but it's inferring them anyway for some reason.
Okay, I sort of understand now why it only works with SomeTypeFamily a1 ~ t a2
. And if I give it the signature baz :: (SomeTypeFamily a1 ~ t a2, SomeClass a1) => a1 -> b
, that is accepted. So I guess the problem with my second example is really just the same as it is in the first: that the Functor (t a2)
constraint gets inferred even though it's redundant.
To be clear, I know it typechecks fine with the long signature. What this bug is about is that it doesn't with the short one.
I don't think the monomorphism restriction is related to what I'm seeing. Even with NoMonomorphismRestriction
, the inferred type of the second example is still the long ugly one, and manually specifying the one I want makes it break.
GHC sometimes infers or requires constraints with type families that are actually redundant due to a superclass constraint.
Load this code into GHCi:
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Trans.State
import Data.Bitraversable
import Data.Functor.Identity
import Data.Functor.Foldable
mapAccum func x ys = embed $ fmap (mapAccum func x') ys' where
(ys', x') = runState (bitraverse (\y -> StateT (\x -> Identity $ func x y)) pure (project ys)) x
Do :t mapAccum
and you'll get this:
mapAccum
:: (Base a ~ t1 t2, Base b ~ t1 c, Bitraversable t1, Recursive a,
Corecursive b, Functor (t1 c)) =>
(t3 -> t2 -> (c, t3)) -> t3 -> a -> b
But Functor (t1 c)
is redundant due to the presence of Corecursive b
and Base b ~ t1 c
. (In this case, manually specifying the type signature without the redundant constraint works fine.)
For another example, load this code into GHCi:
{-# LANGUAGE TypeFamilies #-}
type family SomeTypeFamily a :: * -> *
class Functor (SomeTypeFamily a) => SomeClass a where
foo :: SomeTypeFamily a b -> a -> b
bar :: t a b
bar = undefined
baz = foo (fmap id bar)
Do :t baz
and you'll get this:
baz
:: forall {k} {a1} {t :: k -> * -> *} {a2 :: k} {b}.
(SomeTypeFamily a1 ~ t a2, SomeClass a1, Functor (t a2)) =>
a1 -> b
I was hoping for just baz :: SomeClass a => a -> b
, since the information from the other constraints is already guaranteed by the superclass constraint on SomeClass
. (In this case, manually specifying that type signature doesn't work.)
Optional:
A lot of Haskell newbies make the mistake of using ->
where they mean =>
, and the error we give them is rather cryptic.
ghci> :{
ghci| doTwice :: Applicative t -> t () -> t ()
ghci| doTwice x = x *> x
ghci| :}
<interactive>:6:12: error:
• Expected a type, but ‘Applicative t’ has kind ‘Constraint’
• In the type signature: doTwice :: Applicative t -> t () -> t ()
ghci>
Make the above error suggest changing the ->
after Applicative t
to =>
.
But don't you not need to derive at all? Can't you just use the Stock
type directly, like this? For example, given newtype Opaque = Opaque Int
, couldn't another module write this code?
mkOpaque :: Int -> Opaque
mkOpaque x = y where Stock y = read ("Opaque " ++ show x)
If I have a type that I don't export the constructor of, won't this let code from other modules make arbitrary values of it by using the autogenerated instance Read (Stock Opaque)
?
The fact the match is partial is explicit and visible with the ~ syntax
But this isn't true. There are good reasons to write things like f ~(x,y) = _
, even though (x,y)
is not partial. Consider code like this:
data Foo = Foo Int Bool
f = do
~(Foo x y) <- get
put $ Foo (x + 1) (not y)
It's not partial. Then later someone comes along and changes the first line to data Foo = Foo Int Bool | Bar String
. Now the code is partial, and if we made this change, then there'd be no warning that it's now partial.