Hazy is a Haskell compiler where I plan to experiment on making Haskell more performant.
As of now the project is in a very early stages. The compiler currently unable to bootstrap itself, the standard library isn't implemented yet, some major Haskell features are missing, only a test Javascript backend is implemented.
Ensure that GHC 9.12 and Cabal are downloaded. Then run the following:
# Copy the runtime
cp -R runtime output
# Build the standard library
cabal run -w ghc-9.12 hazy -- -I library/runtime library/base -o output
# Build the hello world example
cabal run -w ghc-9.12 hazy -- -I library/runtime -I library/base test/run/hello/source -o output
# Optionally, Format the generated Javascript
prettier -w output
# Run hello world
node output/index.mjs- Use
UnorderedRecordsall throughout compiler - Add top level documentation to all modules
- Let Pattern Binds
- Integer Literal Patterns
- Floating Point Literals
- Constrained Type Defaulting
- List Comprehensions
- Do notation
- Lambda Case
- Record Updates
- Right Sections
- Type Annotation Expressions
- Deriving
- GADTs
- Polymorphic Components
- Strict Functions
- Standard Library
- Bootstrapping
Hazy implements original extensions alongside some GHC extensions. Note that you can only toggle the only extensions that aren't fully backwards compatible.
- Pragma:
ConstructorFields - Toggleable: True
This is Hazy's take on DisambiguateRecordFields. This extension causes
constructor fields to be associated to the constructor and thus may be accessed
without needing to import said fields.
For example, this would now be valid:
module A (Point (Point)) where
data Point = Point { x, y :: Int}module B where
import A (Point (Point))
construct = Point { x = 1, y = 2 }This extension applies per constructor, so even modules that have this extension disabled are still affected.
Additionaly, the CONSTRUCTORFIELDS pragma can enable this per constructor.
data Multiple = Single Int | Many { x, y :: Int }
{-# ConstructorFields Many #-}- Pragma:
UnorderedRecords - Toggleable: True
This causes record declaration is this module to be unordered. This means that they cannot be accessed with order dependent pattern matching.
Consider this example:
data Point = Point { x, y :: Double }
dot (Point x y) = x + y -- #1
dot' Point {x, y} = x + y -- #2Here, #1 is order dependent so it would be rejected while #2 is order independent so it would be accepted.
This extension applies per constructor, so even modules that have this extension disabled are still affected.
Additionaly, the UNORDEREDRECORDS pragma can enable this per constructor.
For example:
data Multiple = Single Int | Many { x, y :: Int }
{-# UnorderedRecords Many #-}- Pragma:
StableImports - Toggleable: True
This extension requires this property to hold:
Your imported modules may freely add new symbols without causing overlapping symbols.
This boils down to requiring all imports either use import lists or non overlapping qualified imports.
Consider this example:
import A -- #1
import B (x, Y(..)) -- #2
import qualified C -- #3
import qualified D -- #4
import qualified E as D -- #5Import #1 is rejected because it imports everything. Import #4, #5 are rejected because they overlap with import lists. Import #2 is okay because it uses an import list. Import #3 is okay because it's the only qualified module in it's namespace.
Note that exporting new constructors is considered an API break, so importing all constructors of a type is still allowed.
This can be disabled with {-# LANGUAGE NoStableImports #-} or
{-# LANGUAGE Haskell2010 #-}.
- Pragma:
OfGuardBlocks - Toggleable: False
Guard blocks for functions and multiway if blocks may use the of keyword
instead of |. This simply treats the block as newline intended statement.
Consider this example:
addFail maybe1 maybe2
| Just value1 <- maybe1
Just value2 <- maybe2
= value1 + value2
| otherwise = 0This may be written like:
addFail maybe1 maybe2
of
Just value1 <- maybe1
Just value2 <- maybe2
value1 + value2
of
0- Pragma:
ExtendedLocalDeclarations - Toggleable: False
Local let and where can include import, data and class declarations.
For example this code is now possible:
sortBy :: forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy by = map runBy . sort . map By where
import Data.List (sort)
newtype By = By { runBy :: a }
instance Eq By where
a == b
| EQ <- compare a b = True
| otherwise = False
instance Ord By where
compare (By a) (By b) = by a b- Pragma:
ExtendedFunctionBindings - Toggleable: False
Function bindings are not requried to be contiguous and they may have different number of clauses.
For example, this is legal:
notJust (Just bool) = Just (not bool)
id x = x
notJust = idHere, both notJust are combined into a single function and the second case
of notJust is eta expanded.
- Pragma:
ExtendedImportDeclarations - Toggleable: False
Import declarations may appear anywhere inside a module.
- Pragma:
ScopedTypeVariables - Toggleable: False
Hazy only implements the scoped type variables with type signaturues. It does not implement pattern type signatures.
- Pragma:
PolymorphicComponents
This is a subset of GHC's RankNTypes.
Planned.
Pragma: GADTs
Planned.
- Pragma
ImplicitPrelude - Toggleable: True
The implicit prelude can be turn off like in GHC. This has the same effect of
as import Prelude().
- Pragma:
LambdaCase - Toggleable: False
Lambda case syntax is supported.
- Pragma:
MultiwayIf - Toggleable: False
Multiway if syntax is supported.
- Pragma:
EmptyCase - Toggleable: False
Empty case syntax is supported.
- Pragma:
DataKinds - Toggleable: False
Data kinds are supported. However, The scope of constructors does not pollude the scope of types. This means that type level data constructors must be ticked.
If you want unticked operators, it's recommended you use type synonyms.
data Lifted = A | B
type A = 'A
type B = 'BAdditionally, types have two universe levels: type Small and type Large.
Large types cannot be abstracted over or used in data types.
Consider this example:
type BoolList = '[ 'True, 'False ] -- #1
type TypeList = '[ Int, Char ] -- #2Here, #1 is okay because it's a just a lifted list of booleans.
However, #2 is a illegal because lists expect their elements to be Types and
Type is not a Type.
- Pragma:
TypeOperators - Toggleable: False
Proper type operators are not implemented. Instead, only ticked infix constructors
are supported for completeness for DataKinds.
- Pragma:
NamedFieldPuns
Named field pun syntax is supported.
These are deviations that will are planned to get fixed at some point.
Newtype declarations are treated as normal data declarations.
Hazy does not generalize bindings groups. Unannotated declarations that form a cycle are errors.
For example, this would be rejected:
f a b = g a b
g a b = f a bHowever, this would be okay:
f a b = a b
g a b = f a bHazy applies the monomorphism restriction to all declarations. This means that only type variables without constraints are generalized.
Consider these two examples:
f a = a -- #1
f' a = a + 1 -- #2Here, #1 is polymorphic over an unconstrainted type variable so it gets
properly generalized to f :: a -> a. However, #2 is rejected because it
would have the type f' :: Num a => a -> a.
All local bindings are monomorphic. This is nearly equivalent to GHC's
MonoLocalBinds extension.
Constraints must not have overlapping typeclass / rigid variable pairs. For example, something like this is not allowed:
exotic :: (Eq (f Int), Eq (f Char)) => f Int -> f Int -> f Char -> f Char -> Bool
exotic a b c d = a == b && c == dThe fields of a constructor are all lazy.
If you have access to a typeclass, then you are able to define instances for it's methods regards on whether or not the method is exported.
For example, this is legal:
module Hidden ( Hidden ) where
class Hidden a where
private :: amodule Usage where
import Hidden (Hidden)
data Usage = Usage
instance Hidden Usage where
private = UsageThese are deviations that are unlikely to be fixed in the the near future.
Hazy does not implement a negation operator. However negative integer literals are supported. The are parsed when there is no space between the integer and the minus sign.
This example is rejected:
negate x = -xThis example get treated as sections;
section x = (- x)
section' = (- 10)This example is a normal integer literal:
literal = (-10)Haskell 2010 specifics that hiding declarations must also hide constructors.
Hazy instead only hides the type constructor as one would expect.
For example:
import Prelude hiding ( Just )Will hide the Just constructor in Haskell, but this wouldn't hide anything in Hazy.
Instead, the syntax for hiding constructor mirrors that of import constructors.
import Prelude hiding (Maybe (Just))Hazy does not support orphan instances. All instance declarations must have either the class or the data instance in the same module.
Left sections are treated as function application and are not eta expanded.
This follows GHC's PostfixOperators extension.
Copyright © 2026 Freddy "Superstar64" Cubas
The compiler and all other components is licensed under GPL3 only. See LICENSE
for more information. The standard library and runtime (everything under the
library and runtime subdirectories) are licensed under the Boost License.
Any code generated by the compiler follows the license of the input source code. This includes snippets inserted by compiler.