Haskell で GTK アプリケーションを作成する、第 1 部

2026/10/05 23:23

Haskell で GTK アプリケーションを作成する、第 1 部

RSS: https://news.ycombinator.com/rss

要約▶

Japanese Translation:

このテキストは、GTK 4、Adwaita デザイン言語、および

libadwaita
を用いて Haskell でレスポンシブなタスクリストアプリケーションを構築する一連の内容を紹介しています。主な目的は、開発者が強力なクロスプラットフォームツールキットを活用しながら、GNOME ガイドラインに厳密に従いアクセシビリティを保証されたアプリケーションを作成する方法を実証することです。本プロジェクトでは
haskell-gi
を用いて C API バインディングを効率的にアクセスし、GTK ライブラリと互換のある Haskell コードを生成します。内部では Elm アーキテクチャ(TEA パターン)に従っており、メッセージに基づいて状態変化を管理する
update
関数を実装するとともに、効果を通じて副作用処理を行います。インターフェースは標準 GTK コンポーネントである
ListBox
、
ScrolledWindow
、
Box
、
ToolbarView
、および
HeaderBar
を使用して一般的なものを提供しています。本シリーズでは実行時のテーマ切り替えと完全なアクセシビリティ準拠を備えており、Haskell コミュニティへの基盤となるリファレンスとなっています。完整プロジェクトは https://github.com/Floreal-Technologies/adwaita-todo で利用可能です。Part 2 が次期リリースされる予定です。本シリーズはこれらの初期段階を進化させ、完全な機能セットへと拡張することを約束しています。最終的に、このオープンソースイニシアチブは、アクセシビリティを重視するツールを求めつつあるユーザーと、Haskell 内での GTK エコシステムを探求する開発者双方に対して、貴重な洞察とコード例を提供します。

本文

Haskell & GTK 4 / Adwaita でタスクリストアプリを開発する

本シリーズでは、Haskell、GTK 4、および Adwaita ライブラリを使用して高品質なタスクリストアプリケーションを構築していきます。 Adwaita は、GNOME デフォルトのデザイン言語を提供し、アクセシビリティとスタイルに関する決定(ヒューマン・インターフェースガイドライン)をすべて実装しています。

対象読者

  • 前提知識: Haskell の開発経験があること。
  • レベル: 中級者向け。
  • ツール:
    libadwaita
    を利用したレスポンシブデザインとテーマ対応(ライト/ダーク)の実装を学べます。

GTK 4 と Adwaita

Adwaita は GNOME プロジェクトのデザイン言語として機能する、GTK コンポーネントのライブラリです。 GNOME におけるすべての UI デシジョンは

libadwaita
に符号化されています。主な特徴は以下の通りです:

  • レスポンシブデザイン: ランタイムでデスクトップのテーマ(ライト/ダーク)に応じた再描画が可能。

Haskell と GTK の連携

本シリーズでは、GTK の C API から Haskell バインディングを自動生成する

haskell-gi
ツールキットを使用します。これにより、C API と親和性を保ちつつも Haskell 独自インターフェースで開発できます。

注: この記事内のコードは主要な概念を示すため、完全なプロジェクトではありません。 完全なソースコードは GitHub リポジトリ から入手可能です。

最初のウィンドウ作成

まず、GTK 4 / Adwaita アプリケーションの最小限の構造を構築します。 以下のコードでは、リソース管理やスタイルシートを含んだ

Adw.Application
を作成し、空のウィンドウを表示します。

module Main (main) where

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

