NixOS で 以前のシステムに戻す方法

コンフィグレーションファイル

/etc/nixos/configuration.nix

をかえたりした後

sudo nixos-rebuild --update

などとしてシステムを更新したら
再起動後 うまく動かなくなった
そのやうなときに 前のシステムに戻す必要がある

これについて 前より少し分かるやうになったので 書きとめておく

ブートローダーで
All Configuration みたいな項目から 以前の設定を持ったシステムを選択する

無事に起動できたらターミナルで 次のやうに入力する

sudo nix-env --profile /nix/var/nix/profiles/system --list-generations

これで出てくるリストが ブートローダーで表示されてゐた Configurationのリストと一致してゐる
おそらく 問題のある設定(一番最後だと思う)には (Current) と表示されてゐるはずだ
この状態だと 再起動したときに また問題のある設定をもったシステムが起動されてしまふ

そこで 現在の設定(ちゃんと操作ができるシステム) を デフォルトの設定へと切り換える
さきほどのリストには 左端に番号がついてゐるので ちゃんと操作ができる設定の番号を確認する(例として これが33番だとする)

sudo nix-env --profile /nix/var/nix/profiles/system --switch-generation 33

これで まう一度

sudo nix-env --profile /nix/var/nix/profiles/system --list-generations

とすると (Current) の表示が 33番のところに切りかわってゐる
「Current」が取れたシステム設定は 次のやうに消すことができる (ここでは34番だとしやう)

sudo nix-env --profile /nix/var/nix/profiles/system --delete-generations 34

そして 最後に 變更した設定を システムに反映させ grub を更新する

sudo nixos-rebuild switch

これをやらないと nix-env で設定を變えても また以前の設定で起動されてしまふ
これで再起動すれば OKだ

ちなみに

--profile /nix/var/nix/profiles/system

の部分を

--profile /nix/var/nix/profiles/per-user/root/channels

にしても同様の処理ができる(と思ふ)

--追記:
通常システム設定を變更するのに configuration.nix を書きかえた後
sudo nixos-rebuild switch --upgrade としてシステムをアップデートする
このとき私の場合 アップデート後に動作不良となってしまった(画面が映らない・キーボード・マウス反応せず)
これで前のシステムに戻す必要が生じた
おそらく 自分の使ってゐる 古いビデオカード(nvidia GeForce GTS 450) が nixOS では legacy 390 といふドライバに對應するみたいなのだが それがアップデートされたせいで生じた不具合だと思ってゐる
それだけアップデートされないやうにする方法があると思ふので 今後模索してみる

NixOS + haste-complier

なんだらう
ちょっといま いい氣分なので 少し書かうかな と思ふ


hasteの入れかた

ちなみに ここは書いてゐるうちに 追加しやうと思ったのだが
現在hasteを入手するには

sourceforge.net

でダウンロードして
linuxの環境下で 解凍したフォルダにある install.sh といふファイルを

sudo ./install.sh

で 實行し /user/local/bin にPATHを通すのが 一番手取り早いと思ふ
この install.sh は 中身を見れば分かるのだが 單に 解凍したフォルダ内にある實行ファイルを /user/local/binなどのフォルダにコピーしてゐるだけだ
これで hastec といふプログラムが利用可能になる
たとへば haskellでつくったコードが Main.hs といふ名前だとすると

hastec Main.hs

とするだけで
Main.js
といふ javascriptコードができてしまふのだ


ここまでが追記

hasteへの思ひ

