Help us understand the problem. What is going on with this article?

【Elixir超初級】代入、不変性、パターンマッチについて

More than 1 year has passed since last update.

はじめに

株式会社Parkに入社して1年が経とうかとしております。
今年の自分の目標はこのElixirなる言語についての学習に決めました。

なぜElixirにしたのかというと、「これから来る言語」とか「Rubyに似ている」とか「並行プログラミング」とかイケイケな感じがしたからという割と安直な理由です。

それと関数型言語にも触れてみたいというのもあります。
(オブジェクト指向もまだ完全に理解していないのにいいのだろうか…)

Elixirに「代入」という言葉はない

今まで触った言語はRubyやC#なのですがElixirは今まで触ってきた言語と根本的に違うところがあります。

それは「代入」が存在しないということです。
=(イコール)が変数への代入であるという概念を忘れる必要があります。

a = 1
a + 2
# 3

普通に考えると変数aに1を代入しているように見えます。しかし、前にも言ったように「代入」は存在しないのです。

RubyやC#では以下のコードはエラーになります。

Ruby
a = 1
1 = a #syntax error, unexpected '=', expecting end-of-input

C#だと

The left-hand side of an assignment must be a variable, a property or an indexer

要するに「代入の左辺は、変数、プロパティまたはインデクサーでなければなりません。」と怒られます。

しかし、Elixirでは通ります。

Elixir
a = 1
1 = a 
2 = a #(MatchError) no match of right hand side value: 1

一度プログラミング脳を捨て中高生の時の数学を思い出してみれば別に不思議なことではないです。
1行目で右辺の値は左辺と等しいと表明しているのです。
2行目も右辺が左辺と等しいかを見ているためエラーにはならないのです。

3行目ではエラーになります。単純にaは2じゃないよと怒られます。

Elixirの不変性について

このようなコードがあった場合を考えてみました

C#
public class Hello{
    public static void Main(){
        int one = 1;
        Hoge(ref one);
        System.Console.WriteLine(one);
    }
    static void Hoge(ref int refArgument)
    {
        refArgument = refArgument + 1;
    }
}
//2

ここで問題なのは
基本的にoneは1であってhogeメソッドに渡した途端2になってしまうのはどうかという話です。

Elixir的には1と表明したものが2になるとか嘘つきじゃんということです

この例は引数にrefがあり参照渡しでoneの値が変わってしまうことはある程度推測できます。

ではこれはどうだろうか

Ruby
a = [1,2,3]
a.delete(2)

p a
#a = [1,3]

配列は参照渡しがほとんどです。

もし、複数スレッドを起動していてこの変数arrayに例えばTwiceメソッド(二倍にする)とplusoneメソッドがアクセスした場合arrayの状態によっては

2倍したあとに1を足す結果と
1を足した後に2倍したときでは結果が変わってしまいます。

Elixirは値をコピーする

Elixirはこの問題を回避しています。(並行性が売りの言語なので当然といえば当然)

配列の参照渡しの例で示したRubyのコードと比べてみましょう。

Elixir
a = [1,2,3]
b = List.delete(a, 1)


IO.inspect a
#[1,2,3]
IO.inspect b
#[1,3]

変数aのdeleteしたはずの2が復活してますね。

Elixirはこのようにデータをコピーするだけなので元のリストが変わることがありません。

パターンマッチ

Elixirは代入がなく表明で、一度表明したものはもう一度定義されるまで変わらないということがここまででわかったと思います。

それでは次はElixirのパターンマッチというやつをやってみましょう。

Elixir
list1 = [1,2,3]
[a,b,c] = list1
# a = 1
# b = 2
# C = 3

list2 = [1,2,[3,4,5]]
[a,b,c] = list2
# a = 1
# b = 2
# c = [3, 4, 5]

list3 = [:hoge,1,"fuga"]
[a,b,c] = list3
# a = :hoge
# b = 1
# c = "fuga"

左辺の三つの変数に対して、右のリストをそれぞれ結びつけています。
このプロセスをパターンマッチといいます。

パターンマッチを利用してこのようなこともできます。

Elixir
list = [:ok,2,3]
[:ng,b,c] = list
# (MatchError) no match of right hand side value: [:ok, 2, 3]
# b (CompileError) iex:1: undefined function b/0
# c (CompileError) iex:1: undefined function b/0

[:ok,b,c] = list
# b = 2
# C = 3
#[b,:ok,c] = listこの場合はNG

最初の要素の :okにマッチした場合のみ変数に定義することもできます。

アンダースコアを使えば値を無視することもできます。

Elixir
list = [:ok,2,3]
[_,b,c] = list
# b = 2
# c = 3

[a,_,c] = list
# a = :ok
# c = 3

結構便利な機能だと思うのですが気を付けなければいけないことがあります。変数の束縛はマッチの中で一度だけということです。

正直テキストを読むだけでは意味がわからずなかなか理解できなかったのですが、実際に動かしてみるとわかりやすいです。

Elixir
a = 1
[a,a] = [1,2] 
#(MatchError) no match of right hand side value: [1, 2]

一見すると最初の左辺の1番目の変数a(左)と1はマッチし次の変数a(右)が2によって再定義されa=2になるのでは?と思うのですがこれはエラーになります。

この問題を回避する方法もあります。キャレット(^)を付けることによって変数を値として扱うことができます。

Elixir
a = 1
[^a,a] = [1,2]
# a = 2

^を付けることで最初の変数a(左)を1として評価し次の変数a(右)を改めてaに定義しています。

さいごに

これらの要素はelixirの基本中の基本ですがなかなか理解するのに苦労しました。

Rubyに似ている言語だという謳い文句で勉強しだしたのはいいものの根本的に違うことが多すぎて完全に騙されました。

まだまだ再帰遅延評価、そして一番の山場とも思える並行プログラミングの学習があると思うと先が思いやられます…

参考書籍

プログラミングElixir

west_ryo
プログラミング歴1年です。 株式会社パーク 入社1年目
pa-rk
Webアプリ、スマホアプリの開発を手掛ける技術者集団です。
https://www.pa-rk.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away