I'm trying to figure out how the basic IO Haskell functions are defined, so I used this reference and I got to the putChar function definition:
putChar :: Char -> IO ()
putChar = primPutChar
Now, however, I cannot find more information about this primPutChar function anywhere. Maybe it might refer to a pre-compiled function, available as binary from a shared object? If that's the case, is it possible to see its source code?
prim* meansSince you're asking this question in terms of the report, let's also answer this question in terms of the report:
Primitives that are not definable in Haskell , indicated by names starting with "
prim", are defined in a system dependent manner in modulePreludeBuiltinand are not shown here
This is still the same in Haskell2010 by the way.
However, you can have a look at base's source to see how it's implemented in GHC:
putChar :: Char -> IO ()
putChar c = hPutChar stdout c
From there you're going deep into the rabbit hole. How does hPutChar know how to print stuff? Well, it doesn't. It only "buffers" and checks that you can write:
hPutChar :: Handle -> Char -> IO ()
hPutChar handle c = do
c `seq` return ()
wantWritableHandle "hPutChar" handle $ \ handle_ -> do
hPutcBuffered handle_ c
The writing is done in writeCharBuffer which fills an internal buffer until it's full (or a line has been reached—it actually depends on the buffer mode):
writeCharBuffer h_@Handle__{..} !cbuf = do
-- much code omitted, like buffering
bbuf'' <- Buffered.flushWriteBuffer haDevice bbuf'
-- more code omitted, like buffering
So where is flushWriteBuffer defined? It's actually part of stdout:
stdout :: Handle
stdout = unsafePerformIO $ do
setBinaryMode FD.stdout
enc <- getLocaleEncoding
mkHandle FD.stdout "<stdout>" WriteHandle True (Just enc)
nativeNewlineMode{-translate newlines-}
(Just stdHandleFinalizer) Nothing
stdout :: FD
stdout = stdFD 1
And a file descriptor (FD) is an instance of BufferedIO:
instance BufferedIO FD where
-- some code omitted
flushWriteBuffer fd buf = writeBuf' fd buf
and writeBuf uses instance GHC.IO.Device.RawIO FD's write, and that ultimately leads to:
writeRawBufferPtr loc !fd buf off len
| isNonBlocking fd = unsafe_write -- unsafe is ok, it can't block
| otherwise = do r <- unsafe_fdReady (fdFD fd) 1 0 0
if r /= 0
then write
else do threadWaitWrite (fromIntegral (fdFD fd)); write
where
do_write call = fromIntegral `fmap`
throwErrnoIfMinus1RetryMayBlock loc call
(threadWaitWrite (fromIntegral (fdFD fd)))
write = if threaded then safe_write else unsafe_write
unsafe_write = do_write (c_write (fdFD fd) (buf `plusPtr` off) len)
safe_write = do_write (c_safe_write (fdFD fd) (buf `plusPtr` off) len)
where we can see c_safe_write and c_write, which are usually bindings to C library functions:
foreign import capi unsafe "HsBase.h write"
c_write :: CInt -> Ptr Word8 -> CSize -> IO CSsize
So, putChar uses write. At least in GHC's implementation. The report however doesn't require that implementation, so another compiler/runtime is allowed to use other functions.
GHC's implementation uses write with internal buffers to write things, including single characters.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With