hasteは haskellのコードから javascriptを生成するものだ
私にとって 本當なら haskellのコードで ブラウザ(ネットを見るソフト)に いろいろできれば それが一番いい
例へば haskellブラウザゲーム(ネット上で動くゲーム)を書き それがそのまま ゲームとして動けば 一番いい といふことだ
だが 現状はさうなってゐない
javascriptでブラウザを操作するのが一般的だと思ふし 最近でてきた web assembly を使ふ方法は 敷居がちょっと高いと思ふ
肌感覺を言へば haskellまはりは 最近 haskellコードから web assembly (ブラウザが理解できて早く動くコード) を作らう といふ方向で 動いてはゐる
でも まだまだ ちょっと haskell で SDL2(ゲームをつくるのに向いてゐるライブラリ(プログラム))を使って つくったゲームを そのまま web assembly にして ブラウザで實行する(しかも ほぼ自動的に) といふ段階ではないかな と感じてゐる
それより 今ある環境で なるべくなら「ゲームをつくるための環境ゴチャゴチャ設定」にあまり時間を使はずに 「ゲームをつくってやってもらふこと」に専念したい と思ふわけだ
「なら Unity つかへよ」 だと?!
私は haskell で書きたいのだ
それが 「ゲームをつくってやってもらふこと」より 優先するのだああああ!(狂)
・・・・・
すると 私にとって haste は重要なプログラムになってくる
私がつくって web上にアップした HA とか Fi とかいふアプリは hasteで haskellコードから javascriptコンパイル(まあ 變換みたいなもん) された
同じやうなことをするプログラムにghcjs といふものがある
たぶん haskell界隈では haskellコードをjavascriptにするなら ghcjs といふ感覺が一般的だと思ふが 今のところ これは 私には「合はない」
とにかく 色々と向きあってきたつもりだが 「何かよくわからない」「そもそもインストールできない」「インストールしても それからどうするのか 分からない」
haste は 最終的に更新されたのが4年くらい前
比べて ghcjs は 去年に一應更新されてゐるが
ghcjsにしても 「積極的に更新されてゐて 誰でも手軽に使はれるやうになることが 目ざされてゐる」やうには どうしても見へない
なんといふか 直感的に ghcjsといふのは 大きな會社で利用されてゐて その會社でしかなかなか知ることができない 設定方法や ノウハウが蓄積されながら それが いってみれば「オープンにはしてゐるが 素人に手が出せない」やうな状態に置かれてゐる氣がする
一方hasteは 一時は ホームページごと見られなくなる始末で なぜか今は回復してゐるものの 實行ファイルのダウンロードができてゐたリンクは 削除されてゐる
私から見ると Haskellの達人さんが hasteを改良したはうが ghcjsを改良するより ずっとhaskellコーディングの敷居が低くなる と思ふのにだ
たぶん これは一般的に言へることだと思ふし 存在することだと思ふのだが 企業といふものは その企業の利益にプラスになるやうなことで動く場合が多いので 企業が公表しない 「企業秘密」なんてものも きっとあるのだらう
なんらかの「大人の事情」で hasteが世からはじき出され といふかもっと言へば haskell自體に脚光が集まらず(學んぢゃだめだみたいなレッテルが貼られたり) JavaPython そして c# のやうな言語が世の主流を行く といふことも あり得なくはないんぢゃないかな
まあ 私が haskell好き(別にそれを使ひこなしてゐなくても)なのは ちょっとhaskellが はぐれものだったり(といっても有名な企業でメッチャ使はれてるけど) lazy evaluation (遅延評価と訳されてるけど 評価するのに 「怠けもの」みたいな表現が好き) だったりするところも あると思ふ
日本生まれの tron-os とか それこそ ガラパゴスと呼ばれた 昔のケータイとか
私は さういふものに愛着を感じる・・・ はなしが逸れまくったな

さて
ソース(プログラムコード本體)から ビルド(實行ファイルをつくること)する場合 少なくとも私にとっては 多くの困難が立ちはだかってゐた


hasteビルド體驗記

まず 今のバージョンのNixOS (23.11) のパッケージ(まあ アプリみたいなやつ) に ghc710は存在しない
haste は ビルドする際 ghc(有名なhaskellコンパイラ) のバージョン 7.10.3 くらゐなものが必要なのだが NixOS は ある時點で このバージョンの扱ひを打ち切ってゐる
ただし 古いNixOSのバージョンには この ghc 7.10.3 が入ってゐて NixOSの仕組み上 これをテスト環境で組み込むことができる
NixOSのターミナルで

nix-shell -p ghc -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/5d54038    7713f2d29dac423f5faadafd5aea2ff09.tar.gz

を實行すると このターミナルの中に 「ghc 7.10.3が入ってゐる環境」がつくられる
この環境から出るには ただ

exit

とすればいいだけだ
ちなみに この古いバージョンのghc

lazamar.co.uk

のページへ行き Nix channel が nixos-22.11 のときの Package nameに ghc を入れて検索したら出てきた
さて

git clone https://github.com/valderman/haste-compiler.git

として hasteのソースを入手したら(gitがインストールされてゐる前提)

cd haste-compiler

としてその中に入り

stack install

として成功したいものだが
全然ダメだ
よく分からないエラーがでて 結果對処できなかった
しかし まず

nix-shell -p cabal-install

として この環境下に さらに別の環境 すなはち 「cabal-installといふアプリが入ってゐる」といふ環境をつくることができ
そこで

cabal update

さらに

cabal install

とすると はじめのうち エラーが出ずに 色々インストールされてくる
結構ここでワクワクするのだが・・・
やはり 途中でエラーとなる
しかし エラーログを追ってゆき
「zlib」といふものが必要さうだと分かったら

nix-shell -p zlib

としてアプリ(プログラム?)を環境下に入れる
ちなみに どんなパッケージ(アプリ? プログラム?) が 現行のNixOSに入ってゐるか といふのは コマンドでも確認できないことはないが 時間がかかるので

search.nixos.org

で調べることができる
一應 公式に近いのでは と思ふサイトで 古いものが調べられるところも紹介してをく

history.nix-packages.com

