Getting Started
Every language on this site so far has been imperative or object-oriented in flavor — even the ones with functional features bolted on (Java's streams, C#'s LINQ) are still fundamentally "do this, then this" languages underneath. Haskell isn't. This course treats it as what it genuinely is: the real origin point for ideas already met elsewhere on this site under different names — Rust's Option/Result, pattern matching, even the whole idea of a trait. This first chapter states the throughline that runs through the entire track: in Haskell, a function's type doesn't just describe its inputs and outputs — it tells you whether that function can have side effects at all.
GHC and GHCi
GHC (the Glasgow Haskell Compiler) is the standard toolchain; GHCi is its interactive REPL — similar in spirit to Python's or Node's own REPLs, useful for quick experiments before committing to a real file.
Your First Program
Two lines: a type signature (main :: IO ()) and the actual definition (main = ...) below it. Here's the reveal this whole course is built around: IO () is not decoration or convention — it's a real, compiler-checked part of the type system, stating plainly that main is a computation which may perform IO and produces no meaningful value ((), pronounced "unit"). Every other language covered on this site writes a main with a signature that says nothing at all about what it does — void main(), static void Main(), no signature whatsoever in Python. Haskell's says it, in the type, from line one.
The Type Signature Convention
:: reads as "has type." GHC can infer nearly every type signature on its own — but by strong, near-universal community convention, every top-level function gets an explicit signature anyway, written directly above its definition. This is a real stated best practice, not busywork: it documents intent and catches a mismatch between what a function was meant to do and what it actually type-checks as, immediately, at the point of definition rather than somewhere downstream.
Laziness — Present From Line One
One small preview before Chapter 5's full treatment: [1..] genuinely describes an infinite list, and nothing about writing it crashes the program — Haskell only computes values as they're actually demanded, and take 3 only ever demands three of them. This is the language's default evaluation strategy, not a special trick, and it touches nearly everything from here on.
Running Programs — Compiled or Interpreted
ghc compiles straight to native machine code — no bytecode, no VM, unlike Java's or C#'s own JIT-compiled-bytecode model. runghc feels interpreted for quick iteration, but it's genuinely compiling in memory first; Haskell is always compiled, never truly interpreted the way Python is.
| Language | main's signature | Does it reveal IO capability? |
|---|---|---|
| Java | public static void main(String[] args) | no |
| C# | static void Main() | no |
| Python | no signature at all | no |
| Haskell | main :: IO () | yes — directly, in the type |
Coding Challenges
Write a Main.hs with a main :: IO () that prints two separate lines of your choosing using two putStrLn calls, and run it with runghc.
📄 View solutionIn GHCi, evaluate take 5 [10..] and explain in a comment why this doesn't hang or crash despite [10..] describing an infinite list.
📄 View solutionWrite a short comment contrasting Java's public static void main(String[] args) against Haskell's main :: IO (), explaining specifically what information Haskell's version encodes that Java's does not.
📄 View solutionChapter 1 Quick Reference
- GHC compiles to native code directly; GHCi is the interactive REPL
- main :: IO () — the type signature itself states this computation may perform IO, unlike any other language's own main signature
- :: reads as "has type" — explicit top-level signatures are strong convention even though GHC can infer them
- Haskell is lazy by default — [1..] is a real infinite list, safe because nothing is computed until demanded
- ghc always compiles to native code; runghc feels interpreted but compiles in memory first
- Next chapter: functions and types — Hindley-Milner inference and currying as the default