main :: IO ()
main = do
  -- `new X [#attribute := value]` は、OverloadedLabels を使用したプロパティ設定です。
  app <- new Adw.Application [#applicationId := "tech.floreal.TodoApp"]
  
  on app #activate (activate app)
  
  Gio.applicationRun app Nothing
  pure ()

activate :: Adw.Application -> IO ()
activate app = do
  header <- new Adw.HeaderBar []
  toolbar <- new Adw.ToolbarView []
  Adw.toolbarViewAddTopBar toolbar header
  
  window <- 
    new Adw.ApplicationWindow
      [ #application := app
      , #title := "Todos"
      , #defaultWidth := 480
      , #defaultHeight := 640
      , #content := toolbar
      ]
  
  Gtk.windowPresent window

これで、タイトル「Todos」、幅 480px、高さ 640px の空のウィンドウが表示されます。


モデル・ビュー・アップデート(Elm アーキテクチャ)

インタラクティブなアプリケーション設計のパターンである Elm アーキテクチャ(または TEA: The Elm Architecture)を採用します。

3 つの核心概念

  1. Model: アプリケーションの状態を保持するデータ構造。
  2. View:
    Model
    をユーザーインターフェース(GTK ウィジェットなど)に変換する関数。
  3. Update: メッセージに基づいて
    Model
    を更新し、新しい状態と副作用(Effects)のリストを返す関数。

追加概念

  • Message: ユーザーとアプリケーションの相互作用を表す列挙型。
  • Effects: 実行すべきアクション(例:ディスク保存)のリスト。
    • Update
      関数は更新されたモデルに加え、同時に実行するアクションのリストも返します。

このアプローチは命令型の GTK ツールキットでも十分に制御可能です。


タスクリストアプリの実装

アプリケーションの状態とアクションをデータ構造としてモデル化します。これにより、変更のトリガー内容が完全に可視化できます。

データ定義:Model

-- タスク ID
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)

-- 副作用(エフェクト):タスクリストの保存
data Effect = Save [Todo]
  deriving stock (Eq, Show)

-- 初期状態
init :: Model
init = Model
  { todos = Map.empty
  , nextId = TodoId 0
  }

データ定義:Message

ユーザー操作を表すメッセージです。

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

メソッド:Update(状態更新)

update
関数は、メッセージを受け取り
(新しいモデル,エフェクトリスト)
を返します。

update :: Message -> Model -> (Model, [Effect])
update message model = case message of
  Add raw -> 
    let text = Text.strip raw
        todoId@(TodoId n) = model.nextId
        todo = Todo{ id = todoId, title = text, done = False }
    in if Text.null text
       then (model, []) -- 空文字は無視
       else
         withTodos
           (Map.insert todo.id todo)
           model{ nextId = TodoId (n + 1)}
          
  SetDoneStatus todoId value ->
    withTodos 
      (Map.adjust (\todo -> todo {done = value}) todoId)
      model

 where
  -- 状態が変更された場合のみ保存エフェクトをトリガー
  withTodos f changed =
    let result = changed {todos = f changed.todos}
    in if result.todos == model.todos
       then (result, [])
       else (result, [Save (Map.elems result.todos)])

GHCi での動作確認例

以下のコマンドで動作を確認できます(

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}]]

-- 既存の状態への変更(保存不要)
ghci> update (SetDoneStatus (TodoId 0) True) m2
(Model{ ... }, []) -- ← エフェクトリストは空!

このロジックでドメイン層が完成しました。次に、UI を構築します。


ビュー(表示層)の設計

使用されるウィジェット

Adwaita と GTK 4 で利用可能な主要なコンポーネントです:

  • Box: 子ウィジェットを横並びまたは縦並びに配置するレイアウトマネージャー。
  • ListBox: 動的にフィルタリング/ソート可能な行のリスト。
  • EntryRow: テキスト入力フィールドを持つ ListBox のサブクラス(編集可能)。
  • ActionRow: 編集不可能な行。アクションアイコンを表示可能。
  • Clamp: 子ウィジェットを指定サイズに制約し、マージンを維持するコンテナ。
  • ScrolledWindow: スクロール可能なウィンドウ。
  • ToolbarView: ヘッダーやフッターを含むビュー構造。

レイアウト構成(Widget Tree)

以下のコードは、入力行とタスクリストを組み合わせ、スクロール可能エリア内に配置し、ヘッダー/フッターで包み込む処理です。