ちなみにさっき紹介した 古いものを調べるサイトは 「公式ぢゃないよ」と書いてあった
それでも てこずったのは
libbz.h がない とか bz2 がない とかいったエラーで
色々インストール(つまりは nix-shellをつかった環境づくり) を試みたが失敗
なんと 全然違ふ名前の

nix-shell -p bzip2

としたら入った
そして あと一歩
haskell-compilerそのものがコンパイルされる あと一歩のところで
「shellmate-extras」がビルドできへん
みたいなエラーが發生
このエラーは 今でも解決できてゐない


今のところ hasteを入れる方法としては
NixOSとしては たぶん「きたない」やり方になると思ふが
始めに追記されてゐるやうに
實行ファイルをダウンロードして インストールスクリプトを實行し PATHを通す(デフォルトでは NixOSは /usr/local/bin にPATHを通してゐない) 方法しか 私にはできなかった
でも 今回の一連の作業で
NixOSのやばさ(すごさ) が垣間見れたやうな氣がする
本格的にシステムにインストールしなくても どんどん自分が必要な環境をつくって プログラムをお試しできたりする なんて これ結構すごくね?
まう全然わけわかんなくなっちゃって こんがらがっても
exit
ひとつで そこから出られる
ぶっちゃけシステムを變へてしまふやうなことをやらかしてしまっても
前のシステム構成に戻れる
あっ ここで一應 前のシステム構成にもどす方法を 私なりに書いておきたい


前のシステムに戻す

なんかこれ NixOSの賣り のはずなのだが
ここでつっかかる人も多いのでは と思ってしまふ
わたしが現に つっかかってゐたからだ
いや この時の操作などで NixOSのブート(起動)に問題が置こり
まるっと Nixをインストールしなおすはめになった 操作でもある
私としても 完全に理解をしてゐないので 私の説明は中途半端かもしれないが
これまでいぢってきた 経験と直感で話すことにする

nix-channel --list-generations

とすると
左に通し番号が そして右に日付とか時間
そして どれかひとつに (current) と書かれてゐる
この current といふのは 「今あんたは ここにゐるよ」
みたいなやつで
コマンドにもある generations といふのは 「なんか 色々つくってきた NixOSの環境のあつまり」 みたいなものだと思ふ
NixOSをインストールすると ブートローダーといふ NixOSを立ち上げるアプリ(?)もインストールされると思ふのだが 通常に立ち上げるのではなく Advanced Option みたいなところを選ぶと たぶん 今ゐる generation の環境が いくつか 新しい順から表示される と思ふ
たとへば 最後にやった設定で問題が起きてしまひ (たとへば 私のやったやうに 間違ったビデオドライバの指定をして 画面が表示されなくなったみたいな)
前の環境に戻りたいときには その Advanced Option から デフォルトで選ばれるひとつ下の環境を選んでやるとよい
そして 正常な環境が戻ったら やっちまった箇所を直して

sudo nixos-rebuild switch --update

とする
この作業をすると たぶん 現行の generation の一番「最近の」環境がつくられるので
次回デフォルトで起動したときには 正常な環境で起動できると思ふ
もっと前の generationに戻りたい といふときは

sudo nix-channel --rollback

といふのが ひとつ前の generationに戻るやつで

sudo nix-channel --rollback 2

とかやると --list-generations のときに左に出てゐた番号のgenerationに戻ることになる
さうして 起動すれば
Advanced Option に 指定したgenerationの環境が並ぶことになるのでは? と思ふ(ここは ちゃんと確かめてない)


こんなところだらうか
最初に氣分が良いといった理由は 「何となく」で
べつに 何かが實現したから とか さういふのではない
でもやはり hasteが NixOSに入り fi の開發ができるのは 嬉しい
いささか風邪氣味だが 明日には治るだらう
それでは このへんで

NixOS + Haskell + SDL2

インストールしたてのNixOS で Haskell を使ひ SDL2 を利用して 何かつくりたい と思ったとき 最低限(だと思ふ)やることを 列挙する

stack を入れる

おそらく どのやうなインストール方法をしたとしても 何らかのターミナルと nano といふエディタが使へるはずだ
私の場合は XTerm といふターミナルが入ってゐた
ターミナルを開き

sudo nano /etc/nixos/configuration.nix

と入力してEnterキーを押す
はじめに設定してゐたパスワードを入れて 編集画面に入る

environment.systemPackages =  

などと書かれたところを見つけて
次のやうに 中身に haskellPakages.stack を書く(エディタとしてvimも追加したい人は追加する)

environment.systemPackages = with pkgs; [
    vim
    haskellPackages.stack
];

次に 下のやうにコマンドを打ちこむ

sudo nixos-rebuild switch --upgrade  

ここで 一旦再起動してもよいのだが
このままでも stack のプロジェクトを書くことができる
もし stack といふコマンドが認識されてゐないなら

nix-shell -p haskellPackages.stack

