トップページへ

情報支援プログラムTjShien01.hsの解説用文書

Access Count : 853

{-
Copyright © 2020 TAKEHANA TADASHI

 配布、配信、公然での口述、販売、改変、をしないでください。

<TjShien01.hsプログラムの解説用文書>

著作日時: 2020.07.31.金. 13:49:00 著作者、竹花 忠

著作者、竹花 忠


<コマンドプロンプト用プログラム>
情報支援プログラム(※フィルタープログラム)
 フィルタープログラムとは、標準入力からデーターを取得して、それを処理した結果を、標準出力から表示して終了するプログラムである。
 コマンドプロンプトにおいて、コマンド名・プログラム名、と必要に応じて、空白文字区切りで、引数をタイプ入力して改行をタイプすることで、コマンドが・プログラムが、起動します。
 このコマンドプロンプトにおいてタイプ入力した、コマンドの・プログラムの、引数が、コマンドライン引数・コマンドラインパラメータ、です。
 ちなみに、コマンドライン引数・コマンドラインパラメータ、は、コマンドの・プログラムの、起動時に、コマンドに・プログラムに、わたされます。なので、コマンドでは・プログラムでは、起動時に、コマンドライン引数・コマンドラインパラメータ、を取得することができます。

※注.
 先頭に--が付いている行は、コメント行です。プログラムのコードではありません。
 また、{- と -} で、挟まれた・囲まれた・括られた、範囲もコメントです。プログラムのコードではありません。

※注.
 Haskell言語のソースプログラムのファイルタイプ名は、.hsにしてください。
 また、Haskell言語のソースプログラムは、UTF8のコード体系で保存してください。


 参考書籍: すごいHaskellたのしく学ぼう!
-}
-- 以下よりHaskell言語によるプログラムです。

import Data.List
import System.IO
import System.Directory
import System.Environment
import Control.Exception
import Control.Monad

{-概要・予備知識
 プログラム起動時、プログラム名の次の引数に、検索対象とするファイルの名前を指定します。
 "検索文字列:"のプロンプトに対して、検索対象とするファイル中の1行に含まれいる文字列をタイプ入力して改行します。
 すると、→行以下に、その文字列が含まれている1行がすべて表示されます。
 "検索文字列:"のプロンプトに対して、改行だけを入力すると本プログラムは終了します。
-}

main = do
  args <- getArgs
  let
    [targetFile] = args
    argcnt = length args
  if argcnt /= 1
    then
      putStrLn "使い方 : \nTjShien01 検索対象ファイル名"
    else do
      contents <- readFile targetFile
--targetFileに格納されている名前のファイルから、検索対象コンテンツを取り込む。
      let
        lineContents = lines contents
      pickupLineLoop lineContents

{-
 まずは、上記の関数mainから説明します。
 args <- getArgs、にて、コマンドライン引数の1つ1つを要素としたリスト、が取得されます。
 argsは、[String]です。

 [targetFile] = args、にて、argsリストの当該位置のデータが、左辺の変数に取得されます。
 targetFileは、Stringです。

 argcnt = length args、にて、コマンドラインで取得した引数の個数がargcntに取得されます。
 argcntは、Intです。

 if argcnt /= 1
   then
   else
によって、コマンドラインで取得した引数の個数が1個でなかった時の処理がthenの次に記述されます。
 また、コマンドラインで取得した引数の個数が1個であった時の処理がelseの次に記述されます。
 そして、thenの最終的な返り値の型とelseの最終的な返り値の型とは、同一の型である必要があります。
 このif argcnt /= 1
     then
     else
という文は、関数mainの最後の文なので、thenやelseのそれぞれ最後の関数の実行が終了した段階で、本プログラムは終了します。
 putStrLn "使い方 : \nTjShien01 検索対象ファイル名"、は、引数の個数が1個でなかった時の処理です。エラーメッセージです。コマンドラインへの正しい入力の仕方を表示しています。そして本プログラムは終了します。
 引数の個数が個だった時には、elseの後続の処理に進みます。
 contents <- readFile targetFile、にて、コマンドライン引数を格納しているtargetFileの内容を検索対象ファイル名として、そのファイルの全内容を、contentsに取得します。
 lineContents = lines contents、にて、その全内容を、1行ごとに1要素としたリスト形式のデータに変換したものを、lineContentsに取得します。
 pickupLineLoop lineContents、にて、先のリスト形式のデータを引数にして、関数pickupLineLoopを呼び出します。
 以上で処理は終了です。つまり、pickupLineLoopが終了してしまえば、本プログラムは終了します。
 ちなみに、ここで、thenの最終的な型はIO ()でした。elseの最終的な型もIO ()でした。
-}


-- 次に、下記の関数pickupLineLoopを説明します。

--pickupLineLoop lineContents
pickupLineLoop :: [String] -> IO ()
pickupLineLoop lineContents = do
  putStrLn "検索文字列 : "
  searchString <- getLine
--標準入力から、検索文字列を取り込む。
  if null searchString
    then
      putStrLn "情報支援プログラム:TjShien01は、終了しました!!"
    else do
      let
        selectLines = filter (isInfixOf searchString) lineContents
      putStrLn "→"
      mapM_ putStrLn selectLines
      putStrLn ""
      pickupLineLoop lineContents

