読者です 読者をやめる 読者になる 読者になる

技術のメモ帳

気が向いたときに書いてます

elixir学習日誌: 標準入出力..例外処理

標準入出力

IO.puts :stdio, "fantastic!"
IO.puts :stderr, "goddness!"

標準入力

case IO.gets "What's your name?: " do
  "Tom\n"    -> IO.puts "Oh! Tom. Good name."
  "Jesica\n" -> IO.puts "Jesica! How are you?"
  _          -> IO.puts "Nobody knows you"
end

ファイル操作

File.write! "fruits.txt", "apple\norange\nbanana\n"
list = File.read! "fruits.txt"
IO.puts list

Path操作

Path.join "foo", "bar" # => foo/bar
Path.expand "~/baz"    # => /home/Users/baz

標準入出力・ファイル・PathはすべてRubyっぽい。

例外処理

どんな言語でもお馴染みの例外処理です。

try do
  raise "Oh! My goddness."
rescue
  e in RuntimeError -> IO.inspect e
after
  IO.puts "What's up?"
end

Ruby訳すると分かりやすい。

begin
  raise "Oh! My goddness."
rescue => e
  puts e.inspect
ensure
  puts "What's up?"
end

また、try...catchの使い方もある。

try do
  throw "hello"
catch
  t -> IO.inspect t
end

再びRuby訳。これは少しニュアンスが変わりそうだけど、シグナルを受け取って、処理を抜ける意味では同じかな。

catch :hello do
  3.times do |i|
    throw :hello if i == 2
  end
end

参考

elixir学習日誌: 再帰とStream

再帰

末尾再帰とは?

defmodule Person do
  def say(message, n) when n == 1 do
    IO.puts message
  end

  def say(message, n) do
    IO.puts message
    say message, n - 1 # ←この部分が末尾再帰
  end
end
Person.say "Hello", 3
# => Hello
# => Hello
# => Hello
  • 末尾再帰にすると、スタックを積まないので、リソースの無駄使いをしない。

Enumerable

  • Enumerableとは「列挙可能」という意味。
  • リストやマップは、Enumerableな構造データ。
Enum.map %{ first_name: "taro", family_name: "yamada" }, fn {k, v} -> "#{k} is #{v}" end
# => ["family_name is yamada", "first_name is taro"]

パイプ演算子

["foo", "bar", "baz"] |> Enum.map(fn x -> x <> x end) |> Enum.reduce("", fn x, result -> result <> x end)
# => "foofoobarbarbazbaz"
  • |> は、|>の左側を、右側の第一引数として渡すための演算子
  • 一時変数を書かずに、記述できる(関数型言語らしい)。
  • カリー化のような雰囲気。

Stream

m = ["foo", "bar", "baz"]

IO.inspect m |> Enum.filter(fn x -> x in ["foo", "bar"] end) |> Enum.map(fn x -> x <> x end)

s = m |> Stream.filter(fn x -> x in ["foo", "bar"] end) |> Stream.map(fn x -> x <> x end)
IO.inspect Enum.to_list s
  • Streamを使うと、遅延評価され、to_listで呼び出されるまで、リストの中身を列挙する処理は実行されない。
  • Streamは、毎回列挙せず、一度限りの列挙となる(遅延評価しない場合は、毎回列挙することになる)。

参考

elixir学習日誌: 関数

基本

defmodule Say do
  def hello name do
    "Hello, #{name}!"
  end
end

IO.puts Say.hello "Alice" # => "Hello, Alice!"
  • defmodule関数をグループ化するためのモジュール定義
  • すべての関数は、モジュール名.関数名 引数 で呼び出せる。
  • 関数の呼び出しには () が付けられるが、ミミックメソッドの書式も可。

関数のスコープ

defmodule Say do
  defp bye name do
    "Bye, #{name}..."
  end
end

IO.puts Say.bye "Bob" # => UndefinedFunctionError
  • defpはモジュールの外からは呼び出せない。
  • defAPIを定義し、defpは内部実装を定義する。

ガード節

defmodule School do
  def classmate?(name) when name in ["Alice", "Bob"] do
    true
  end
end

School.classmate? "Alice"   # => true
School.classmate? "Charles" # => FunctionClauseError
  • 関数が呼び出される時点で、条件分岐を行う。
  • falseの場合の関数も定義する必要がある(例外が発生するため)。

引数のデフォルト値

