In the module System.Info
I see these functions:
os :: String arch :: String compilerName :: String compilerVersion :: Version
Why is there no IO
there? They are accessing the system... Am I wrong? My expectation was something like:
os :: IO String arch :: IO String compilerName :: IO String compilerVersion :: IO Version
Use case:
print os -- "darwin" print arch -- "x86_64" print compilerName -- "ghc"
IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO a is the type of an IO action that returns an a . You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.
Haskell is a pure language and even the I/O system can't break this purity. Being pure means that the result of any function call is fully determined by its arguments. Procedural entities like rand() or getchar() in C, which return different results on each call, are simply impossible to write in Haskell.
An IO String is a String in the IO-Monad. If a function in the IO-Monad returns an IO String, you get the String by doing: do str <- ioFunc. A function is in the IO-Monad when it needs IO-access and has to return IO types.
You aren't getting that information at runtime. They are hardcoded in the compiler as installed on your system.
This is most obvious if you look at the definition for compilerName
as found in http://hackage.haskell.org/package/base-4.12.0.0/docs/src/System.Info.html.
compilerName :: String compilerName = "ghc"
but even something like os
os :: String os = HOST_OS
is defined in terms of an otherwise undefined name HOST_OS
(a value starting with an uppercase letter??) which suggests that is just a placeholder which gets replaced during installation.
Someone can also correct me (please!), but the {-# LANGUAGE CPP #-}
pragma at the top of that file suggests that HOST_OS
and the like are replaced by appropriate strings by the C preprocessor before compilation.
The question is a good one. The answer, such as it is, is that those values are static per program compilation. They are essentially compiled into the program, and never change after that. As such, nothing (in the assumptions GHC uses) breaks if you treat them as constants. And it's more convenient to use a simple constant than an IO action.
But that's all sort of legacy reasoning. Haskell is an old language. (No really, it's older than Java by several years.) A lot of libraries have been built with reasoning that is no longer considered best practices. These are examples of that. A modern library exposing them would probably make them IO actions even though the results don't change after compilation. It's more useful to put things that aren't constants at the source level behind IO actions, though there are still some notable exceptions, like Int
changing size between 32- and 64-bit platforms.
In any case... I'd say your expectations are solid, and those types are the results of historical oddities.
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