fugafuga.write

日々のログ



すごいH本 part2

前の記事でも少しふれたが、Haskell における関数についての続き

blog.tokoyax.com

関数を組み合わせる

doubleUs x y  = x * 2 + y * 2

doubleUs x y = doubleMe x + doubleMe y

このように関数を組み合わせて表現してゆく。
これは関数型でなくてもやってる。

分岐がある関数

doubleSmallNumber x = if x > 100
                        then x
                        else x * 2

実行結果

*Main> doubleSmallNumber 100
200
*Main> doubleSmallNumber 1
2
*Main> doubleSmallNumber 101
101
  • Haskell の if はかならず else が必要
    • 値を返さないということを許さない
    • 制御構文ではなく、式である

関数名に ' を使える

doubleSmallNumber' x = (if x > 100 then x else x * 2) + 1

実行結果

*Main> doubleSmallNumber' 100
201
  • ' のつけどころ
    • 正格(遅延ではない)版の関数につけたりする
    • 少し変更したバージョンの関数につけたりする

関数名のルール

  • 先頭文字は小文字で始める
  • 関数が値をとらないとき、それを定義または名前と呼ぶ
    • 変数ではない、定数みたいなものか

今日はここまで。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る

すごいH本入門 ―イントロダクション―

Elixir を触っていると関数型プログラミングに興味が湧いてきたので、すごいH本を読んでいこうと思う。 この本を読むことで関数型プログラミングの基本的な考え方を得たい。

そんでもって、めちゃくちゃエロいらしい。

今日は1日目。

イントロダクション

  • Haskell は純粋関数型プログラミング言語である
    • 何をするか、ではなく何であるかをコンピューターに伝える
  • immutable である
  • 関数は副作用を持たない
    • '参照透明性を持つ' と表現される
    • インプットが同じなら必ずアウトプットも同じとなる
  • 遅延評価
    • 結果が必要になるまで関数を実行しない
  • 静的型付け言語である
  • 型推論を持つ
    • 型を明示しなくてもコンパイラがコードから型を推測する
  • エレガントである
    • 短いコード
    • すごい博士たちによってHaskellは作られている

Haskell の開発環境

必要なものは、

  • エディタ
  • Haskell コンパイラ

のみ。

Haskell のコンパイラは GHC が最も広く使用されている。

と、本には書いてあるがググると stack なるものがあるのでそれを導入する。

Home - The Haskell Tool Stack

  • Installing GHC automatically, in an isolated location.
  • Installing packages needed for your project.
  • Building your project.
  • Testing your project.
  • Benchmarking your project.

とあるようにGHCも入れてくれるし、他にも色々便利な機能があるみたい。

とりあえず、stack をインストールする。

curl -sSL https://get.haskellstack.org/ | sh

プロジェクトを作成する

> stack new my-project
Downloading template "new-template" to create project "my-project" in my-project/ ...

The following parameters were needed by the template but not provided: author-email, author-name, category, copyright, github-username
You can provide them in /Users/takuya/.stack/config.yaml, like this:
templates:
  params:
    author-email: value
    author-name: value
    category: value
    copyright: value
    github-username: value
Or you can pass each one as parameters like this:
stack new my-project new-template -p "author-email:value" -p "author-name:value" -p "category:value" -p "copyright:value" -p "github-username:value"

Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- my-project/my-project.cabal

Selecting the best among 11 snapshots...

Downloaded lts-9.14 build plan.
Selected mirror https://s3.amazonaws.com/hackage.fpcomplete.com/
Downloading root
Selected mirror https://s3.amazonaws.com/hackage.fpcomplete.com/
Downloading timestamp
Downloading snapshot
Downloading mirrors
Cannot update index (no local copy)
Downloading index
Updated package list downloaded
Populated index cache.
* Matches lts-9.14

Selected resolver: lts-9.14
Initialising configuration using resolver: lts-9.14
Total number of user packages considered: 1
Writing configuration to file: my-project/stack.yaml
All done.