defmodule Say do
  def hello name \\ "Alice" do
    "Hello, #{name}!"
  end
end

Say.hello # => "Hello, Alice"
  • \\ で区切るとデフォルト値が指定できる。

無名関数

greeting = fn name -> "How are you, #{name}?" end
greeting.("Alice") # => "How are you, Alice?"
  • fn(args) ->で無名関数が定義できる。
  • 名前付き関数との使い分けは、使い捨てにできるか否か

標準ライブラリ Enum

関数型言語なので、Enumを巧みに扱うことが求められる。

TODO: empty? sort reverse uniq with_index flat_map zip

Enum.map [1, 2, 3, 5, 8], fn(x) -> x*x end                   # => [1, 4, 9, 25, 64]
Enum.reduce [1, 2, 3, 4, 5], 0, fn(x, sum) -> sum + x end    # => 15
Enum.filter [:foo, :bar, :baz], fn(name) -> name == :foo end # => [:foo]
Enum.reject [:foo, :bar, :baz], fn(name) -> name == :foo end # => [:bar, :baz]
Enum.find [1, 2, 3], fn(x) -> x == 2 end                     # => 2
Enum.join [1, 2, 3], ","                                     # => "1,2,3"
Enum.each [1, 2, 3], fn(x) -> IO.puts x end                  # => 1 2 3
Enum.empty? []                                               # => true
Enum.sort [1, 2, 3], fn(a, b) -> a > b end                   # => [3, 2, 1]
Enum.reverse [1, 2, 3]                                       # => [3, 2, 1]
Enum.uniq [1, 1, 2]                                          # => [1, 2]
Enum.with_index [1, 2, 3]                                    # => [{1, 0}, {2, 1}, {3, 2}]
Enum.flat_map [[1, 2], [3, 4]], fn(x) -> x end               # => [1, 2, 3, 4]
Enum.zip [:foo, :bar], [1, 2]                                # => [foo: 1, bar: 2]

Ruby訳は以下となります。普段使っているメソッドと同じなので、かなり覚えやすいです。

[1, 2, 3, 5, 8].map {|x| x*x }
[1, 2, 3, 4, 5].reduce(0) {|sum, x| sum + x }
[:foo, :bar, :baz].select {|name| name == :foo }
[:foo, :bar, :baz].reject {|name| name == :foo }
[1, 2, 3].find {|x| x == 2 }
[1, 2, 3].join(",")
[1, 2, 3].each {|x| puts x }
[].empty?
[1, 2, 3].sort {|a, b| b <=> a }
[1, 2, 3].reverse
[1, 1, 2].uniq
[1, 2, 3].map.with_index {|x, i| [x, i] }
[[1, 2], [3, 4]].flat_map {|x| x }
[:a, :b].zip([1, 2])

参考

Part13:関数 | μ'sと学ぶソフトウェア開発入門

qiita.com

もっとEnumを知りたい場合は下記がいい感じ。all?やany?なんてメソッドもある。

http://elixir-lang.org/docs/v1.0/elixir/Enum.html

elixir学習日誌: データ型〜パターンマッチング

学習動機

不純な動機は、ひとつもありません。

ゴール

  • Phoenixを使って、APIサーバを立てる。

余談

たいていどんな言語も、次のパスをなぞれば、なんかしらのカタチにはなる...と思っている。

  1. FizzBuzzしながらシンタックスを覚える。
  2. 主要ライブラリを“使う”
  3. フレームワークに触れる
  4. APIをサーブする
  5. ファイル操作関連を覚える
  6. DBと連携する
  7. フレームワークで何か作る

教材

ラブライブは知らないので、どんな世界観が分からなかったが、非常に丁寧に解説されていた...有り難い)

インストール

データ型

Type Content
integer 整数
float 浮動小数
atom アトム
string 文字列
list リスト
tuple タプル
map マップ
keyword キーワードリスト

整数・浮動小数

1      # integer (decimal)
0x1F   # integer (hexdecimal)
0o765  # integer (octal)
0b1010 # integer (binary)
1.0    # float
1 + 2  # => 3
3 - 1  # => 2
5 * 5  # => 25
10 / 2 # => 5.0 必ずfloat
rem 10, 3   # => 1
max 3, 2    # => 3
min 3, 2    # => 2
round 2.52  # => 3
trunc 2.52  # => 2

文字列

"ハロー" # => "ハロー"              : string
'ハロー' # => [12495, 12525, 12540] : list
  • 結合は<>を使う
