logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·30.10.26
← Back to news

Making a GTK application in Haskell, part 1

Original article
floreal.tech|74 points|8 comments|by Vosporos|Oct 5, 2026

Developing a GTK Application with Haskell: Part 1

Authored by Feriel Choutri de Tarlé (Floréal Technologies) Date: 2026-10-05

In this introductory guide, we embark on a journey to construct a todo-list application. Our technical stack consists of Haskell, GTK 4, and the Adwaita library.

Target Audience: This tutorial is designed for intermediate Haskell developers who already possess a working knowledge of the language.


🎨 Understanding Adwaita

Adwaita is more than just a library; it is the foundational design language for the GNOME project.

Essentially, any decision regarding style or accessibility—as defined by the Human Interface Guidelines (HIG)—is baked directly into libadwaita. By utilizing this library, you gain access to powerful features such as:

  • Responsive Design: Layouts that adapt to window size.
  • Dynamic Theming: Automatic switching between light and dark modes at runtime.

🛠️ Integrating Haskell and GTK

To bridge the gap between Haskell and the C-based GTK libraries, we utilize the haskell-gi toolkit. This tool automatically generates Haskell bindings, providing an interface that remains recognizable to those familiar with the original C API.

Note: To maintain clarity and focus on core concepts, the code snippets provided here are conceptual and not fully exhaustive. For the complete implementation, visit the official GitHub repository.

Creating Your First Window

Below is a foundational example demonstrating how to structure a GTK 4/Adwaita application. We start by initializing the Application object, which manages resources and stylesheets.

module Main ( main ) where

import qualified GI.Awd as Adw
import qualified GI.Gio as Gio
import qualified GI.GTK as Gtk

main :: IO ()
main = do 
    -- Create the application with a unique identifier
    app <- Adw.Application [ #applicationId := "tech.floreal.TodoApp" ]
    
    -- Link the 'activate' signal to our handler
    on app # activate ( activate app )
    
    -- Launch the main event loop
    Adw.Application.run app []

activate :: Adw.Application -> IO ()
activate app = do
    -- Define the main window properties
    window <- Adw.ApplicationWindow [ 
        # application := app, 
        # title := "Todos", 
        # defaultWidth := 480, 
        # defaultHeight := 640, 
        # content := toolbar 
    ]
    Gtk.Widget.show window

The result is a window titled "Todos" with a fixed initial size of 480×640480 \times 640480×640 pixels.


🔄 Architecture: Model-View-Update (MVU)

To manage the state of our application, we adopt The Elm Architecture (TEA). This pattern is particularly effective for fighting taming the imperative nature of GTK.

Core Concepts of TEA

ConceptDescription
ModelThe singular source of truth representing the application state.
ViewA pure function that transforms the Model→UI\text{Model} \rightarrow \text{UI}Model→UI.
UpdateA function that evolves the Model based on incoming Messages.
MessageA sum type representing every possible user interaction.
EffectsSide-effecting actions (like I/O) triggered by the Update function.

The MVU Lifecycle


📝 Implementing the Todo App

We will model our application using strict data structures to ensure total visibility over state transitions.

1. The Model

We define our identity and data types as follows:

newtype TodoId = TodoId Word 
    deriving stock ( Show ) 
    deriving newtype ( Eq , Ord )

data Todo = Todo 
    { id    :: TodoId 
    , title :: Text 
    , done  :: Bool 
    } deriving stock ( Eq , Show )

data Model = Model 
    { todos  :: Map TodoId Todo 
    , nextId :: TodoId 
    } deriving stock ( Eq , Show )

-- Effects represent external actions, e.g., persistence
data Effect = Save [ Todo ] 
    deriving stock ( Eq , Show )

init :: Model
init = Model { todos = Map.empty, nextId = TodoId 0 }

2. The Messages

User actions are captured as a sum type:

data Message 
    = Add Text 
    | SetDoneStatus TodoId Bool 
    deriving stock ( Eq , Ord )

3. The Update Logic

The update function follows the mathematical form: f(Message,Model)→(Model,[Effect])f(\text{Message}, \text{Model}) \rightarrow (\text{Model}, [\text{Effect}])f(Message,Model)→(Model,[Effect])

update :: Message -> Model -> ( Model , [ Effect ])
update message model = case message of 
    Add raw -> 
        let text = Text.strip raw
            todoId = nextId model
            newTodo = Todo { id = todoId, title = text, done = False }
            (n) = let (TodoId v) = todoId in v
        in if Text.null text 
           then (model, []) 
           else withTodos (Map.insert todoId newTodo) 
                model { nextId = TodoId (n + 1) }

    SetDoneStatus todoId value -> 
        withTodos (Map.adjust (\todo -> todo { done = value }) todoId) model
  where 
    -- Helper to trigger Save effect only if data actually changes
    withTodos f changed = 
        let result = changed { todos = f (todos changed) }
        in if result == changed 
           then (changed, []) 
           else (result, [ Save (Map.elems (todos result)) ])

🧪 Testing in GHCi

We can verify the domain logic using the REPL:

$ cabal repl
ghci> let (m1, e1) = update (Add "Buy leeks") init
ghci> e1
[Save [Todo {id = TodoId 0, title = "Buy leeks", done = False}]]

ghci> let (m2, e2) = update (SetDoneStatus (TodoId 0) True) m1
ghci> e2
[Save [Todo {id = TodoId 0, title = "Buy leeks", done = True}]]

-- Redundant updates produce no effects
ghci> update (SetDoneStatus (TodoId 0) True) m2
(Model {nextId = TodoId 1, todos = ...}, []) -- Empty effect list!

🖼️ The View

Before coding the UI, it is vital to plan the user experience.

"From experience, design does not immediately follow from data; I avoid letting the shape of my data dictate my design."

Required Widgets

To build this interface, we will utilize the following GTK components:

WidgetPurposeVisual Reference
BoxOrganizes children linearly (rows/columns).View Image
ListBoxA dynamic list for filtering and sorting.View Image
EntryA single-line text input field.View Image

Progress Checklist

  • Define Application Structure
  • Implement Model
  • Implement Update Logic
  • Build the View (Part 2)