PureScriptで特定ディレクトリ内のファイルを列挙する
PureScriptの言語仕様の全貌をあまりまだ理解しきれいていないけど、練習として特定ディレクトリのファイルの列挙に挑戦してみる。
spago run
でPureScriptのプログラムを実行すると、PureScriptのコードはJavaScriptに変換されてNode.jsで実行されるので、Node.js向けのモジュールを使うことができる。
特定ディレクトリのファイルを列挙するには、Node.jsのfs
モジュールのreaddir
が使えるが、このfs
モジュールを使うためのPureScriptのモジュールとして以下の2つがあった。
node-fs
は非同期処理の実行結果をコールバック関数で受け取るもので、node-fs-aff
は非同期処理の実行結果をPureScriptのAff
で受け取るもので、Aff
版の方がPureScriptらしいと言える。
purescript-node-fs-extraというものもあったけど、これはNode.jsのfs
ではなく、fs-extra
という別物のモジュールを使うためのもののようだった。
spago install purescript-node-fs-aff
[error] The following packages do not exist in your package set:
[error] - purescript-node-fs-aff
何か間違えている。
purescript-node-fs-aff - Pursuit
purescript-node-fs-aff is deprecated. Functions and values from this library were merged as-is into the purescript-node-fs library.
purescript-node-fs
にマージされたっぽいし、インストール時にpurescript-
の部分は不要。
spago install node-fs
Geminiにプログラムを作ってもらった。
module Main where
import Effect (Effect)
import Effect.Console (log)
import Node.FS.Aff (readdir)
main :: Effect Unit
main = do
let directoryPath = "./"
filesOrError <- readdir directoryPath
case filesOrError of
Left error -> log $ "エラーが発生しました: " <> show error
Right files -> do
for_ files \file -> log $ "- " <> file
spago run
Error found:
in module Main
at src/Main.purs:7:16 - 7:20 (line 7, column 16 - line 7, column 20)
Unknown type Unit
Unit
が無い?
import Prelude
されていなかった。
GeminiはPureScriptは得意じゃない?
spago build
[1 of 1] Compiling Main
Error found:
in module Main
at src/Main.purs:16:5 - 16:15 (line 16, column 5 - line 16, column 15)
Could not match type
Either t1
with type
Array
while trying to match type Either t1 t2
with type Array String
while checking that expression case filesOrError of
(Left error) -> (apply log) ((...) (...))
(Right files) -> (for_ files) (\file ->
...
)
has type Aff t0
in value declaration main
where t0 is an unknown type
t1 is an unknown type
t2 is an unknown type
Either
まわりがよくわからない。
Microsoft Copilotにお願いしたら以下のコードができた。
module Main where
import Prelude
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Console (log)
import Node.FS.Aff as FS
main :: Effect Unit
main = launchAff_ do
let dir = "./target-directory"
files <- FS.readdir dir
log $ "Files in " <> dir <> ":"
traverse_ log files
spago build
[1 of 1] Compiling Main
Error found:
in module Main
at src/Main.purs:14:3 - 14:12 (line 14, column 3 - line 14, column 12)
Unknown value traverse_
さっきのGeminiと違ってlaunchAff_
でAff
をEffect
に変換するのはやってくれている。
traverse_
が無い。Data.Foldable
からtraverse_
をインポートする必要があるとのことなので、import Data.Foldable (traverse_)
を追加。
spago build
[1 of 1] Compiling Main
Error found:
in module Main
at src/Main.purs:14:3 - 14:34 (line 14, column 3 - line 14, column 34)
Could not match type
Effect
with type
Aff
while trying to match type Effect t1
with type Aff t0
while checking that expression (discard ((apply log) ((...) (...)))) (\$__unused ->
(traverse_ log) files
)
has type Aff t0
in value declaration main
where t0 is an unknown type
t1 is an unknown type
Microsoft Copilot曰く、Aff
の文脈でEffect
のlog
を使おうとしているためのようで、import Effect.Class.Console (log)
を使えば良い、とのこと。
[error] Some of your project files import modules from packages that are not in the direct dependencies of your project.
To fix this error add the following packages to the list of dependencies in your config:
- aff
- foldable-traversable
You may add these dependencies by running the following command:
spago install aff foldable-traversable
モジュール不足。
spago install aff
spago install foldable-traversable
spago build
[info] Build succeeded.
OK.
spago run
[info] Build succeeded.
(作業ディレクトリ)/output/Effect.Aff/foreign.js:530
throw util.fromLeft(step);
^
[Error: ENOENT: no such file or directory, scandir './target-directory'] {
errno: -2,
code: 'ENOENT',
syscall: 'scandir',
path: './target-directory'
}
Node.js v20.17.0
[error] Running failed; exit code: 1
Node.js側でエラーが出た。
あ、"./target-directory"
の固定になってた。
とりあえずカレントディレクトリにする。
let dir = "./"
spago run
[1 of 1] Compiling Main
[info] Build succeeded.
Files in ./:
.gitignore
.purs-repl
.spago
output
packages.dhall
spago.dhall
src
test
できた!
完成コード。
module Main where
import Prelude
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class.Console (log)
import Node.FS.Aff as FS
import Data.Foldable (traverse_)
main :: Effect Unit
main = launchAff_ do
let dir = "./"
files <- FS.readdir dir
log $ "Files in " <> dir <> ":"
traverse_ log files
VSCodeの拡張機能 PureScript IDEでimport文の自動並び替えをして、vscode-purtyで自動整形したら以下のようになった。
module Main where
import Prelude
import Data.Foldable (traverse_)
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class.Console (log)
import Node.FS.Aff as FS
main :: Effect Unit
main =
launchAff_ do
let
dir = "./"
files <- FS.readdir dir
log $ "Files in " <> dir <> ":"
traverse_ log files
Data.Foldable
のtraverse_
について。
Data.Foldable - purescript-foldable-traversable - Pursuit
traverse_ :: forall a b f m. Applicative m => Foldable f => (a -> m b) -> f a -> m Unit
シグネチャを見てもよくわからない。
Traverse a data structure, performing some effects encoded by an Applicative functor at each value, ignoring the final result.
データ構造を走査し、最終結果を無視して、各値で Applicative 関数によってエンコードされたいくつかの効果を実行します。
files
の各値にlog
を適用する、みたいな感じだろうか。ゆくゆくちゃんと学ぶ。