{-
 putStrLn "検索文字列 : "、にて、検索文字列の入力を促すプロンプト"検索文字列 : "、を表示します。

 searchString <- getLine、にて、タイプ入力された改行までのうち、改行を除いた文字列がsearchStringに取得されます。
 searchStringの型は、Stringです。

 if null searchString
    then
    else
によって、searchStringの中身が空だった時、thenの次からが実行されます。
 また、searchStringの中身が空でなかった時、elseの次からが実行されます。
 thenの最終的な型とelseの最終的な型は同一の型でなければなりません。
 このif null searchString
     then
     else
という文は、関数pickupLineLoopの最後の文なので、thenやelseのそれぞれ最後の関数の実行が終了した段階で、本プログラムは終了します。
 putStrLn "情報支援プログラム:TjShien01は、終了しました!!"、は、searchStringが空だった時に実行されます。"情報支援プログラム:TjShien01は、終了しました!!" 、と表示して本プログラムは終了します。これは、本プログラムの通常の終了の仕方です。
 searchStringが空でなかった時には、elseの後続の処理に進みます。

 selectLines = filter (isInfixOf searchString) lineContents、にて、lineContentsの各1行のデータの中に、searchStringに取得されている検索文字列が含まれていれば、その1行を要素として追加してリストを作ります。そのリストが、selectLinesに取得されます。
 詳細に説明すると次のようなことです。
 関数filterは、第1引数に、述語とか述語関数とか呼ばれる、真/偽、を返す関数、第2引数にリスト、を設置して使用する関数である。
 なお、第1引数の、述語・述語関数、には、第2引数のリストの各要素が順番に供給される。
 そして、第1引数は、述語・述語関数、による評価に基づいて、真か偽かを返す。
 関数filterは、述語・述語関数、が真を返した時の、述語・述語関数、に供給されたリストの要素をリストアップする。
 filter (isInfixOf searchString) lineContents、はその一例である。
 ここで、関数isInfixOfは、その第1引数が、その第2引数の中に含まれていた時に真を返し、それ以外の時に偽を返す、関数である。
 ここでは、その関数isInfixOfに第1引数が適用してあるので、つまり、(isInfixOf searchString)となっているので、(isInfixOf searchString)全体で、引数1個を要求する関数、として扱われる・となっている。
 (isInfixOf searchString)の引数として文字列が供給されると、その文字列の中に、変数searchStringに格納されている文字列が含まれていた時に真を返し、それ以外の時に偽を返す。
 そしてここで変数lineContentsは、各要素が各1行の文字列から成るリストである。
 なので、関数filterの機能により、(isInfixOf searchString)に毎回、lineContentsから順番に各1行の文字列が供給される。>
 そして、(isInfixOf searchString)が真を返した時の1行の文字列が、関数filterの機能により、リストアップされてゆく。
 したがって、filter (isInfixOf searchString) lineContents、によって、変数searchStringに格納されている文字列を含んでいる、リストlineContentsの要素をリストアップしたリストが取得されます。
 なお、関数filterの第1引数である、述語・述語関数、は、1引数の関数であることが要求されます。
 関数isInfixOfは2引数関数ですが、そのうちの引数1個を、適用して・部分適用して、(isInfixOf searchString)とすることで、残り1個の引数だけを要求する1引数関数、となっています・の扱いとなります。
 ですから、filter (isInfixOf searchString) lineContents、は正常に動作します。

 putStrLn "→"、にて、"→"を表示します。

 mapM_ putStrLn selectLines、にて、検索文字列を含んでいた行を要素とするリストselectLinesを、1要素ずつ表示し尽くします。
 ここでは、関数putStrLnによる画面へのリストデータの表示だけが目的であって、その結果の返り値である()を取得することは目的ではない。
 なので、返り値は捨ててしまうことになるmapM_を使う。
 mapM_ではなく、mapMを使えば、返り値の取得も行われる。
 rval = mapM putStrLn ["ab", "cd", "ef"]、なら、rvalに[(), (), ()]が取得される。
 mapM_を使用した場合には、同様の記述にしても、つまり、
 rtval = mapM_ putStrLn ["ab", "cd", "ef"]、としても、rtvalは、()となるだけで、putStrLn "ab", putStrLn "cd", putStrLn "ef"、のそれぞれを実行した返り値のリストアップは行われない。
 mapMにしてもmapM_にしても、共通なのは、第1引数の関数を、第2引数のリストの各要素に割り当てて形成される関数呼び出しのリストを、順次、実行すること。
 mapMとmapM_の違いは、mapMは、関数呼び出しのリストを実行したあとの返り値をリストアップするが、mapM_は、関数呼び出しのリストを実行したあとの返り値を捨て去ってしまうことである。
 ちなみに、mapM putStrLn ["ab", "cd", "ef"]、は、sequence [putStrLn "ab", putStrLn "cd", putStrLn "ef"]、と同等である。

 putStrLn ""、にて、セパレータとして1行、空行を表示します。

 pickupLineLoopを、またlineContentsを引数にして呼び出します。

 thenの最終的な型は、putStrLnの返り値の型のIO ()です。
 elseの最終的な型は、pickupLineLoopの返り値の型のIO ()です。
 thenもelseも最終的な型が同一だったので、このif then else文は、正常に動作します。

 ですから、本プログラムは、"検索文字列 : "、のプロンプトに対して、改行だけを入力した時に終了し、それ以外の入力の時には、検索結果を表示しては再び、"検索文字列 : "、のプロンプトを表示して、待機状態になることを繰り返します。
 以上が本プログラムが行っていることです。
-