LoginSignup
2
0

More than 3 years have passed since last update.

すごいHaskellたのしく学ぼう! を学びなおしてみる(第5章)[with Elixir]

Last updated at Posted at 2020-04-26

はじめに

  • 前回
  • Elixirを使いはじめてだいたい1年くらいがたちました
  • すごいHaskellたのしく学ぼう!という本を2015年に買って、一通り読んだあとずっと本棚にしまわれていたままでした
  • 久しぶりに引っ張り出して読んでみると、こんなに愉快な内容だったけ!? という感想を持ちました
  • Elixirで関数プログラミングにだいぶ慣れたので、ユーモアの部分を楽しむ余裕ができたのだとおもいます
  • 少しずつ読み進めながら、興味が向いたところだけElixirで書き換えてみたりして理解を深めていきたいとおもいます
  • 今回は第5章 高階関数

applyTwice

Haskell

Prelude> applyTwice f x = f (f x)
Prelude> applyTwice (+3) 10
16

Elixir

awesome.exs
defmodule Awesome do
  def apply_twice(fun, x), do: fun.(x) |> fun.()
end

iex> Awesome.apply_twice(&(&1 + 3), 10)                                     
16

zipWith

Haskell

baby.hs
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys

Prelude> :l baby.hs 
*Main> zipWith' (+) [4,2,5,6] [2,6,2,3]
[6,8,7,9]
*Main> zipWith' (++) ["foo ", "bar ", "baz "] ["fighters", "hoppers", "aldrin"]
["foo fighters","bar hoppers","baz aldrin"]

Elixir

awesome.exs
defmodule Awesome do
  def zip_with(_, [], _), do: []
  def zip_with(_, _, []), do: []
  def zip_with(fun, [x | xs], [y | ys]), do: [fun.(x, y) | zip_with(fun, xs, ys)]
end

iex> Awesome.zip_with(&(&1 + &2), [4,2,5,6], [2,6,2,3])                                        
[6, 8, 7, 9]
iex> Awesome.zip_with(&(&1 <> &2), ["foo ", "bar ", "baz "], ["fighters", "hoppers", "aldrin"])
["foo fighters", "bar hoppers", "baz aldrin"]

map

Haskell

Prelude> map (+3) [1,5,3,1,6]
[4,8,6,4,9]

Elixir

iex(47)> [1,5,3,1,6] |> Enum.map(&(&1 + 3))
[4, 8, 6, 4, 9]

Enum.map/2

filter

Haskell

Prelude> filter (>3) [1,5,3,2,1,6,4,3,2,1]
[5,6,4]

Elixir

iex> Enum.filter([1,5,3,2,1,6,4,3,2,1], &(&1 > 3))
[5, 6, 4]

Enum.filter/2

10万以下の数のうち3829で割り切れる最大の数を探してみる

Haskell

baby.hs
largestDivisible = head (filter p [100000, 99999..])
    where p x = x `mod` 3829 == 0

Prelude> :l baby.hs 
*Main> largestDivisible
99554

Elixir

iex> (
...> Stream.iterate(100000, &(&1 - 1))
...> |> Stream.filter(&(rem(&1, 3829) == 0))
...> |> Enum.take(1)
...> |> hd
...> )
99554

1から100までの数のうち、長さ15以上のコラッツ列の開始数になるものはいくつあるか?

Haskell

baby.hs
chain 1 = [1]
chain n
    | even n = n : chain (n `div` 2)
    | odd n  = n : chain (n * 3 + 1)

numLongChians = length (filter isLong (map chain [1..100]))
    where isLong xs = length xs > 15

Prelude> :l baby.hs 
*Main> numLongChians 
66

Elxiir

awesome.exs
defmodule Awesome do
  def chain(1), do: [1]
  def chain(n) when rem(n, 2) == 0, do: [n | div(n, 2) |> chain()]
  def chain(n), do: [n | (n * 3 + 1) |> chain()]

  def num_long_chains do
    # Pipe operator is great!!!
    1..100
    |> Enum.map(&chain/1)
    |> Enum.map(&Enum.count/1)
    |> Enum.filter(&(&1 > 15))
    |> Enum.count()
  end
end

Pipe operator

scanl

Haskell

Prelude> scanl (+) 0 [3,5,2,1]
[0,3,8,10,11]

Elixir

iex(68)> Enum.scan([3,5,2,1], 0, &(&1 + &2))
[3, 8, 10, 11]

Enum.scan/3

奇数の平方根で10000より小さいものの総和を求める

Haskell

Prelude> oddSquareSum = sum . takeWhile (<10000) . filter odd $ map (^2) [1..]
Prelude> oddSquareSum
166650
  • 関数合成というものを利用した書き方だそうです

Elixir

iex> (
...> 1..(:math.sqrt(10000) |> round)
...>     |> Enum.filter(&(rem(&1, 2) == 1))
...>     |> Enum.map(&(&1 * &1))
...>     |> Enum.filter(&(&1 < 10000))
...>     |> Enum.sum()
...> )
166650

Haskellのほうにでてきた【カリー化】とか【部分適用】、【関数適用】、【関数合成】あたりはあんまり理解が進んでいませんが、Elixirでは|>を使うと似たようなことをうまく書ける印象を持ちました

Elixirで&記法を使って引数に指定する関数を書くと、HaskellとElixirで書き方がよく似た形になるようにおもいました

Wrapping Up

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0