Haskell Quickstart
Get up and running with EiplGrader for Haskell in minutes.
Prerequisites
- GHC (Glasgow Haskell Compiler) 8.0+
- Python 3.7+ (for running EiplGrader)
- OpenAI API key (or compatible LLM API)
Installation
pip install eiplgrader
Basic Example
1. Generate Haskell Code
from eiplgrader.codegen import CodeGenerator
# Initialize the code generator for Haskell
api_key = "your-openai-api-key"
generator = CodeGenerator(api_key, client_type="openai", language="haskell")
# Generate code from a student's explanation
result = generator.generate_code(
student_response="that doubles each element in a list",
function_name="doubleList",
gen_type="cgbg"
)
print("Generated code:")
print(result["code"][0])
Output:
doubleList :: [Int] -> [Int]
doubleList xs = map (*2) xs
2. Test the Generated Code
from eiplgrader.tester import CodeTester
# Define test cases - Haskell REQUIRES explicit type annotations
test_cases = [
{
"parameters": {"xs": [1, 2, 3, 4, 5]},
"parameter_types": {"xs": "[Int]"}, # Haskell list type
"expected": [2, 4, 6, 8, 10],
"expected_type": "[Int]"
},
{
"parameters": {"xs": []},
"parameter_types": {"xs": "[Int]"},
"expected": [],
"expected_type": "[Int]"
},
{
"parameters": {"xs": [-1, 0, 1]},
"parameter_types": {"xs": "[Int]"},
"expected": [-2, 0, 2],
"expected_type": "[Int]"
}
]
# Create and run the tester
tester = CodeTester(
code=result["code"][0],
test_cases=test_cases,
function_name="doubleList",
language="haskell"
)
results = tester.run_tests()
print(f"Tests passed: {results.successes}/{results.testsRun}")
Haskell-Specific Features
Pure Functional Programming
Haskell is purely functional, so all functions are pure by default:
# Generate pure functions
result = generator.generate_code(
student_response="that calculates factorial using recursion",
function_name="factorial"
)
# Output includes type signature
# factorial :: Int -> Int
# factorial 0 = 1
# factorial n = n * factorial (n - 1)
Type Annotations are REQUIRED
Haskell requires explicit type information for test cases:
# CORRECT - with type annotations
test_case = {
"parameters": {"x": 5, "y": 3},
"parameter_types": {"x": "Int", "y": "Int"}, # Required!
"expected": 8,
"expected_type": "Int" # Required!
}
# WRONG - missing type annotations
test_case = {
"parameters": {"x": 5, "y": 3},
"expected": 8
# This will fail with: "Missing required type information"
}
Haskell Type Mappings
Generic Type | Haskell Type | Example |
---|---|---|
int | Int | 42 |
double | Double | 3.14 |
string | String | "hello" |
bool | Bool | True |
List[int] | [Int] | [1, 2, 3] |
List[string] | [String] | ["a", "b"] |
Type Signatures
Generated Haskell code includes type signatures:
-- Function with multiple parameters
add :: Int -> Int -> Int
add x y = x + y
-- Function with lists
sumList :: [Int] -> Int
sumList xs = sum xs
-- Function with strings
greet :: String -> String
greet name = "Hello, " ++ name
Common Haskell Patterns
List Comprehensions
result = generator.generate_code(
student_response="that generates all even squares up to n",
function_name="evenSquares"
)
test_cases = [
{
"parameters": {"n": 20},
"parameter_types": {"n": "Int"},
"expected": [4, 16],
"expected_type": "[Int]"
}
]
Higher-Order Functions
result = generator.generate_code(
student_response="that filters a list keeping only positive numbers",
function_name="keepPositive"
)
test_cases = [
{
"parameters": {"nums": [-2, -1, 0, 1, 2, 3]},
"parameter_types": {"nums": "[Int]"},
"expected": [1, 2, 3],
"expected_type": "[Int]"
}
]
Pattern Matching
result = generator.generate_code(
student_response="that returns the head of a list or a default value if empty",
function_name="safeHead"
)
test_cases = [
{
"parameters": {"xs": [1, 2, 3], "default": 0},
"parameter_types": {"xs": "[Int]", "default": "Int"},
"expected": 1,
"expected_type": "Int"
},
{
"parameters": {"xs": [], "default": 0},
"parameter_types": {"xs": "[Int]", "default": "Int"},
"expected": 0,
"expected_type": "Int"
}
]
Recursion
result = generator.generate_code(
student_response="that calculates the nth Fibonacci number",
function_name="fibonacci"
)
test_cases = [
{
"parameters": {"n": 0},
"parameter_types": {"n": "Int"},
"expected": 0,
"expected_type": "Int"
},
{
"parameters": {"n": 10},
"parameter_types": {"n": "Int"},
"expected": 55,
"expected_type": "Int"
}
]
Working with Different Types
Strings
result = generator.generate_code(
student_response="that reverses each word in a sentence",
function_name="reverseWords"
)
test_cases = [
{
"parameters": {"sentence": "Hello World"},
"parameter_types": {"sentence": "String"},
"expected": "olleH dlroW",
"expected_type": "String"
}
]
Tuples
# Note: Tuple handling is limited in test cases
result = generator.generate_code(
student_response="that returns the minimum and maximum of a list",
function_name="minMax"
)
# Test the first element of the tuple
test_cases = [
{
"parameters": {"xs": [3, 1, 4, 1, 5]},
"parameter_types": {"xs": "[Int]"},
"expected": 1, # Testing minimum only
"expected_type": "Int"
}
]
Custom Types
# Basic types work best with the test harness
result = generator.generate_code(
student_response="that counts occurrences of each element",
function_name="countElements"
)
# Return as a list of pairs rather than custom types
test_cases = [
{
"parameters": {"xs": [1, 2, 2, 3, 3, 3]},
"parameter_types": {"xs": "[Int]"},
"expected": [[1, 1], [2, 2], [3, 3]],
"expected_type": "[[Int]]"
}
]
Lazy Evaluation
Haskell’s lazy evaluation allows working with infinite lists:
result = generator.generate_code(
student_response="that takes the first n prime numbers",
function_name="firstPrimes"
)
test_cases = [
{
"parameters": {"n": 5},
"parameter_types": {"n": "Int"},
"expected": [2, 3, 5, 7, 11],
"expected_type": "[Int]"
}
]
Module Structure
The test harness creates a complete Haskell module:
module Main where
-- Your generated function
doubleList :: [Int] -> [Int]
doubleList xs = map (*2) xs
-- Test harness main function
main :: IO ()
main = do
-- Test execution code
print (doubleList [1, 2, 3])
Best Practices
- Use explicit type signatures:
# Always include Haskell types "parameter_types": { "xs": "[Int]", # List of Int "x": "Int", # Single Int "str": "String", # String type "flag": "Bool" # Boolean }
- Prefer simple types:
# Stick to basic types for test cases # Good: Int, Double, String, Bool, [Int], [String] # Avoid: Complex ADTs, IO types, Monads
- Functional style:
-- Generated code should use: -- map, filter, fold -- Pattern matching -- Guards -- List comprehensions
- Handle edge cases functionally:
# Empty lists, zero values test_cases = [ {"parameters": {"xs": []}, "expected": 0}, {"parameters": {"n": 0}, "expected": 1} ]
Common Gotchas
- No side effects: Haskell functions must be pure
# Cannot test functions with IO or side effects # Stick to pure transformations
- Type inference: Test harness requires explicit types
# Even though Haskell can infer types, # test cases must specify them explicitly
- Infinite lists: Be careful with lazy evaluation
# Always use 'take' or similar to limit infinite lists
- String as [Char]: Remember String is a list
-- String is [Char] in Haskell -- This affects some operations
Function Name Detection
The executor can automatically detect function names from type signatures:
# If function_name is not specified, it's extracted from the code
result = generator.generate_code(
student_response="that zips two lists together",
gen_type="cgbg"
# function_name omitted - will be detected
)
Compilation and Execution
- Compilation:
ghc -o output input.hs
- Module: Always creates
module Main
- Entry point:
main :: IO ()
added automatically
Error Handling
Haskell provides detailed type errors:
try:
results = tester.run_tests()
if not results.was_successful():
for result in results.test_results:
if not result["pass"]:
print(f"Test failed: {result['function_call']}")
# Common Haskell errors:
# - Type mismatches
# - Pattern match failures
# - Undefined functions
except Exception as e:
print(f"Error: {e}")
Advanced Example
# Generate a more complex function
result = generator.generate_code(
student_response="""that implements quicksort algorithm
for a list of integers""",
function_name="quicksort"
)
test_cases = [
{
"parameters": {"xs": [3, 1, 4, 1, 5, 9, 2, 6]},
"parameter_types": {"xs": "[Int]"},
"expected": [1, 1, 2, 3, 4, 5, 6, 9],
"expected_type": "[Int]"
},
{
"parameters": {"xs": []},
"parameter_types": {"xs": "[Int]"},
"expected": [],
"expected_type": "[Int]"
},
{
"parameters": {"xs": [1]},
"parameter_types": {"xs": "[Int]"},
"expected": [1],
"expected_type": "[Int]"
}
]
Next Steps
- Compare with Go Quickstart for another functional-friendly language
- Learn about Test Case Format for complex types
- See Language Support for Haskell-specific details
- Explore functional patterns in Advanced Features