コンパイラをダウンロードする

> stack setup
Preparing to install GHC to an isolated location.
This will not interfere with any system-level installation.
Downloaded ghc-8.0.2.
Installed GHC.
stack will use a sandboxed GHC it installed
For more information on paths, see 'stack path' and 'stack exec env'
To use this GHC and packages outside of a project, consider using:
stack ghc, stack ghci, stack runghc, or stack exec

プロジェクトをビルドする

> stack build
...
...
Registering my-project-0.1.0.0...

プログラムを実行する

> stack exec my-project-exe
someFunc

REPL を起動

> stack ghci
...
...
*Main Lib>
*Main Lib> 1 + 1
2

スクリプトを書いてみる

src配下に baby.hs を作成し、以下の内容を入力

doubleMe x = x + x

関数名 {引数} = {関数本体} となっている模様。

REPL 起動し、baby.hs をロードする

> stack ghci
...
...
*Main Lib> :l baby.hs
[1 of 1] Compiling Main             ( baby.hs, interpreted )
Ok, modules loaded: Main.

実行してみる

*Main> doubleMe 8
16

他の関数を baby.hs に加えてみる

doubleUs x y = x * 2 + y * 2

2個引数を取り、それらを2倍して足した値を返す。

REPL で再度 baby.hs をロードして実行してみる

*Main> doubleUs 2 4
12

今日はとりあえずここまで。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る

'no'コマンドについての本当にちょっとした話 ―速度改善編―

blog.tokoyax.com

この記事の続き

速度の問題

> yes | pv -r > /dev/null
[31.0MiB/s]
> ./no | pv -r > /dev/null
[85.6KiB/s]

自作のコマンドnoはこのようにyesコマンドに比べて遅い。

英語の練習も兼ねて dev.to に同じ記事を投稿したところ、以下のようなコメントをいただけた。

you've better to use buffer for writing. Actually, yes command does not call write(2) for each lines.

なるほど、I/Oの回数が多いことが原因と思われるのでバッファを使うほうがよいとのこと。

改善してみる

IO List を使う

Elixir and IO Lists, Part 1: Building Output Efficiently

この記事によると、文字列を結合しながらIO.putsに渡す場合、
結合された文字列を1つだけ渡すのではなく、結合前の文字列のパーツをリストに格納するほうがパフォーマンスが良いとのこと。

リスト内に同じ文字が格納されている場合、同じ文字は同じアドレスを参照するため効率がよいらしい。

バッファリングするようにしたものがこちら。

no.exs

defmodule No do
  @moduledoc """
  Documentation for No.
  """

  def main(args) do
    args
    |> parse_args()
    |> run()
    |> wait()
  end

  def say(args) do
    {_, _, _, debug} = args
    message = buffer(args)
    case debug do
      true -> IO.inspect({:debug, message, self()})
      _    -> IO.puts(message)
    end
    say(args)
  end

  defp buffer(args) do
    {argv, process_num, _, debug} = args

    message = case debug do
      true -> "#{expletive(argv)} from process no.#{process_num}"
      _    -> expletive(argv)
    end

    buffer(args, message, [])
  end

  defp buffer(args, message, list) do
    # faster than append last
    # https://hexdocs.pm/elixir/List.html
    list = [message | list]
    cond do
      IO.iodata_length(list) >= 4096 -> list
      true -> buffer(args, message, list)
    end
  end

  defp expletive(_ = ''), do: "n\n"
  defp expletive(value), do: value

  defp parse_args(args) do
    {opts, argv, _} = OptionParser.parse(
      args,
      swithces: options_list(),
      aliases: aliases_list()
    )
    {argv, opts}
  end

  defp options_list() do
    [
      processes: :integer,
      debug: :boolean,
    ]
  end

  defp aliases_list() do
    [
      p: :processes,
      d: :debug,
    ]
  end

  defp run(args) do
    {argv, opts} = args

    num = case opts[:processes] do
      nil -> 1
      _   -> String.to_integer(opts[:processes])
    end

    Enum.each((1..num), fn(n) ->
      spawn_link(No, :say, [{argv, n, self(), opts[:debug]}]) end)
  end

  defp wait(args) do
    wait(args)
  end
