Zulip Chat Archive

Stream: new members

Topic: Why does not declaring `main` type cause this error?


Dan Abramov (Aug 31 2025 at 23:59):

This typechecks:

def main := do
  IO.print "Enter your name: "
  let stdin  IO.getStdin
  let name  stdin.getLine
  IO.println ("Hello, " ++ name)

Now let's remove the first print. Suddenly it doesn't:

def main := do
  let stdin  IO.getStdin
  let name  stdin.getLine
/--
type mismatch
  stdin.getLine
has type
  IO String : Type
but is expected to have type
  BaseIO ?m.10048 : Type
--/
  IO.println ("Hello, " ++ name)

I can fix it by adding : IO Unit though:

def main : IO Unit := do
  let stdin  IO.getStdin
  let name  stdin.getLine
  IO.println ("Hello, " ++ name)

How do I make sense of this? What is the chain of inferences that breaks this?

I'm asking both because it would kind of feel neater to not have to write : IO Unit, and because I don't understand what's happening. I was actually stuck on this error message for a while.

Aaron Liu (Sep 01 2025 at 00:05):

probably some sort of monad lift stuff

Aaron Liu (Sep 01 2025 at 00:06):

IO.getStdin is guaranteed to never error, and that is reflected in its return type being BaseIO instead of IO

Aaron Liu (Sep 01 2025 at 00:07):

but that also means when you bind it you need to convert the BaseIO into IO

Aaron Liu (Sep 01 2025 at 00:07):

and that is usually done automatically

Aaron Liu (Sep 01 2025 at 00:07):

but this time it wasn't done because we didn't know it had to be converted since there was no expected type

Eric Wieser (Sep 01 2025 at 00:09):

The other consideration here is that main is not special when it comes to type-checking, and so there is no expected type available of the form "well this is the main function so it must be IO"

Dan Abramov (Sep 01 2025 at 00:09):

Thanks. Is there any other place I could've done this annotation? Purely scientific interest. I tried putting IO in a few places but I'm not sure I understand which type is flowing where.

Dan Abramov (Sep 01 2025 at 00:11):

I've tried

def main := do
  let stdin  (IO.getStdin: IO IO.FS.Stream)
  let name  stdin.getLine
  IO.println ("Hello, " ++ name)

but now the problem is it's being inferred as EIO IO.Error Unit which I'm not sure where it's coming from.

Eric Wieser (Sep 01 2025 at 00:11):

This works:

def main := do
  let stdin  liftM <| IO.getStdin
  let name  stdin.getLine
  IO.println ("Hello, " ++ name)

Eric Wieser (Sep 01 2025 at 00:12):

Dan Abramov said:

but now the problem is it's being inferred as EIO IO.Error Unit which I'm not sure where it's coming from.

IO Unit = EIO IO.Error Unit is true by definition

Eric Wieser (Sep 01 2025 at 00:12):

I think this error message is a bug

Eric Wieser (Sep 01 2025 at 00:13):

Something somewhere is saying "is the type exactly IO Unit?", but should probably be saying "Is the type reducibly defeq to IO Unit?".


Last updated: Dec 20 2025 at 21:32 UTC