"foo" <> "bar"   # => foobar
"#{20 + 5} year" # => 25 year
String.length "hello"       # => 5
byte_size "hello"           # => 5
String.at "hello", 1        # => "e"
String.split "foo,bar", "," # => ["foo", "bar"]

リスト

  • 飛び飛びのメモリ領域を結びつける。
  • 先頭から順に辿っていく(検索時にオーバーヘッドがある)
  • ランダムアクセスには向かない。
[1, :foo, "bar"]            # => [1, :foo, "bar"]
hd [:foo, :bar, "baz"]      # => :foo
tl [:foo, :bar, "baz"]      # => [:bar, "baz"]
Enum.at [25, :num], 1       # => :num
Enum.at [25, :num], 2       # => nil
length [1, 2, 3]            # => 3
Enum.sum [1, 2, 3]          # => 6
Enum.reverse [1, 2, 3]      # => [3, 2, 1]
List.first [1, 2, 3]        # => 1
List.last [1, 2, 3]         # => 3
List.flatten [[1, 2], 3, 4] # => [1, 2, 3, 4]

タプル

  • 連続したメモリ領域を確保するので、アクセスしやすい(同じ処理時間でたどれる)
  • ランダムアクセスに向いている。
{1, 2, 3}
elem {1, 2, 3}, 1 # => 2
  • タプルは変更には向かない。できないこともないけれど。
Tuple.delete_at {1, 2, 3}, 0 # => {2, 3}
put_elem {"a", "b"}, 1, "c"  # => {"a", "c"}
tuple_size {1, 2, 3}         # => 3
  • 長さを計算する関数はlength、決まっている長さを返すのはsizeって名前を使う
  • タプルは最初から長さが決まっているので「size」を使う
  • リストとタプルは相互変換可能
List.to_tuple [:foo, :bar, :baz] # => {:foo, :bar, :baz}
Tuple.to_list {1, 2, 3}          # => [1, 2, 3]

マップ

  • リストやタプルに近いデータの集まり。
  • 順序はないが、データに見出しをつけられる。
%{:umi => "foo", :sora => "bar"}
%{:umi => "foo", :umi  => "bar"} # => %{umi:"bar"}
  • キーの重複はできない仕様で、後から書いた方が優先される。
  • 添字で値を取得できる。
%{:foo => 1, :bar => 2, :baz => 3}[:baz] # => 3
  • 値は更新しながらもデータは不変なのはElixirの特徴
%{foo: 1, bar: 2, baz: 3}
  • ファットアローを使わない書き方もできる。
Dict.size %{ foo: 1, bar: 2, baz: 3 }                # => 3
Dict.keys %{ foo: 1, bar: 2, baz: 3 }                # => [:foo, :bar, :baz] ※順序不確定
Dict.split %{ foo: 1, bar: 2, baz: 3 }, [:foo, :bar] # => [%{ bar: 2, foo: 1 }, %{ baz: 3 }]
  • splitは指定した引数を含むもの/含まないものを分割する

キーワードリスト

[{:foo, 1}, {:bar, 2}, {:baz, 3}] # => [foo: 1, bar: 2, bar: 3]
  • 左辺の省略記法がキーワードリスト
  • 最初の要素がアトムになっているタプルのリスト
[foo: 1, bar: 2, baz: 3] ++ [foobar: 4] # => [foo: 1, bar: 2, baz: 3, foobar: 4]
  • リストなので、リストと同じ操作ができる
length [foo: 1, bar: 2, baz: 3] # => 3
  • マップのような要素アクセスも可能
[foo: 1, bar: 2, baz: 3][:foo] # => 1
  • ただし、あくまでリストなので、ランダムアクセスには向いていない。

真偽値

  • == と === は厳密に型まで比較している(JavaScript風味?)

パターンマッチング

  • = 演算子のニュアンスを知る。
  • 左右の値が同じかどうか比較する。
  • 変数の場合は代入が行われる。
foo   = "bar"
"bar" = foo  # => "bar"
"baz" = foo  # => MatchError
  • = は「比較と代入ができる便利な演算子」と覚えるとよい。
{foo, bar, baz} = {1, 2, 3}
foo # => 1
  • タプルでマッチさせると、多重代入にも使える。
{foo, bar, baz} = [1, 2, 3]    # => MatchError
{foo, bar, baz} = {1, 2, 3, 4} # => MatchError
  • 両辺の型が異なると例外が発生する。
  • 両辺の要素数が合っていないと例外が発生する。