end

やったこと

  • List は要素を先頭に追加するほうが速いのでそのように
  • 親プロセス側でメッセージを待つのをやめて各子プロセスで出力するように

測定する

before

> ./no | pv -r > /dev/null
[85.6KiB/s]

after

> ./no | pv -r > /dev/null
[ 802KiB/s]

SUCCESS 😇 😇 😇 😇 😇 😇 😇 😇 😇 😇

並列実行してみる

> ./no -p 4 | pv -r > /dev/null
[1.33MiB/s]

単位が上がりましたね....これで無限にプロセスを増やせば....

> ./no -p 10000 | pv -r > /dev/null
[5.00MiB/s]
[3.16MiB/s]
[1.84MiB/s]
[ 458KiB/s]
[ 466KiB/s]
[ 569KiB/s]
[ 724KiB/s]
[ 805KiB/s]
[1.27MiB/s]
[6.17MiB/s]
[15.3KiB/s]
[1.13MiB/s]

ブレがすごい

まとめ

  • IO List など速度改善の知見が得られた
  • 自分で作ったものが速くなるのは単純に楽しい
  • まだyesには全然敵わないので速度改善の余地はありそう
  • ブレがすごい

ブレについては気が向いたら調べる

Github: GitHub - tokoyax/no: Say No!!!!

プログラミングElixir

プログラミングElixir

(参考)

Hex に自作ライブラリを登録する

この前とても役に立つコマンド(個人差有)

github.com

を作った。

Hex に登録して escript.install すると作ったコマンドをすぐ共有できてよさそうなのでやる。

環境

Elixir 1.5.2

Hex にユーザー登録する

mixコマンド は elixir に同梱されている。

> mix hex.user register
By registering an account on Hex.pm you accept all our policies and terms of service found at https://hex.pm/policies

Username: tokoyax
Email: tokoyax.dev@gmail.com
Password:
Password (confirm):
Registering...
Generating API key...
Encrypting API key with user password...
You are required to confirm your email to access your account, a confirmation email has been sent to tokoyax.dev@gmail.com

API keyは~/.hex/hex.configに保存される。
メールが届くので confirm するとアカウントが作成される。

mix.exsの編集

ライブラリのページに記載されるメタデータを編集する

defmodule No.Mixfile do
  use Mix.Project

  # 追加
  @description"""
  This is `no` command that outputs `n` implemented in Elixir.
  """

  def project do
    [
      app: :no,
      version: "0.1.0",
      elixir: "~> 1.5",
      name: "No",
      description: @description, # <= 追加
      package: package(), # <= 追加
      escript: escript_config(),
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

...

  # 追加
  def package do
    [
      maintainers: ["tokoyax"],
      licenses: ["MIT"],
      links: %{ "Github": "https://github.com/tokoyax/no" }
    ]
  end

Hex へ登録

Passphrase を聞かれるのでアカウント作成時のパスワードを入力する。

> mix hex.publish
Passphrase:
Publishing no 0.1.0
  Files:
    lib/no.ex
    mix.exs
    README.md
  App: no
  Name: no
  Description: This is `no` command that outputs `n` implemented in Elixir.
  Version: 0.1.0
  Build tools: mix
  Licenses: MIT
  Maintainers: tokoyax
  Links:
    Github: https://github.com/tokoyax/no
  Elixir: ~> 1.5
Before publishing, please read the Code of Conduct: https://hex.pm/policies/codeofconduct
Proceed? [Yn] Y
Building docs...
Compiling 1 file (.ex)
Generated no app
The "docs" task is unavailable. Please add {:ex_doc, ">= 0.0.0", only: :dev} to your dependencies in your mix.exs. If ex_doc was already added, make sure you run the task in the same environment it is configured to
** (Mix) The task "docs" could not be found. Did you mean "do"?

なんかエラーが。

{:ex_doc, ">= 0.0.0", only: :dev}が無いので追加しないといけないっぽい。

mix.exsを編集する

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:ex_doc, ">= 0.0.0", only: :dev}, # <= 追加
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end

