【Haskell】データコンストラクタにバリデーションを設ける方法
HaskellでCLIツールを作っているときに, 本記事のタイトルにあるようなことを疑問に思いました.
例えばサイコロを表すデータ型を次のように定義したとしましょう(diceの単数形はdieであることに注意). ここではDie
モジュールの中でDie
型を定義しています.
module Die where data Die = Die Int
しかしこれだと, 以下のような値も構築することができてしまいますね.
die_0 = Die 7 die_1 = Die 0 die_2 = Die (-2)
これではサイコロの目が1から6までであるという前提でコードを書くことができなくなってしまいます. 外部からこのモジュールを利用したい場合なんかは特に困りますね.
こんな状況で使えるのが, 「smart constructor」というパターンです.
smart constructor
早速コードの全体をお見せしましょう.
module Die ( Die , makeDie ) where data Die = Die Int makeDie :: Int -> Maybe Die makeDie n | 1 <= n && n <= 6 = Just $ Die n | otherwise = Nothing
ポイントは2つです.
makeDie
というバリデーション関数を定義する- データコンストラクタ
Die
はモジュール外に提供しない
特に2つめが重要です. いくらmakeDie
という関数で, 不正な数値によるDie
型の構築を排除できても, データコンストラクタDie
が使えてしまうのでは意味がありません.
上のコードでなぜデータコンストラクタDie
が外部から使えないようになっているのかを補足しておきます. もしデータコンストラクタDie
も外部へ提供する場合は以下のように書きます.
module Die where -- もしくは module Die ( Die(..) , makeDie ) where -- もしくは module Die ( Die(Die) , makeDie ) where
pattern synonyms
さてsmart constructorというパターンを使うことにより, 不正な引数で値を構築されることを防ぐことができるようになりました. しかしひとつ困ったことがあります. それは, データコンストラクタが提供されていないため, パターンマッチが使えないということです.
module Foo where import Die func :: Die -> Bar func Die n = -- このような書き方ができない
これでは不便ですから, smart constructorの恩恵を得ながらもパターンマッチが使えるようにしましょう. それを実現するのがPatternSynonymsという言語拡張です. ではこちらもコードの全体をお見せしてしまいましょう.
-- 言語拡張の使用を宣言 {-# LANGUAGE PatternSynonyms #-} module Die ( Die , makeDie , pattern Die ) where -- データコンストラクタにアンダースコアをつけた data Die = Die_ Int -- パターンマッチに使うキーワードを指定 pattern Die n <- Die_ n -- これはさっきと同じ makeDie :: Int -> Maybe Die makeDie n | 1 <= n && n <= 6 = Just $ Die_ n | otherwise = Nothing
まずはじめに言語拡張の使用を宣言しています. これによりpattern
関数が使えるようになります. 使い方はコード内に書かれている通りです.
pattern Die n <- Die_ n
これは, 「モジュール内で言うところのDie_ n
を, モジュール外にはDie n
として提供するよ(ただしパターンマッチでしか使えないけどね)」ということを表しています. こう捉えれば, 左向きの矢印が使われていることも理解しやすくなるかもしれません.
パターンマッチで使うキーワードにDie
を譲ったので, データコンストラクタにはDie_
を使っています.
最後に, モジュール外に提供する識別子の中にpattern Die
が含まれていることにも注意しておきましょう.
PatternSynonymsについては https://qiita.com/as_capabl/items/d2eb781478e26411a44c に詳しく書かれているので, 併せて参照していただくのがよいかと思います.
【Rails5】assert_differenceで複数の値をチェックする
概要
特定の処理の前後における数値の増減をテストしたいことはよくあります. 例えばPOSTリクエストの前後でUser
が増加するテストなどです.
これをテストするにはassert_difference
というメソッドを使い, 次のように書けばいいですね.
assert_difference 'User.count', 1 do post users_path, params: { user: { name: "foo" } } end
では一つの処理に対して複数の値をチェックしたい場合はどうすればよいでしょうか. 実はこれもassert_difference
でテストすることができます.
テストの書き方
詳しくは公式ドキュメントを参照してもらうことにして, ここでは2つのケースを考えてみます.
チェックする値は複数あるが, 増減幅は一緒の場合
例えば記事Article
を1つ投稿すると通知Notification
も1つだけ送られる場合を考えます. このときは以下のように書けばよいでしょう.
assert_difference [ 'Article.count', 'Notification.count' ], 1 do # 記事を投稿する処理 end
チェックする値が複数あり, それぞれ増減幅が異なる場合
例えば記事Article
を1つ投稿しつつ, 新たにタグTag
を2つ作成する場合を考えます. このときは以下のようにテストを書くことができます.
assert_difference ->{ Article.count } => 1, ->{ Tag.count } => 2 do # 記事を投稿する処理 end
ラムダを使った特殊な記法ですね.
ひとこと
あったらいいのになーと思う機能があったりなかったり. もっとRailsを理解していけば, 「こんな機能はRailsが用意してくれてるはずだ」と分かるようになっていくんでしょうかね.
【Rails】RakeタスクをMinitestでテストしてみた
はじめに
TDDでRakeタスクを作ろうとしたものの, テストをどう書けば良いのかがわかりませんでした. ネット上にあるのは, RSpecの記事や英語の記事ばかりだったため, MinitestでRakeタスクをテストする方法を書き残しておくことにしました.
主に参考にしたのは以下の記事です.
https://ieftimov.com/test-rake-tasks
Rakeタスクの例
具体例を考えるのが面倒なので, 自分が実際につくったタスクをもとに説明していきます.
以下は, YAMLファイルからデータを読み込んでusers
テーブルに登録するタスクです.
# lib/tasks/user.rake require 'yaml' namespace :user do desc "insert users" task create_users: :environment do users = YAML.load_file(File.dirname(__FILE__) + "/users.yml") users.map do |user| User.create!(user) end end end
テスト
テストコードは以下のように書きました.
# test/lib/create_users_test.rb require 'test_helper' require 'yaml' class CreateUsersTest < ActionDispatch::IntegrationTest def setup # タスクをロード # Myappはアプリケーション名 Myapp::Application.load_tasks # 挿入されるレコードの数を取得 @count = YAML.load_file(Rails.root.to_s + '/lib/tasks/users.yml').count end test "users creation" do # タスク実行前後でユーザーの数が変化していることをテスト assert_difference 'User.count', @count do Rake::Task['user:create_users'].invoke end end end
ポイントは, タスクをロードするコードと, タスクを実行するコードですね. これさえ押さえておけば, あとは普通のテストと一緒です.
ちなみにテストコード内のMyapp
はアプリケーション名ですが, これはconfig/application.rb
に定義されているモジュールの名前です. アプリケーションごと異なるので注意してください.
また, test/lib
というディレクトリは自分で作りました.
テストの実行
ここで作成したテストは, rails test:lib
のように実行できないようです. rails test
では実行されました. 個別にテストしたい場合はファイル名を指定するのが良さそうですね.
HaskellのStateモナドを定義から理解する
本記事の目的
Stateモナドの挙動を定義から導出したい. それが本記事の目標です.
Stateモナドについて調べると, いくつかの記事がヒットします. そこではStateモナドの使い方として以下のようなサンプルコードが引き合いに出されることが多いでしょう.
import Control.Monad.State pickHead :: State [Int] Int pickHead = do x:xs <- get -- 状態(整数のリスト)を取得 put xs -- 先頭以外の部分を新たな状態としてセット return x -- 先頭を返す sumHeads :: State [Int] Int sumHeads = do x <- pickHead -- 先頭を取得(このとき状態が更新される) y <- pickHead -- 先頭(初期状態では2番目の要素)を取得 return $ x + y -- 足して返す main = do print $ runState sumHeads [1, 1, 1, 1, 1] print $ runState sumHeads [1, 2, 3, 4, 5]
実行結果は以下の通りです.
(2,[1,1,1]) (3,[3,4,5])
コメントが書かれているため, どのように処理が進んでいくのかを追うことはできます. しかし釈然としません. なぜStateモナドはまるで状態を保持しているように振る舞うのかがわからないのです.
本記事ではそれを解決したいと思います.
解説
Stateモナドの定義
コードを定義から繙いていくわけですから, 定義の把握が必要ですね.
newtype State s a = State { runState :: (s -> (a,s)) } instance Monad (State s) where return a = State $ \s -> (a,s) (State x) >>= f = State $ \s -> let (v,s') = x s in runState (f v) s'
class MonadState m s | m -> s where get :: m s put :: s -> m () instance MonadState (State s) s where get = State $ \s -> (s,s) put s = State $ \_ -> ((),s)
ひとつの補題
サンプルコードを理解する上で助けになる補題をひとつ挙げておきます. 補題といっても定義をそのまんま適用したものにすぎません.
まずは補題に必要な登場人物の紹介です.
m :: State s a f :: a -> State s b x :: s
上記の設定のもとにおいて, 以下の2式は等価です.
runState (m >>= f) x
runState (f a) x'
ただし以下の言い換えをしています.
(a, x') = runState m x
記号a
が重複していますが, そのほうが対応がわかりやすいだろうとの判断です.
以上が補題の主張です. >>=
の定義を追っていけば確かめられるでしょう. この補題により, >>=
をひとつずつほぐしていけるようになりました.
コードを読み解いていく
最初に挙げたサンプルコードを解読していきます.
まずは部分的に
まずは以下の式が何をどう返すのかをみていきましょう.
runState pickHead [1,2,3]
pickHead
の定義をそのまま適用します.
runState ( x:xs <- get put xs return x ) [1,2,3]
ただし上の式は文法的に正しくありません. <-
等の記号はdo
ブロックの中でしか使えないからですね. 見辛くはなりますが, 正しく書き換えましょう.
runState ( get >>= \(x:xs) -> put xs >> return x ) [1,2,3]
こんな風にラムダ式の中でもパターンマッチが使えるんですね. さてこれで, 上で述べた補題が使える形になりました. では一つ目の>>=
を解消しましょう.
get
の定義から
runState get [1,2,3] == ([1,2,3],[1,2,3])
ですから, もとの式は次の式に等価です.
runState (f [1,2,3]) [1,2,3]
ただしf = \(x:xs) -> put xs >> return x
という置き換えをしています.
さて,
f [1,2,3] == put [2,3] >> return 1
ですから, 結局もとの式は次の式に等価です.
runState (put [2,3] >> return 1) [1,2,3]
>>
は>>=
の特殊なバージョンですから, この式にも補題を適用することができますね.
put
の定義から,
runState (put [2,3]) [1,2,3] == ((), [2,3])
ですから, 結局もとの式は次の式に等価です.
runState (return 1) [2,3]
これを計算することにより, 以下が得られました.
runState pickHead [1,2,3] == (1, [2,3])
そして全体へ
今得られた等式を利用して, コード全体を読み解いていきましょう.
評価したいのは以下の式です.
runState sumHeads [1,2,3]
これが(3, [3])
に等しいことを定義から導出していきます.
上の式に, sumHeads
の定義を文法が正しくなるように適用します.
runState ( pickHead >>= \x -> pickHead >>= \y -> return $ x + y ) [1,2,3]
先と同様に補題を適用して>>=
を解消していきます.
上で得られた結果から
runState pickHead [1,2,3] == (1, [2,3])
ですから, もとの式は次の式に等価です.
runState ( pickHead >>= \y -> return $ 1 + y ) [2,3]
同様にして以下のように変形できます.
runState ( return $ 1 + 2 ) [3]
これを計算することにより, 以下の式が得られました.
runState sumHeads [1,2,3] == (3,[3])
まとめ
なんとか処理を追いかけることができました. 変化させた状態を次々にリレーしていく様子が明らかになったのではないでしょうか. このようにしてStateモナドは状態を保持しているように振る舞うことができるわけですね.
【GAS】GoogleDrive上のExcelをGoogleスプレッドシートに一括変換
概要
まだまだExcelさんは現役ですね. でもMac使いだとOfficeがインストールされていなかったり, 共有したいときに不便だったりします. そんなときはGoogleスプレッドシートに変換してしまいましょう. GoogleDrive上でExcelファイルを開くと, 「Googleスプレッドシートで開く」というボタンが表示されます. それをクリックすればGoogleスプレッドシートに変換されたファイルが新たに生成されます.
ただし変換したいファイルが多数ある場合はプログラムで一括変換したいものですね. この記事ではGASからDrive APIを利用することにより, ExcelファイルをGoogleスプレッドシートに一括変換する方法をご紹介します.
ただし注意していただきたいのは, ExcelとGoogleスプレッドシートは完全な互換性があるわけではありません. Excelにしかない関数やマクロを使っている場合はうまく変換できないことがあります.
参考ページ
いろいろ検索しまくって, 一番参考になったのはやっぱり公式リファレンスでした. このページには, GASからDrive APIを使う方法が書かれています. 本記事では少し手を加えて, Excelをスプレッドシートに変換できるようにしていきます.
日本語のページではこちらも大いに助けになりました.
本記事ではGASの基礎を前提として進めていきます. 分からないところがあれば, 前回の記事を参考にしてください.
やってみよう
用意するもの
同じフォルダに格納されても全く問題はありませんが, 今回は分けることとします.
早速コード
function myFunction() { // Excelファイルが入っているフォルダをidによって取得 var source_folder = DriveApp.getFolderById('*****'); // Excelファイルたちを変数に保存 var excel_files = source_folder.getFiles(); // 変換されたファイルが格納されるフォルダをidによって取得 var dest_folder = DriveApp.getFolderById('*****'); // Excelファイルをイテレートして順にスプレッドシートに変換 while(excel_files.hasNext()) { var file = excel_files.next(); convertToSpreadsheet(file, dest_folder); } } function convertToSpreadsheet(file, folder) { // 各種オプションを設定 // mimeTypeの指定により, スプレッドシートに変換される options = { title: file.getName(), mimeType: MimeType.GOOGLE_SHEETS, parents: [{id: folder.getId()}] }; // Drive APIへfileをPOSTする Drive.Files.insert(options, file.getBlob()) }
Drive APIを利用しているのはDrive.Files.insert
の部分です.
Drive APIを有効にする
コードを書いただけではAPIを利用することはできません. APIを有効にするには, 以下の手順を踏む必要があります.
- 右端にある「認証情報を作成」をクリック
- 認証情報を以下のように作成
認証情報が作成できれば, このページは閉じてもらって構いません. APIの一覧が並んだダイアログの「OK」を押せば, APIの有効化は終了です.
実行
以上で準備は整いました. 初回実行時は認証が必要ですから, 指示にしたがって認証をしましょう. もしうまくいかないようであれば, 前回の記事を参考にしてください.
GASからGoogleDrive操作してみた.
概要
Google Apps Scriptを使って, Google Drive上のファイルにアクセスしてみました. 当然ながら, アクセスするのは自分のGoogleアカウントに紐づいたDriveです.
情報源
基本的には公式リファレンスを読めば大体のことがわかります. 困ったときはそちらを参照するのがよいでしょう.
やってみよう
例として, 特定のフォルダにあるファイル名を全てログに出力するということをやってみます.
コード
まずはじめにコード全体をお見せします.
function myFunction() { // idをもとにフォルダを取得 var folder = DriveApp.getFolderById('********'); // フォルダに属するファイルを全て取得 var files = folder.getFiles(); // filesをイテレートして, ファイル名をログに出力 while(files.hasNext()) { var file = files.next(); Logger.log(file.getName()); } }
解説
はじめ, IDをもとにフォルダを取得していますが, そもそもフォルダのIDとは何なのでしょうか. それはGoogleDrive内のフォルダをひとつ開いてみるとわかります. URLが以下のようになっていますよね.
https://drive.google.com/drive/folders/******
******
の部分がこのフォルダのIDです. これをコピペすればいいというわけです.
次に変数files
についてです. これはリファレンスを見ればわかるように, FileIterator
クラスのオブジェクトです. ファイルの配列ではないんですね. したがってファイルを順に処理したい場合は上のコードのようになります.
実行
実行するには三角の再生ボタンを押します. 初回の実行時は, 「承認が必要です」とのダイアログが表示されるでしょう. その場合は以下の手順を実行します.
- 「許可を確認」をクリック
- 自分のアカウントを選択
このとき, 「このアプリは確認されていません」というダイアログが表示された場合は, 追加で以下の手順を実行します.
- 「詳細」をクリック
- 「プロジェクト名(安全ではないページ)に移動」をクリック
- 「許可」をクリック
これでコードが実行されるはずです. 上部にある「表示」メニューからログを選ぶと, ファイル名が順に表示されているのが確認できます.
さいごに
Google Driveの特定のフォルダにあるファイル名を, すべて列挙することに成功しました. とはいえこれができたところでなにも嬉しくはありません. 次回の記事では今回の応用として, ドライブのフォルダ内にあるエクセルファイルを一括でGoogleスプレッドシートに変換する方法を解説します.
Slack Botの土台をGASで構築
Slack Botの土台をGASで構築
業務効率を上げたり, 時には癒しをくれたり, 調べてみると様々なBotの活用事例があるようです. この記事ではSlackBotの土台となるものをGASで構築していきます.
ゴール
この記事を読んだらなにができるようになるかを述べておきましょう. 本記事におけるBotの土台とは,
Slackで特定のワードを発言すると, Botが反応して一定の返事をくれる
というものです. これだけっちゃこれだけですが, GASを利用しているので, 以下のようなBotに発展させることもできるでしょう.
- Googleカレンダーのイベントを教えてくれる
- Google翻訳を利用して, 日本語を英訳してくれる.
手順
以下のような手順で進めていきます.
Webhook URLを取得
Webhook URLというのは, メールアドレスのようなものですね. このURLに対しPOST
リクエストを投げることで, Slackにメッセージを投稿することができます. ではやっていきましょーう.
まずはSlack APIにアクセスしましょう. 右上のCreate New App
をクリックします. そうなんです実は, これからBotをSlackアプリとして作ろうとしているんですね. さてCreate New App
をクリックすると次のようなダイアログが出現します.
上の欄には好きなアプリ名を, 下の欄では実験用のWorkspaceを選択しましょう. 注意書きにあるように, アプリ名は後から変えられるので心配いりません.
入力を終えたらCreate App
をクリックします.
すると以下のようなページに飛びます.
まずはBots
をクリックし, このアプリがBotとして動作するよう設定します. といっても2つの入力欄を埋め, Always Show My Bot as Online
をオンにするだけです.
この設定を保存したら, 次に左のメニューのIncoming Webhooks
をクリックし, トグルをONにします.
下の方にいってAdd New Webhook to Workspace
をクリック.
Botからの返信を待ち受けるチャンネルをここで選択します. 選択できたらAuthorize
をクリック.
するとSuccess!
というFlashメッセージとともに, 先のページにリダイレクトされます. 下のほうをみると, Webhook
が追加されているのがわかりますね. Webhook URLもちゃんと書いてあります. これで第一段階は終了です.
SlackからPOST
リクエストを受け取り, POST
リクエストを返す機構をGASで構築
さて次はGASです. Google ドライブ にアクセスし, GASプロジェクトを新規作成します. プロジェクト名はお好きに決めてください.
ここからはコーディングです. GASのベースはJavaScriptですから, メソッド名の最後に()
をつけたり, 式をセミコロンで締めくくったり, 気をつけなければならないことがたくさんありますね...
// POSTリクエストを受け取る関数 function doPost(e) { // Webhook URLを変数に格納 var url = 'https://hooks.slack.com/services/ほにゃらら'; // メッセージ本文 var payload = { 'text': 'ここが本文' }; // HTTPリクエストの設定 var options = { 'method': 'post', 'contentType': 'application/json', 'payload': JSON.stringify(payload) } // SlackへのPOSTリクエストを発行 UrlFetchApp.fetch(url,options); }
各コードの意味はコメントに書いたので, だいたい理解できるかと思います.
コードが書けたら上の方にあるデバッグボタンを押して, 問題がないかチェックします. 問題がなければコードが実行されるので, Slackチャンネルに「ここが本文」というメッセージが投稿されるはずですが, どうでしょうか...?
次はこのアプリをネット上に公開する必要があります. そうでないとSlackからPOST
リクエストを投げられないですからね. 上の方の「公開」から, 「アプリケーションとして導入」を選択します.
アプリケーションにアクセスできるユーザーを, 「全員(匿名ユーザーを含む)」に設定し, 「導入」をクリックします. 「変更内容の説明」は空欄でOKです.
すると承認が必要という旨のダイアログが出るので, 誘導にしたがって承認作業をしてください.
次のようなダイアログが出れば成功です. 「現在のウェブ アプリケーションのURL」はあとで使うので, ダイアログは出したままでOKです.
これで第二段階は終了です.
SlackからPOST
リクエストを発行するためのトリガーを設定
次は, SlackにTrigger word
を設定します. まず実験用に用意したWorkspaceへアクセスしてください.
左のペインからアプリを検索します.
Outgoing WebHooks
を検索します. 数文字入力すれば十分でしょう. インストールボタンをクリックします.
Add Configuration
をクリックします.
次のページでAdd Outgoing WebHooks integration
をクリックします.
リダイレクト先のページを下にスクロールすると, 以下のような入力フィールドが現れます.
Channel
ではTrigger word
に反応してほしいチャンネルを選択します.
Trigger words
はコンマ区切りで入力しましょう.
URL
にはさっきの「現在のウェブ アプリケーションのURL」を入力します.
以上の入力ができたら一番したまでスクロールし, Save Settings
をクリックします.
これで第三段階も完了です.
完成とその後
これで全ての手順が終了しました. Slack上でTrigger word
を送信すると, 自動で返信が来ることが確認できるかと思います. 返信の表示形式をもっとリッチにする方法を, またこんど記事にするつもりです.
そして上でも述べましたが, GoogleカレンダーやGoogle翻訳, Gmailなどのサービスを活かしたBotを作成することもできます. 本格的なBotを作るのは無理でも, 多少遊んでみるくらいはできそうです.