I looked at the module of GHC.Prim and found that it seems that all datas in GHC.Prim are defined as data Float#
without something like =A|B
, and all functions in GHC.Prim is defined as gtFloat# = let x = x in x
.
My question is whether these definations make sense and what they mean.
I checked the header of GHC.Prim like below
{-
This is a generated file (generated by genprimopcode).
It is not code to actually be used. Its only purpose is to be
consumed by haddock.
-}
I guess it may have some relations with the questions and who could please explain that to me.
A brief expansion of jozefg's answer ...
Primops are precisely those operations that are supplied by the runtime because they can't be defined within the language (or shouldn't be, for reasons of efficiency, say). The true purpose of
GHC.Prim
is not to define anything, but merely to export some operations so that Haddock can document their existence.The construction
let x = x in x
is used at this point in GHC's codebase because the valueundefined
has not yet been, um, "defined". (That waits until the Prelude.) But notice that the circularlet
construction, just likeundefined
, is both syntactically correct and can have any type. That is, it's an infinite loop with the semantics of ⊥, just asundefined
is.... and an aside
Also note that in general the Haskell expression
let x = z in y
means "change the variablex
to the expressionz
whereverx
occurs in the expressiony
". If you're familiar with the lambda calculus, you should recognize this as the reduction rule for the application of the lambda abstraction\x -> y
to the termz
. So is the Haskell expressionlet x = x in x
nothing more than some syntax on top of the pure lambda calculus? Let's take a look.First, we need to account for the recursiveness of Haskell's let expressions. The lambda calculus does not admit recursive definitions, but given a primitive fixed-point operator
fix
,1 we can encode recursiveness explicitly. For example, the Haskell expressionlet x = x in x
has the same meaning as(fix \r x -> r x) z
.2 (I've renamed thex
on the right side of the application toz
to emphasize that it has no implicit relation to thex
inside the lambda).Applying the usual definition of a fixed-point operator,
fix f = f (fix f)
, our translation oflet x = x in x
reduces (or rather doesn't) like this:So at this point in the development of the language, we've introduced the semantics of ⊥ from the foundation of the (typed) lambda calculus with a built-in fixed-point operator. Lovely!
We need a primitive fixed-point operation (that is, one that is built into the language) because it's impossible to define a fixed-point combinator in the simply typed lambda calculus and its close cousins. (The definition of
fix
in Haskell's Prelude doesn't contradict this—it's defined recursively, but we need a fixed-point operator to implement recursion.)If you haven't seen this before, you should read up on fixed-point recursion in the lambda calculus. A text on the lambda calculus is best (there are some free ones online), but some Googling should get you going. The basic idea is that we can convert a recursive definition into a non-recursive one by abstracting over the recursive call, then use a fixed-point combinator to pass our function (lambda abstraction) to itself. The base-case of a well-defined recursive definition corresponds to a fixed point of our function, so the function executes, calling itself over and over again until it hits a fixed point, at which point the function returns its result. Pretty damn neat, huh?
It's magic :)
These are the "primitive operators and operations". They are hardwired into the compiler, hence there are no data constructors for primitives and all functions are bottom since they are necessarily not expressable in pure haskell.
(Bottom represents a "hole" in a haskell program, an infinite loop or
undefined
are examples of bottom)To put it another way
These data declarations/functions are to provide access to the raw compiler internals. GHC.Prim exists to export these primitives, it doesn't actually implement them or anything (eg its code isn't actually useful). All of that is done in the compiler.
It's meant for code that needs to be extremely optimized. If you think you might need it, some useful reading about the primitives in GHC