もう一度実行

> mix hex.publish
...
Proceed? [Yn] Y
Building docs...
Unchecked dependencies for environment dev:
* ex_doc (Hex package)
  the dependency is not available, run "mix deps.get"
** (Mix) Can't continue due to errors on dependencies

またエラーが。

依存関係のパッケージが無いのでmix deps.getしないといけませんよーとのこと。 次のアクションを促してくれるのは親切でとてもよい。

> mix deps.get
Running dependency resolution...
Dependency resolution completed:
  earmark 1.2.3
  ex_doc 0.18.1
* Getting ex_doc (Hex package)
  Checking package (https://repo.hex.pm/tarballs/ex_doc-0.18.1.tar)
  Fetched package
* Getting earmark (Hex package)
  Checking package (https://repo.hex.pm/tarballs/earmark-1.2.3.tar)
  Fetched package

Ok, 再度実行

> mix hex.publish
Passphrase:
Publishing no 0.1.0
  Files:
    lib/no.ex
    mix.exs
    README.md
  App: no
  Name: no
  Description: This is `no` command that outputs `n` implemented in Elixir.
  Version: 0.1.0
  Build tools: mix
  Licenses: MIT
  Maintainers: tokoyax
  Links:
    Github: https://github.com/tokoyax/no
  Elixir: ~> 1.5
Before publishing, please read the Code of Conduct: https://hex.pm/policies/codeofconduct
Proceed? [Yn] Y
Building docs...
==> earmark
Compiling 3 files (.erl)
Compiling 24 files (.ex)
Generated earmark app
==> ex_doc
Compiling 15 files (.ex)
Generated ex_doc app
==> no
Compiling 1 file (.ex)
Generated no app
Docs successfully generated.
View them at "doc/index.html".
Publishing package...
[#########################] 100%
Package published to https://hex.pm/packages/no/0.1.0 (1b7e3d36abb1e88b60b9fd3872cc251dc32ef3aa19a13ead26bd040644af42d3)
Publishing docs...
[#########################] 100%
Docs published to https://hexdocs.pm/no/0.1.0[f:id:tokoyax:20171118170418j:plain]

OK

hex.pm にアクセスしてみる。

f:id:tokoyax:20171118170418j:plain

https://hex.pm/packages/no

SUCCESS 😇 😇 😇 😇 😇

escript.install してみる

escript.install – Mix v1.6.0-dev

If an argument is provided, it should be a local path or a URL to a prebuilt escript, a Git repository, a GitHub repository, or a Hex package.

とあるように、github など Hex 以外からも install できるらしい。便利。

> mix escript.install hex no
Running dependency resolution...
Dependency resolution completed:
  no 0.1.0
* Getting no (Hex package)
  Checking package (https://repo.hex.pm/tarballs/no-0.1.0.tar)
  Using locally cached package
Compiling 1 file (.ex)
Generated no app
Generated escript no with MIX_ENV=prod
Found existing entry: /Users/takuya/.mix/escripts/no
Are you sure you want to replace it with "no"? [Yn] Y
* creating /Users/takuya/.mix/escripts/no

warning: you must append "/Users/takuya/.mix/escripts" to your PATH if you want to invoke escripts by name

実行可能ファイルは、~/.mix/escripts に install される。 warning にもあるように PATH 通さないと使えないので気をつけてね、とのこと。

まとめ

  • elixir さえインストールされていれば結構お手軽にコマンド配布できたので最高という感想
  • ドキュメントの整備はまた気が向いたらやる

(参考)
* ElixirのライブラリをHexに公開する | Developers.IO

プログラミングElixir

プログラミングElixir