としてEnter
プロンプトの部分が
[nix-shell: なんとかかんとか]
になってる筈

stack new myproject

などとして プロジェクトを立ちあげる (myproject はプロジェクト名なので なんでもいい)

cd myproject

として プロジェクトフォルダの中にはいる

stack.yaml を編集する

sdl2を使ふ設定は stack.yaml といふファイルに書く

nano stack.yaml

(vimをエディタで使ふ人は vi stack.yaml)
一番下の行に(別に途中でもいいけれど)
次を追加する

nix:
  enable: true
  packages: [pkg-config, SDL2, SDL2_image, SDL2_ttf, SDL2_gfx, SDL2_mixer, libtiff, harfbuzz, freetype, libwebp, glib, pcre2, libsndfile, libpulseaudio, alsa-lib, jack2]

package.yaml を編集する

次に package.yaml を編集する
sdl2 単体の他に sdl2-ttf(フォント関係) sdl2-image(イメージ関連) sdl2-mixer(音関連) sdl2-gfx(描画補助関連) を入れたいばあいはそれも記述する
場所は dependencies: といふところの下

dependencies:
- base >= 4.7 && < 5   --(もともと書いてあるやつ)
- sdl2
- sdl2-ttf
- sdl2-image
- sdl2-gfx
- sdl2-mixer

あとは app フォルダにある Main.hs ファイルを編集したりして
sdl2を使ったコードなんかを書き myprojectフォルダ(はじめに作ったやつ)直下で

stack run

をすれば ちゃんとコードが書かれてゐるなら 實行できる筈
おおまかだけど だいたい こんなとこかな〜

おべんきゃう

この何日間かでやってゐたことは
debian 12 の設定
ほとんどこれ・・・
きっかけは
debian 11 で エディタを使ってゐて
無性に skk(漢字變換のツール)で變態仮名が使ひたくなったこと
さすが 変態ですね・・・
ある記事を参考に 辞書の utf-8化をしやうとしたところ
skkが動かなくなった・・・
記事の中で 「ここで一旦再起動して、uimでひらがなが入力できるかくらいは確認した方が良いでしょう。」
と書いてあり 再起動した後の話である・・・
そして さらに わたしは やってはいけないことを やってしまった・・・
システムフォルダの一部を削除するといふ!!!!
uimのフォルダをまるっと消してしまった
理由は 上の記事で變更した箇所をクリアにして aptでインストールしなおさう なんて考へたからだ
すると どうなったか・・・
なんと gnomeベースで動いてゐるやうな すべてのアプリが起動しなくなったのだった!
xtermが起動する といふ 救ひはあったものの
アクティビティにアイコンとしてあるやうなものすべてが 起動しなくなった
もちろん xterm から brave-browser などとやって brave(インターネット閲覧ソフト)を起動しやうとしても無理
別のOS上で ブラウザを開き 色々と解決策を調べたのだが
なんだか にっちもさっちも 行かない状態となり・・・
決断した
さう debian11を 削除する と・・・
バックアップはとったつもりだった
さう・・・つもり だったのだ
前に NixOSを入れたときに使ったusbメモリ(16ギガ)を usb formatter(だったか?)でフォーマットし debian12live のイメージを入れ(なんとかエッチャーだっけ?)
あ ちなみに これらの操作は windows11上でやった
(あんまり OSを毛嫌ひしてはならない
windowsだって いいとこあるよ 感謝してるよ〜
といふのも iphoneを心の中でけなしまくってゐたら
せっかく妹からもらった ちょっと古いけど 全然高機能なiphone
實機動作確認のために 色々使はうと思ってゐたのに 不慮の事故で(なんと 寝てゐる間に ゴミ箱に落とす) おさらばとなってしまった
要するに 日頃の接し方が 大事と痛感したからである)
といふわけで かわいいよ windows ・・・ そして
debian11のパーティションを初期化 (さらば 我が友よ〜)
そこからは 逐一記録をとりつつ (だって めんどくさい設定 また一から考へるの さすがに飽きたのよ) こてこてやっておりました

まあ なるべくポジティブに考へるとするなら 今回のことで 色々と おべんきゃう できたわけで
skk まはりのこと (fcitx5とか そもそも最近は utf-8の辞書も用意されてゐる とか) 時間のずれ (ロケールが東京になってゐるのに何時間もずれてゐる)の直しかた
たぶん そのほかにも 色々 確認できたことなどが あったわけですよ
だが
hasteが入らないのは痛い・・・
あと バックアップを取ってゐた筈の ホームフォルダが 實はバックアップされてなくて
うひょおえあぼんばびぎゃ
なぜか ghc-7.10.3 を入れやうとしても入らない(エラー)
ネットのスレッドによると これは未解決の問題(?)らしい
まあ hasteは 更新されてないし 古いし いづれ ghcjsがすごくなって
もっと簡單に javascriptをはきだしてくれる日が來る筈・・・
いや 古いからこそ 貴重なのでは
失はれし 古代文明! みたいな
ほら ジョンタイター だって めっちゃ古いコンピュータ (IBMなんちゃら) 探してたやんけ〜
やはり fi の開發がとまってしまふのは 辛いので
debian11の どこかへのインストール
もしくは docker なんかを使った やり方なんかも 試してみたいとは思ふ
NixOSが もっと使ひこなせれば かういふ問題にも對処できさうだけどね・・・