[head | tail] = [:foo, :bar, :baz]
IO.puts head # => :foo
IO.puts tail # => [:bar, :baz]
  • | を使うと、先頭とそれ以外で分割できる。
{name, name} = {"taro", "taro"}   # => {"taro", "taro"}
{name, name} = {"taro", "hanako"} # => MatchError
  • 同じ変数を2回利用できるが、異なる値は代入できない。
  • Elixurでは基本的に再代入を行わない
{^name, name} = {"taro", "hanako"}
  • ^ はピン演算子で、代入の対象とはならない。
{_, _, foo} = {1, 2, 3}
IO.puts _ # => CompileError
  • _ は何にでもマッチするので、パターンの一部を無視したいときに使う
  • 変数ではないので後から参照することはできない。

辛み

nodebrewをインストールするときの手順

homebrewでインストールするときの手順。

パスを追加する際、brew infoに従っても、うまく動作しなかったのでメモ。

インストール

% homebrew install nodebrew

パスの追加

~/.zshrcにパスを追加。

export NODEBREW_ROOT=/usr/local/var/nodebrew
export PATH=$NODEBREW_ROOT/current/bin:$PATH

rbenvと同じく、$HOME/.nodebrewに集約させたかったけれど、curlコマンドでエラーが発生するなど、少々ハマったので、上記で妥協した(特に問題ある感じでもなかったので)。

nodeパッケージのインストール

% nodebrew ls-remote
% nodebrew install-binary stable
% nodebrew ls
% nodebrew use ${VERSION}
% node -v
% npm -v

ついでにnpmを最新版に。

% npm install -g npm

[GAS]Google Apps Scriptでツイートを投稿する

今回は、Twitter REST APIを用いて、Google Apps Scriptでツイートを投稿するまでの実装を行ってみます。

ツイートを投稿するには、OAuth認証を行う必要があります。OAuth認証については、はてなウェブサービスで利用した、google-apps-oauth1ライブラリを利用すると、簡単に実装できます。

github.com

実装準備

  1. スクリプトエディタを起動し、新規gsファイルを作成します。この際、URLのhttps://script.google.com/macros/d/{PROJECT KEY}/のPROJECT_KEYを控えておきます。
  2. Twitter Developersから、新規アプリケーションを登録します。この際、コールバックURLには、先ほどのPROJECT_KEYを利用し、https://script.google.com/macros/d/{PROJECT KEY}/usercallbackと設定します。
  3. スクリプトエディタに戻り、ライブラリに、MFE2ytR_vQqYfZ9VodecRE0qO0XQ_ydfbを追加します(このライブラリは、apps-script-oauth1ライブラリに則り、Twitterでの認証を簡単に行えるよう作成したものです)。

コードの実装例

以下がコード実装例となります。authorizeとauthCallback関数は必須となります。

実装後、スクリプトエディタからauthorizeを実行します。ログに認証用URLが表示されますので、ブラウザでそちらを実行します。認証が済むと、authCallbackの処理が行われ、タイムラインの取得や、ツイートの投稿が可能になります。

'use strict';

// OAuth1認証用インスタンス
var twitter = TwitterWebService.getInstance(
  '***CONSUMER_KEY***',
  '***CONSUMER_SECRET***'
);

// 認証を行う(必須)
function authorize() {
  twitter.authorize();
}

// 認証をリセット
function reset() {
  twitter.reset();
}

// 認証後のコールバック(必須)
function authCallback(request) {
  return twitter.authCallback(request);
}

// タイムラインを取得
function getUserTimeline() {
  var service  = twitter.getService();
  var response = service.fetch('https://api.twitter.com/1.1/statuses/user_timeline.json');
  Logger.log(JSON.parse(response));
}

// ツイートを投稿
function postUpdateStatus() {
  var service  = twitter.getService();
  var response = service.fetch('https://api.twitter.com/1.1/statuses/update.json', {
    method: 'post',
    payload: { status: '***MESSAGE***' }
  });
  Logger.log(JSON.parse(response));
}

非常に簡単なので、Botなどもサクっと作れそうですね。

最後にライブラリのコードは次のようになりました。

興味があったら利用してみてください。

[GAS]はてなブログAtomPubを扱うライブラリ

前回に引き続き、はてなウェブサービスを扱うライブラリをGoogle Apps Scriptで実装しました。今回は、はてなブログです。

