What is prim*
Since you are asking this question in terms of the report, let's also answer this question in terms of the report:
Primitives that cannot be defined in Haskell, with names beginning with " prim ", are defined in a system-dependent manner in the PreludeBuiltin module and are not shown here.
By the way, in Haskell2010 everything is the same .
How it is implemented in the GHC
However, you can take a look at the base source to see how it is implemented in GHC:
putChar :: Char -> IO () putChar c = hPutChar stdout c
From there you go deep into the rabbit hole. How can hPutChar print things? Well, that is not so. It only "buffers" and verifies that you can write:
hPutChar :: Handle -> Char -> IO () hPutChar handle c = do c 'seq' return () wantWritableHandle "hPutChar" handle $ \ handle_ -> do hPutcBuffered handle_ c
Writing is done in writeCharBuffer which fills the internal buffer until it is full (or a line is 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? This is 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 the 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 write , and that ultimately results in :
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 the implementation of the GHC. However, such an implementation is not required in the report, therefore another compiler / runtime is allowed to use other functions.
TL DR
The GHC implementation uses write with internal buffers to write things, including single characters.