うん hasteのことは ひとまづ 置いておかう
この新しくインストールしたdebian12上で
ha の變換機能を ある程度いい感じにすることには成功したぜ
(てか それしないと 變換時の文字がどこにも表示されんのだよ・・・)
さう debian11でやってゐた時は 「なんで かういふ動作をするのか」が
よく分からないまま 「まあ できたんだから いいか」
って感じでスルーしてきたけど
新たな環境で 何かが「できた」場合 じっくり調べながらやっていけば
その理由も 結構はっきりするわけで
やっぱり それは よかったかな と

てなわけで 今回も お勉強でした〜

SDL2 イメージ操作

コードを載せる

import SDL.Video (Renderer)
import SDL.Video.Renderer (Surface,Texture,SurfacePixelFormat(..),Rectangle(..),PixelFormat(..)
                          ,rendererDrawColor,clear,destroyTexture
                          ,createTextureFromSurface,copyEx,present
                          ,lockSurface,unlockSurface,surfacePixels,surfaceFormat,createRGBSurfaceFrom)
import SDL.Vect (Point(P),V2(..),V4(..))
import SDL.Internal.Numbered (fromNumber)
import qualified SDL.Raw.Types as SDLT
import Foreign.Ptr (castPtr)
import Foreign.ForeignPtr (newForeignPtr_)
import Foreign.Storable (peek)
import qualified Data.Vector.Storable.Mutable  as VM
import System.Random.Shuffle (shuffleM)
import Data.Word (Word8)

draw :: Renderer -> [Surface] -> IO ()
draw re imageS = do
  imageTO <- mapM (createTextureFromSurface re) imageS
  let imageS0 = head imageS

  -- メモリ操作のため surfaceをロックする
  lockSurface imageS0

  -- surfaceのPixelForat情報があるポインタを取得
  SurfacePixelFormat pointerPixFormat <- surfaceFormat imageS0

  -- ポインタ(アドレス)の情報を読む
  surPixFormat <- peek pointerPixFormat

  -- このPixelFormatは SDL.Raw.Type で定義されてゐるもので
  -- pixelFormatFormat といふデータに SDL.Video.Renderer で定義される PixelFormat の情報が
  -- Word32といふ型で格納されてゐる
  -- これは print で表示すると 整数なのだが これを fromNumber で PixelFormatの形式に變換する
  let sPixFormat = fromNumber (SDLT.pixelFormatFormat surPixFormat) :: PixelFormat

  -- イメージのsurfaceのピクセル情報が格納されてゐるアドレスを得る(ポインタ)
  pointer0 <- surfacePixels imageS0

  -- データからsurfaceをつくるためには IOVector Word8 といふ型でデータを保持しなければならない
  -- IOVector は MVector (PrimState IO) と同義であり
  -- surfacePixels函數で得られる Ptr () を castPtr で Ptr Word8 へ變換し
  -- Ptr Word8 を newForeignPtr_ で ForeignPtr Word8 へ變換する
  frPointer <- newForeignPtr_ (castPtr pointer0)

  -- イメージの1ピクセルは 4つのWord8(0〜255)のデータで表される
  -- Word8の総数は 4*64*64 となる (64*64ピクセルだから)
  -- この長さを ポインタを起点としたアドレスから讀み込み MVector型のデータを得る
  let mvector = VM.unsafeFromForeignPtr0 frPointer (4*64*64) :: (VM.MVector (VM.PrimState IO) Word8)
  -- ロックを解除
  unlockSurface imageS0

  -- mvector のクローンを作成
  -- mvectorを書き替えると imageS0 の内容がそのまま書き替はる
  -- surface が變更されると 表示してゐたtextureも變はる
  mvector2 <- VM.clone mvector

  -- 4x4のピクセルを單位として ランダムにピクセル情報を變更する
  -- 水の中にあるやうな効果を出すことができた
  mapM_ (\y -> mapM_ (\x -> sfl4x4 mvector2 (V2 x y)) [0..15]) [0..15]

  -- MVector Word8 型のデータ, surfaceのサイズ, ピッチ(1行のピクセル(64px)のバイト數),
  -- そして 先程求めたsPixFormat (このイメージはABGR8888だった)を使い 新たなsurfaceをつくる
  newImageS0 <- createRGBSurfaceFrom mvector2 (V2 64 64) (4*64) sPixFormat

  newImageT <- createTextureFromSurface re newImageS0
  let imageTextures = imageTO ++ [newImageT]
  initDraw re
  imageDraw re imageTextures 
  mapM_ destroyTexture imageTextures 
  present re

