Skip to content

effekt-community/EffektPy

 
 

Repository files navigation

Note

This is a community-maintained fork of a MIT-licensed student project in the Effective Programming with Effects course in winter semester 2025/2026. Be warned that this is not an officially endorsed project, the code in this repository may be not idiomatic Effekt.

The original repository is https://github.com/DennisKleinhans/EffektPy

EffektPy

EffektPy is a lightweight interpreter for a Python-like programming language, implemented purely in Effekt. It features a fully functioning pipeline including a lexer, parser, desugarer, typechecker, and evaluator, all built using algebraic effects to manage state, environments, and error handling.

Features

  • Python-like Syntax: Familiar syntax with braces {} for blocks.
  • Type Inference: Types for variables and function parameters can often be inferred, making type annotations optional in many cases.
  • Smart Recursion: Support for mutual recursion and nested recursion (functions defined inside other functions).
  • Interactive REPL: A Read-Eval-Print Loop with persistent state, multi-line input support, and smart history.
  • First-Class Functions: Support for closures, higher-order functions, lambdas and default parameter values.
  • Control Flow: if/else, while loops, break, continue, and implicit returns.
  • Built-in Standard Library: Includes essential functions like print, input, and string conversions.
  • Syntactic Sugar: Supports convenient operators like += and -=.

Prerequisites

To build and run this project, you need:

  1. The Effekt Compiler: Install Effekt
  2. Node.js: Required as the JavaScript backend runtime.

Installation & Building

Compile the project to JavaScript:

effekt --compile --backend=js src/main.effekt

Running the Interpreter

Once compiled, the executable JavaScript file is located in the out directory (typically named main__main.js). You use Node.js to execute it.

1. Start the REPL (Interactive Mode): Run without arguments to enter the interactive shell.

node out/main__main.js

2. Execute a File: Pass a filename to execute a specific script.

node out/main__main.js examples/fibonacci.pff

3. Show Help:

node out/main__main.js --help

Language Guide

Variables & Type Inference

You can annotate types explicitly, but EffektPy can also infer them.

// Explicit typing
val pi: Int = 3
var count: Int = 0

// Type Inference (types are automatically detected)
val num = 42
var active = true

// Syntactic sugar for assignment
count += 1

Functions & Recursion

Functions support implicit returns (the last expression is returned) and default parameter values.

// Return type inferred, 'return' keyword omitted
def add(a, b) {
    a + b
}

// Default arguments
def greet(name = "World") {
    print("Hello, " + name)
}

EffektPy handles complex recursion patterns, including mutual recursion (functions calling each other) and nested recursion, without needing forward declarations.

def isEven(n) {
    if n == 0 then true else isOdd(n - 1)
}

def isOdd(n) {
    if n == 0 then false else isEven(n - 1)
}

print(isEven(4)) // true

Functional Programming & Lambdas

EffektPy treats functions as first-class citizens, meaning they can be passed as arguments, returned from functions, and assigned to variables.

Anonymous Functions (Lambdas)

You can define functions without a name using the lambda keyword. These are ideal for use with Higher-Order Functions (HOFs).

// Passing a lambda directly to a HOF
def applyOp(a, b, op) { op(a, b) }

val result = applyOp(5, 5, lambda(x, y) { x * y }) // 25

Closures & Lexical Scoping

Lambdas capture their surrounding environment. This allows for powerful patterns like factory functions.

def makeMultiplier(factor) {
    // The inner function 'remembers' factor
    lambda(n) { n * factor }
}

val double = makeMultiplier(2)
print(double(10)) // 20

Type Inference for HOFs

EffektPy's type inference is powerful enough to handle most Higher-Order Functions (HOFs) automatically.

// Type inference works even for HOFs 
def apply(f, x) { 
	f(x) 
} 
def increment(n) { n + 1 } 
apply(increment, 10) // Returns 11

Note on Advanced Types: While inference works for standard cases, explicit type annotations are required for complex scenarios such as Rank-2 types (functions that take polymorphic functions as arguments) or self-application patterns.

Standard Library (Built-ins)

The interpreter comes with a set of built-in functions available in the global scope:

  • print(args...): Prints values to the console (accepts variable arguments of different types).
  • input(prompt = ""): Reads a string from standard input.
  • str(value): Converts any value to its string representation.
  • min(a, b, ...) / max(a, b, ...): Returns the minimum or maximum of at least two numbers.

The REPL

The REPL supports incremental type checking and evaluation. It features a brace-counting heuristic to support multi-line input.

> def fib(n) {
...   if (n <= 1) { n }
...   else { fib(n-1) + fib(n-2) }
... }
> fib(10)
55

About

A tiny Python-inspired functional language with static type inference, implemented and evaluated in Effekt using effects and handlers; originally DennisKleinhans/EffektPy

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Nix 100.0%