このライブラリを利用すると、Google Apps Scriptから、ブログエントリ一覧の取得、ブログエントリの取得・作成・編集・削除、カテゴリ一覧の取得などが操作できます。

はてなブログAtomPub - Hatena Developer Center

アプリケーションの認証〜OAuthの認可までは前回のエントリを参考にしてください。

利用方法

consumer keyとconsumer secretの取得、およびOAuth認可のライブラリ登録と認可を済ませていることを前提にしています。

最初に、はてなブログの詳細設定から、AtomPub→ルートエンドポイントを確認し、https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atomというURL表記になっているので、それぞれのIDを控えておきます。

スクリプトエディタからライブラリを選択し、My7DzK_6fJTPmyz1FbJKEpMLo7O4tWOsVを登録。

あとは、次の実装例のようなコードを記述するだけです(CONSUMER_KEY, CONSUMER_SECRET等は適宜置き換えてください)。

'use strict';

/**
 * OAuth認証用インスタンス
 * @params {string} consumer_key
 * @params {string} consumer_secret
 * @params {string} scope
**/
var service = HatenaWebService.getInstance(
  '***CONSUMER_KEY***',
  '***CONSUMER_SECRET***',
  'read_public,read_private,write_public,write_private'
);

// 認証を行う
function authorize() {
  service.authorize();
}

// 認証をリセット
function reset() {
  service.reset();
}

// 認証後のコールバック
function authCallback(request) {
  return service.authCallback(request);
}

var blog = getInstance(service, '***HATENA_ID***', '***BLOG_ID***');

// サービス文書を取得
function getSelf() {
  Logger.log(blog.get('self'));
}

// ブログエントリ一覧の取得
function getEntries() {
  Logger.log(blog.get('entries'));
  // blog.get('entries', { page: '***PAGE_ID***' })
}

// カテゴリ一覧の取得
function getCategories() {
  Logger.log(blog.get('categories'));
}

// ブログエントリの取得
function getEntry() {
  /**
   * @params {string}  entryId : required
  **/
  Logger.log(blog.get('entry', { entryId: "***ENTRY_ID***" }));
}

// ブログエントリの取得
function postEntries() {
  /**
   * @params {string}  title      : required
   * @params {string}  content    : required
   * @params {string}  author     : option
   * @params {array}   categories : option
   * @params {date}    updateAt   : option
   * @params {boolean} draft      : option
  **/
  Logger.log(blog.post('entries', {
    title:      'Hello, New Post!',
    content:    '## New Post\n\nMessage on New Post!!',
    author:     'Taro-Yamada',
    categories: ['Category1', 'Category2'],
    updateAt:   new Date(2015,10,10,10,10,0),
    draft:      true
  }));
}

// ブログエントリの更新
function putEntry() {
  /**
   * @params {string}  entryId    : required
   * @params {string}  title      : required
   * @params {string}  content    : required
   * @params {string}  author     : option
   * @params {array}   categories : option
   * @params {date}    updateAt   : option
   * @params {boolean} draft      : option
  **/
  Logger.log(blog.put('entry', {
    entryId:    '***ENTRY_ID***',
    title:      'Hello, Updated Post!',
    content:    '## Updated Post\n\nMessage on Updated Post!!',
    author:     'Taro-Yamada',
    categories: ['Google Apps Script', 'Ruby'],
    updateAt:   new Date(2015,10,10,10,10,0),
    draft:      true
  }));
}

// ブログエントリの削除
function destroyEntry() {
  /**
   * @params {string}  entryId : required
  **/
  Logger.log(blog.destroy('entry', { entryId: '***ENTRY_ID***' }));
}

ブログエントリの本文は、ブログ設定がWISIWIGかMarkdownかで変わる(?)ようです。

ライブラリのコード

ライブラリのコードは次の通りです。前回作成したはてなブックマークとさほど変わりませんが、今回はPOST/PUTする際に、XMLでリクエストを投げる必要があったので、HTMLテンプレートを利用して、XMLを組み立てました。

XMLJSONに変換する方法

おまけです。返り値がXMLだと扱いにくい場合は、次のエントリを参考に、JSONに変換すると幸せかもしれません。

Convert XML to JSON with Apps Script

なお、はてなブログAtomPubについては、OAuth認可をせずとも、Basic認証でいけますが、前回ライブラリを作ったのもあり、OAuthを前提としております。