きっかけは ブロックを使った操画(ゲーム)を考へてゐて そのブロックのイメージを 「もうちょっと柔らかいものにしたいな〜」 といふことだった
BABA IS YOU といふ 超名作ゲームのブロックの ほわほわする感じが すごく良くて そんなやうなイメージが創れたらいいな〜 といふ 安直な思ひがあった
しかし おそらく それをやるためには イメージを大量に用意して アニメーションさせるのではなく イメージそれ自體にアクセスして その情報を變へるようなことが必要だ と考へた
イメージにアクセスするためには SDL2の場合 surfaceの情報を取得する必要がある
SDL.Video.Renderer のドキュメントを見ると

surfacePixels :: MonadIO m => Surface -> m (Ptr ())

とあり
Obtain the pointer to the underlying pixels in a surface. You should bracket this call with lockSurface and unlockSurface, respectively.
と説明されてゐる
要は 「ほら 情報にアクセスできるポインタだよ これで後はよろしく」
みたいな感じだ
何の例もない
どうすればいいか どこにも書いてゐない

ちなみに 仮にデータが得られたとして それを どう使ふのかといへば

createRGBSurfaceFrom
  :: (Functor m, MonadIO m)
=> IOVector Word8  --(The existing pixel data)
 -> V2 CInt  --(The size of the surface)
 -> CInt  --(The pitch - the length of a row of pixels in bytes)
 -> PixelFormat  --(The bit depth,red,green,blue and alpha mask for the pixels)
 -> m Surface

最終的に IOVector Word8 といふ型でデータが供給できればよい
んで Data.Vector.Storable.Mutable のドキュメントを見ると

unsafeFromForeignPtr0
  :: Storable a
=> ForeignPtr a
 -> Int
 -> MVector s a

これが 私の見たところ このモジュール内で ポインタからMVector をつくる唯一(offsetなしなら)の方法だった
ちなみに つくりたいのは IOVector Word8 である
ここで

type IOVector = MVector RealWorld

と書いてある
また
MVector s a の s は (PrimMonad m => PrimState m) であり
PrimState IO が RealWordl である(らしい) https://stackoverflow.com/questions/8959226/constructing-iovector-from-storable-mvector

この邊の理屈は 正直よく分からんかった
が 先程の

unsafeFromForeignPtr0 :: Storable a => ForeignPtr a -> Int -> MVector s a

の a を Word8 として s を PrimState IO とすれば 返り値の型は MVector RealWorld Word8
すなはち IOVector Wrod8 となる
つまり ForeignPtr Word8 型のポインタと データサイズ(Int)を與へることで
目的の型をもつデータがゲットできる といふわけだ
そこで Ptr () を ForeignPtr Word8 にしなければならない
Foreign.Ptr モジュールのドキュメントによると

castPtr :: Ptr a -> Ptr b

といふのがある
これで () を Word8にできるのでは?
といふことで castPtr を適用し
Foreign.ForeginPtr モジュールにある

newForeignPtr_ :: Ptr a -> IO (ForeignPtr a)

を適用して めでたく ForeginPtr Word8 をつくりあげることができた

さて 一番説明がやっかいさうなのは PixelFormat だ
SDL.Video.Renderer に PixelFormatは定義されてゐて
RGB888 や RGBA8888 ABGR8888 BGRA8888 など いくつものフォーマットがある
最終目的を思ひ出して欲しい
createRGBSurfaceFrom に必要なデータを揃えることだった
IOVector Word8 はゲットしたし イメージサイズも分かってゐる
ピッチといふのは ピクセル一行分が 何バイトに相當するかを示すもので
1ピクセルにつき4バイト必要な RGBA8888 や ABGR8888などは (横の幅(px)) × 4 で求まる
殘るは PixelFormat だけなのだが
色々と適當に フォーマットを試してみて できたイメージの結果を確認していくと
これは ABGR8888 だな といふのが分かる
それ以外のものは 明らかに色が もとのイメージと異なったからだ
(といふことは ある意味 PixelFormatだけ變へてやれば 瞬時に画像の色合いを變へることができるわけだ)
けれども これから色々なイメージを讀み込まうと思ってゐる場合
常に ABGR8888とは限らないだらう
だから イメージの情報を讀み取ることで PixelFormatの情報を得たいわけだ
これに關して

surfaceFormat :: MonadIO m => Surface -> m SurfacePixelFormat

といふ函數が SDL.Video.Renderer で用意されてゐる
しかし 返ってくるのは なぜか PixelFormat ではなく SurfacePixelFormat といふ謎なやつだ
そこの部分のドキュメントを見ると