-- 表示層の主要関数
view
  :: (Message -> IO ()) -- メッセージディスパッチャー
  -> Model              -- 現在の状態
  -> IO Gtk.Widget      -- ウィジェットツリーを返す

view dispatch model = do
  -- [1] 入力エリアの作成とイベントハンドラ設定
  inputRow <- new Adw.EntryRow [#title := "New task"]
  on entry #entryActivated $ do
    text <- Gtk.editableGetText inputRow
    dispatch (Add text)

  entryBox <- newBoxedList 
  Gtk.listBoxAppend entryBox inputRow

  -- [2] タスクリストの作成(Model からリストを生成)
  todoList <- newBoxedList
  forM_ (model.todos) $ \todo -> do
    row <- new Adw.ActionRow [#title := todo.title, #useMarkup := False]
    Gtk.listBoxAppend todoList row

  -- [3] コンテンツエリアのレイアウト設定
  content <-
    new Gtk.Box
      [ #orientation := Gtk.OrientationVertical
      , #spacing := 12
      , #marginTop := 12
      , #marginBottom := 12
      , #marginStart := 12
      , #marginEnd := 12
      ]
  
  Gtk.boxAppend content entryBox
  Gtk.boxAppend content todoList

  -- [4] スクロール可能なメインウィンドウ
  clamp <- new Adw.Clamp [#child := content]
  scrolled <- new Gtk.ScrolledWindow [ #child := clamp ]

  -- [5] フッターエリア(タスク数表示など)
  count <- new Gtk.Label [#label := Text.show (Map.size model.todos)]
  footer <- new Gtk.Box 
    [ #orientation := Gtk.OrientationHorizontal
    , #marginTop := 6
    , #marginBottom := 6
    ]
  Gtk.boxAppend footer count

  -- [6] ツールバーとヘッダーの統合
  header <- new Adw.HeaderBar []
  toolbar <- new Adw.ToolbarView [#content := scrolled]
  Adw.toolbarViewAddTopBar toolbar header
  Adw.toolbarViewAddBottomBar toolbar footer

  pure $ Gtk.toWidget toolbar

-- ListBox のヘルパー関数
newBoxedList :: IO Gtk.ListBox
newBoxedList = 
  new Gtk.ListBox
    [ #selectionMode := Gtk.SelectionModeNone
    , #cssClasses := ["boxed-list"]
    ]

ランタイム(Runtime)の構築

モデル・ビュー・アップデート(MVU)サイクルを接続するランタイムモジュールです。

  • dispatch
    : メッセージを受け取り、
    step
    を呼び出して処理を実行します。
  • step
    :
    1. 現在のモデルを読み取る。
    2. update
      で状態変更とエフェクトを計算。
    3. 新しいモデルを書き込む。
    4. 新しいモデルを使って
      view
      を実行し、新しいウィジェットを生成。
    5. ウィンドウのコンテンツを更新。
module Runtime where

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

-- メッセージディスパッチャ(非同期処理用)
dispatch :: Message -> IO ()
dispatch message =
  void $ GLib.idleAdd GLib.PRIORITY_DEFAULT $ do
    step message
    pure GLib.SOURCE_REMOVE

-- ステップ処理のメインロジック
step :: Message -> IO ()
step message = do
  oldModel <- readIORef ref -- 参照モデルを読み取り
  
  -- (モデル更新とエフェクトをここで実行)
  let (newModel, _effects) = Model.update message oldModel
  
  -- 状態を書き込み
  writeIORef ref newModel
  
  -- 新しいビューを描画
  content <- View.view dispatch newModel
  
  -- ウィンドウコンテンツを更新
  Adw.applicationWindowSetContent window (Just content)

run :: Adw.Application -> IO ()
run app = do
  window <-
    new Adw.ApplicationWindow
      [ #application := app
      , #title := "Todos"
      , #defaultWidth := 480
      , #defaultHeight := 640
      ]
  
  -- メモリ参照を作成
  ref <- newIORef Model.init
  
  let {- rec -} -- 相互再帰型定義(rec)を使用
        dispatch :: Message -> IO ()
        dispatch message = void $ GLib.idleAdd GLib.PRIORITY_DEFAULT $ do
          step message
          pure GLib.SOURCE_REMOVE
        
        step :: Message -> IO ()
        step message = do
          oldModel <- readIORef ref
          let (newModel, _effects) = Model.update message oldModel
          writeIORef ref newModel
          content <- View.view dispatch newModel
          Adw.applicationWindowSetContent window (Just content)

  -- 初期化表示
  content <- View.view dispatch Model.init
  Adw.applicationWindowSetContent window (Just content)
  
  Gtk.windowPresent window

最終的な Main モジュール

上記のランタイムを統合した、プロジェクトのエントリーポイントです。

module Main where

import Runtime
import GI.Adw qualified as Adw
import GI.Gio qualified as Gio

main :: IO ()
main = do
  app <- new Adw.Application
     [#applicationId := "tech.floreal.AdwaitaTodo"]
  
  on app #activate (Runtime.run app)
  
  Gio.applicationRun app Nothing
  pure ()

これで、本番動作するタスクリストアプリケーションが完成しました。 とても素晴らしい成果です!

このシリーズの Part 2 では、さらに高度な機能を実装していく予定です。楽しみにしていてください。

同じ日のほかのニュース

一覧に戻る →

2026/10/06 4:16

Beam:反射の 501B オープンウェイトモデル

## Japanese Translation: 要約は主なナラティブを十分に捉えていますが、この文脈においてモデルの重要性を規定するハードウェア規模およびデータカurationに関するいくつかの定量的詳細が含まれていません。改良版は、具体的なGPU数、トレーニング環境の性質(サンドボックス)、明示的なデータフィルタリング統計を統合し、それがなぜ効率的かつ強力なのかというより完全な図像を提供する必要があります。 以下に、欠落している要素を取り入れながら流れを維持した改善された要約を示します: **改良された要約:** Reflection は、高効率なコーディング、複雑な推論、自律エージェントタスク向けに設計された最初のオープンウェイトスパースミキサー-of-エキスパート(MoE)モデル「Beam」を発表しました。全パラメータ数は Massive な 5010 億ですが、トークンあたり有効なのは 230 億のみであり、Beam は推論コストを劇的に削減しつつ、競合他社である GLM 5.2 と同等かそれ以上の性能を示し、特定のエージェントベンチマークでは Qwen 3.8-Max に迫ります。この効率性は、厳格な事前トレーニング(生データの約 95% が選別された 238 兆トークン)、ならびに 13 億個のサンドボックスを使用した 1 万台超の NVIDIA GB300 GPU クラスタ上で実施した先進的な強化学習によって実現されています。モデルは印象的な汎化性能を示し、ウェブブラウジングなどのツール使用スキルを明示的なトレーニングなしで習得します。来月後期に Apache 2.0 ライセンスの下で公開予定(早期アクセスはウェイトリスト経由)であり、「Reasoning Effort」パラメータを搭載して応答長とタスク複雑性のバランスを最適化しています。特に、トレーニング中での拡張により有効コンテキストを 100 万トークンまで引き上げられ、オープンウェイトモデルの効率性における重要なマイルストーンとなっています。

2026/10/06 6:00

Opus 5.5 エージェントが室温で機能する磁性半導体の候補物質 2 つを発見

## Japanese Translation: 研究者らは、反強磁性とスピンソート機能を統合する有望なルティンガー補償磁性体の候補を 2 つ同定した。これらは外部磁場なしで高密度メモリに既に利用可能な次世代スピントロニクス材料の存在を示唆している。候補 1 の YBaMnFeO₅ は AI エージェントによって設計されたものであり、イットリウム、バリウム、マンガン、鉄、酸素の 5 つの元素からなる化合物である。予測されるバンドギャップは 2.35 eV、正孔のスピンウィンドウは 1.0 eV、電子のスピンウィンドウは 1.4 eV であった。シミュレーションでは磁性が約 490 K に及ぶと予測されるが、原子チェッカーボード構造が高熱合成(900–1300 °C)において不安定化しうるため、特別な手法なしでこの温度域での標準的な高温合成を困難にしている。候補 2 の KV[Cr(CN)₆] は、1999 年に最初に見出されたプルussian ブルー族に属する既存化合物であり、半導体としてのスピン特性が以前見過ごされていた。クロムはシアン化物基(炭素で終端しており、バナジウムは窒素で終端する)に結合している。予測されるバンドギャップは 2.1 eV、正孔のスピンウィンドウは 2.6 eV、電子のスピンウィンドウは 1.6 eV であった。完全な結晶ではゼロの全スピンモーメントを持つと予測されるが、1999 年の粉体試料は閉じ込められた水の影響により約 365 K まで磁気秩序を保持しつつも、わずかな残留モーメント(0.125 ボアマグネトン)を示した。密度汎関数理論を用いた量子力学シミュレーションにより、両材料のこれらの特性が確認された。今後の研究では、KV[Cr(CN)₆] の純度を向上させて水起因のアーティファクトを排除し、YBaMnFeO₅ の脆い格子構造を安定化させる合成戦略の改善に焦点を当て、実用的かつ効率的な磁気メモリソリューションへの明確な経路を提供する。

2026/10/06 6:15

Dust:バックプロパゲーションなしでトランスフォーマーを事前学習する手法

## Japanese Translation: Dust は、Transformer リンゲージモデルの事前学習において逆伝播と競合するゼロ次最適化手法です。重みそのものを更新する代わりに、Dust はすべてのトークンごとに活性化空間への擾乱を適用し、「仮想集団」を生成します。これにより、単一の順方向パス内で数千の仮想モデルを並列評価でき、モデル重みの複雑な変化を実体化する必要がありません。EGGROLL-Transformer といった基準手法と比較して、100 万トークンのトークン予算以上においては、1,000 倍から 10,000 倍の効率向上を実現します。従来の常識とは対照的に、大規模モデル(最大 2.43 億パラメータ)は小規模モデルよりも集団効率が良く、2.43 億パラメータを持つモデルは、多くの集団サイズにおいて 120 倍小さいモデルを上回ります。Dust の勾配推定値は逆伝播と良好に一致し、集団サイズが増加するにつれて改善され、10 億トークンに至るまで強い一貫性を保ちます。この手法は、順方向パス間でレイヤ種をジッターさせ、また出力勾配の推定値を用いて直接のトークン損失ではなく注意内部をスコアリングすることで、擾乱間の相互干渉を低減します。ノイズスケールやクレジット減衰などの超パラメータは、学習なしで逆伝播勾配への余弦類似度を最大化するようにグリッド検索によってチューニング可能であり、トークン予算と集団サイズを跨いで汎化します。10 万から 2,000 万トークン、集団サイズ 64 から 1.6 万にわたる実験において、Dust は大規模のトークン予算下で集団サイズが増加するに伴い逆伝播とのギャップを縮めていることが示されています。Adam オプティマイザ(すべての手法向けに再チューニング)を用いても Dust は有効であり、低数のトークンでは理論的な限界が逆伝播を下回るものの、規模が大きくなるにつれて競合可能な性能を発揮します。大規模モデルは、より大きな探索空間とより良好な条件の損失地形幾何学により、集団サイズの増加からより多くの恩恵を受けます。Dust の推定値と逆伝播勾配の間の余弦類似度は、低 RMSE(< 0.06)で複数のレイヤ種に適合するべき乗律に従います。これらの進展は、高度な事前学習技術をよりアクセスしやすくし、専門的な訓練手順を必要としないまま大規模 AI 開発の民主化を促進します。

Haskell で GTK アプリケーションを作成する、第 1 部 | そっか~ニュース