newtype SurfacePixelFormat

Constructors:
SurfacePixelFormat (Ptr PixelFormat)
となってゐる
Ptrだとおおお?!? すなはち surface の PixelFormat を知りたくて尋ねたら 「ハイ! アドレスだよ! 見といてね?」 なんて言はれ ポカーン とするしかない状況が生じたのだ

ここで メモリのアドレスにあるデータを見る方法が必要になってくる
これは 色々検索して

https://naohaq.github.io/

を見たときに分かったのだが
「peek」といふものだ
Foreign.Storable モジュールに書かれてゐる
やった〜!
といふことで peekを使ひ その内容をPixelFormatとして createRGBSurfaceFrom に入れてみた
が・・・エラー
なんやねん といふことで peekした内容を表示してみたら

PixelFormat {pixelFormatFormat = 376840196, pixelFormatPalette = 0x0000000000000000, pixelFormatBitsPerPixel = 32, pixelFormatBytesPerPixel = 4, pixelFormatRMask = 255, pixelFormatGMask = 65280, pixelFormatBMask = 16711680, pixelFormatAMask = 4278190080}

な〜〜?! SDL.Video.Renderer のドキュメントで
SurfacePixelFormat (Ptr PixelFormat) とあるうち PixelFormatのリンクをクリックすると
全然別のドキュメントに飛ばされる
それが
SDL.Raw.Types だ
ここで定義される PixelFormatは

data PixelFormat

Constructors:

PixelFormat  
pixelFormatFormat :: !Word32     
pixelFormatPalette :: !(Ptr Palette)     
pixelFormatBitsPerPixel :: !Word8    
pixelFormatBytesPerPixel :: !Word8   
pixelFormatRMask :: !Word32  
pixelFormatGMask :: !Word32  
pixelFormatBMask :: !Word32  
pixelFormatAMask :: !Word32

この内容は さきほど プリントさせたものと合致してゐる
んで どないしろっちゅーねん!
まう一度 SDL.Video.Renderer にある PixelFormat のドキュメントとにらめっこした
何か このふたつの PixelFormatには 關聯性がある筈なんだ・・・
ん?

 FromNumber PixelFormat Word32
 ToNumber PixelFormat Word32

Word32?
さういへば SDL.Raw.Types のPixelFormat の最初に Word32の型があったな・・・
それは 意味不明な數字だった・・・
そして fromNumber といふ函數があるらしい・・・
まさか あの數字が ABGR8888 を表はしてるとか???
fromNumber函數は モジュール SDL.Internal.Numbered にあった
これを SDL.Raw.Types の PixelFormat 内にある PixelFormatFormat の數字に適用すると・・・
動いた!!!

Youtube のストリーム配信で 説明しやうとやってみた

www.youtube.com

コードは githubに載せてゐる

github.com

一言いはせてほしい
Haskell は 冒険だ!

StateTだとぉ〜!

ちょっと どうやってまとめていいか 整理できてゐないのだが
とにかく今 感動してゐるので 書き殘しておきたい

ゲーム制作でなくとも アプリかなんかつくるんであれば
色々と画面に表示(描画)しながら 何らかの「状態」を變化させていくだらう

Haskell では その「状態」を表す データ型をつくって それを函數に渡しながら そのデータ型に基いた 新たなデータをつくり 「状態」を變化させていったりできる

だが つねに 「今」の状態を示すデータを 受けとって それをもとに 新しい「状態」データを作成し また それを次の函數に渡していく〜といふのは なんといふか 「必ずやる」ことなのに 毎回函數に加へなくてはならない情報であって やはり 「なんか もっと うまいやり方はないのかな〜」などと思ってしまふ

そんで 本などを見ると
State s モナド なんていふものが書いてある
これを利用すると 「状態」を簡単に扱へる といふことらしいのだが
いまいち 私には その具体的な使ひ方が 腑に落ちなかった

とにかく使ってみやう といふことで SDL2を使ってイメージの讀み込みと ウインドウ表示だけをするコードを書き 色々と實驗してみた

しかし うまくいかない

State s モナド は (State 状態 結果) を返す函數と 初期の状態を runState といふ函數に與へることで機能する
だけど runState に與へたい函數は 単に状態を變化させる といふだけでなく 様々な出力に對應する つまり IO型を扱ふ函數であってほしい〜といふか さうでなくては困るのだ

それを念頭に Hackage の Control.Monad.State.Strict のドキュメントとにらめっこしてゐて
StateT についての項目をながめてゐた

newtype StateT s (m :: * -> *) a

s - The state.
m - The inner monad.
と書かれてゐる
もしかして m を IOにできるってこと?
StateT モナドを實行する runStateT といふ函數もある
・・・もしかして・・・
コードを書いてみた (一部を載せる)

module App(appMain) where

import Control.Monad.State.Strict (runStateT)
import MySDL.Load (loadFiles)
import MySDL.Loop (loop)
import MySDL.Init (withInit)
import MySDL.Video (withVideo)
import MyData (initWorld)

appMain :: IO ()
appMain =
  withInit $ do
    imageS <- loadFiles
    withVideo $
      \renderer -> runStateT (loop renderer imageS) initWorld
module MySDL.Loop where

import Control.Monad.State.Strict (StateT,get,put)
import Control.Monad.IO.Class (liftIO)
import SDL.Video.Renderer (Renderer,Surface)
import SDL.Time (delay)
import MyData (World(..), Input(..),  delayTime)
import Event (inputEvent)

loop :: Renderer -> [Surface] -> StateT World IO ()
loop re imageS = do
  inp <- liftIO inputEvent
  wld <- get
  let newWorld = wld{abc=abc wld + 1}
  liftIO (print (abc wld))
  put newWorld
  delay delayTime
  if inp==QIT then return () else loop re imageS

今回實驗で使った「状態」は Worldといふ型で abc=0, def=1 といふ値を初期値とする 單純なものだ
これを メインループ上で取り出し abcに1を加へてターミナルに表示させることができた
ここで loop函數は 状態を表す引數をもってゐない
それでも get函數で 状態を取得し put函數で 状態を書き込むことができる! この函數のなかで IOアクションを實行するときは liftIO を適用すればよい

これで 「状態」をかなりスマートに扱ふことができるやうな氣がする〜

コード全体は

github.com

Haskell 多重リスト

この言葉が 廣く使はれてゐるのかは知らない
多次元リスト といふ言葉も ネットに載ってゐる
リストの中に リストがある といった意味合ひで使はれるのだらうとは思ふ
例へば
[[1,2],[1,2,3],[4],[6,5,4,3]]
といったものだらう
だが 私としては 「多重リスト」といふ言葉を
「リストの中に 「リストのリスト」も 「リストのリストのリスト」も含むやうなリスト」
の意味で使ひたい
いや 既にそれを表す用語があるのなら それを使ふのだが 今私はそれを知らないから

つまり 多重リスト といふのは 私にとって
[1, [2,3], [3,[4,5]], [[[6,7],[8]]]]
のやうに書けるやうなものだ
多分 Haskell では かういうのは リストと言はないだらうが

これは おそらく 構造としては 「木」の構造だから 「木」と呼ぶべきなのだらう
けれども 上の表記は それなりに データの構造が分かりやすく可視化できてゐると思ふ
Data.Tree モジュールを利用して この構造を實装し 可視化しやうとしたのが 私のつくった MyTreeモジュールだ

github.com

module MyTree (Elm(..), showF, addElem) where

import Data.Tree 
import Data.List (intercalate)

data Elm a = El a Int Int

showT :: Show a => Tree a -> String 
showT (Node x []) = show x
showT (Node x tr) = "["++show x++", "++tail (showF tr)

showF :: Show a => Forest a -> String 
showF [] = ""
showF tr = "[" ++ intercalate ", " (map showT tr) ++ "]"

addElem :: Elm a -> Forest a -> Forest a 
addElem (El mn _ _) [] = [Node mn []]
addElem (El mn 0 0) fo = fo ++ [Node mn []]
addElem (El mn l r) fo
  | lng > l && l > 0 = let (h,(Node s sf):t) = splitAt (lng - l) fo
                           newNode = Node s (t++addElem (El mn l r) sf)
                        in h ++ [newNode]
  | r > 0 = let (it,lt) = (init fo,last fo)
                Node x subf = lt  -- last tree
             in if null subf then it ++ [lt, Node mn []]
                             else it ++ [Node x (addElem (El mn l r) subf)]
  | otherwise = fo ++ [Node mn []]
  where lng = length fo

addElem といふのは リストに 新たな要素を加へやうとしてゐる
Tree a といふ型のリストが Forest a と呼ばれるものだ
Forest に要素を加へていき showF で その内容を リストっぽく見せやうとしてゐる

Elm といふ型は 加へたい要素の型と
「今まで加へた要素の 最後のいくつを 今回のものといっしょにまとめるか」 「次に加へる要素の いくつを 今回のものといっしょにまとめるか」 といふ3つの情報を指定して 要素を加へやうといふためのものだ
ちなみに最後の情報は 別のコードから呼び出すときに使ふためのもので 今回 ghciでは利用しない

かういふことをする場合 このやうな「木」のデータ構造にする意義があるのではないかと思ふ

たとはば このモジュールをghci で読みこんで

> a = addElem (El 1 0 0) []
> b = addElem (El 2 0 0) a
> c = addElem (El 3 0 0) b
> d = addElem (El 4 0 0) c
> e = addElem (El 5 2 0) d
> f = addElem (El 6 1 0) e

としたとき showF f
"[1, 2, [3, 4, [5, 6]]]" となる