やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

ビジュアルプログラミングを卒業した後に覚えたいプログラミング言語

プログラミングを学ぶ背景

2020年より小学校ではプログラミングが必修化されて、必然的に小学校を卒業するまでに生徒たちはプログラミングを学ぶことになります。

www.mext.go.jp

小学校でのプログラミングが必修化されている目的として「プログラミング的思考力」を鍛えたいという点があります。「いや、プログラミング的思考力って何やねん」というツッコミはおいておいて、実際に小学校で取り入れている、もしくは小学生が自身で学ぶ場合のプログラミング教育の実例をいくらか見てみました。
事例として多いのは「ビジュアルプログラミングを用いて総合的な時間や既存教科に合わせた取り組み」といったケースです。

miraino-manabi.jp

miraino-manabi.jp

既存のカリキュラムに対してより理解を深めてもらうためにプログラミングを用いて授業を進めているそうです。例として上がっているのは正三角形の描画をビジュアルプログラミングを用いて行うものでした。
良い時代になりましたね。自分が小学生の時は分度器と定規を使って、正三角形を作業的に作図した記憶がほのかに残っています。

また2021年には中学校でも、既存のカリキュラムを見直し「双方向ネットワーク」という要素を重要視したプログラミング教育の授業を行なっていきたいという旨を政府が公開しております。すでに取り組みを始めている中学校も多いようです。

ビジュアルプログラミングを終了した後の流れがない

最近はプログラミング教育に関する資料や書籍、情報によく目を通すようにしているのですが、気になった点があります。すでに課題として多くの有識者が意見を述べていますが、改めて。

それは「ビジュアルプログラミングで学んだ後に実際のプログラミング言語を学ぶための導線がない」という点です。小学校でプログラミングが必修化されたことで多くの生徒がビジュアルプログラミングでの体験をすることになります。しかしながら、中学校に進学して、より難易度が高いであろうプログラミング言語の記述に触れられる機会は少ないようです。

当時の私の記憶では、技術の時間にOfficeを使った簡単なパソコン操作と、タッチタイピングがあった程度でした。またICT支援員と呼ばれるプログラミング教育を支援する人材が不足していることも問題の1つです。

現場教員のインタビュー記事をいくらか拝見しましたが、上記のような問題を解決するために壁となっている要因として多いのは以下の通り。

  • プログラミング言語を用いた教育が行える環境が整っていない -> eg: ブラウザが使えない
  • 指導できる教員がいない -> ICT支援員の不足
  • 何をしていいのか分からない -> 正三角形をプログラミング言語を使って書くのは面白いのか
  • プログラミング言語の文法の理解 -> 変数だの分岐処理だの繰り返し処理だの...

プログラミング言語を記述するということ

ビジュアルプログラミングと実際にプログラミング言語を用いてプログラムを書くという作業は本質的な部分、作業を細分化して列挙していくという部分では共通する部分があるでしょうが、その性質は異なるといっていいでしょう。

先ほども壁となっている要因としてあげたように、プログラミング言語を使いこなすためには言語特有の文法を理解して、変数分岐処理繰り返し処理といった基本的なプログラミング言語の動作に関する知識と文法への理解が必要になってくるでしょう。

また、記述をすすめる中でエラーとなり思ったように動かないことも当然あるでしょう。その時にはなぜ動かないのかをスタックトレースと呼ばれるエラーログから追う必要があります。エラーログを追う作業は現役のエンジニアであっても、初心の頃に1年程の時間をかけて感性を磨いていくものです。ましてやエラーログはほぼ全て英語で記述されており、プログラミング言語特有の概念であったり、単語が頻出する場合もあり、英語に対しての理解があっても解読が難しいこともあります。

では、この作業を小学校でビジュアルプログラミングを学んだ生徒たちが中学校に進学した後に行えないかと言うと、決してそうではないと思います。実際に、小学生や中学生であってもプログラミング言語を使いこなし自分の好きなものを作っている方は多く存在していますし、学生の好奇心にはいつも驚かされます。

しかしながら、決して難易度が低いとは言えません。最初に選択するプログラミング言語が非常に重要になります。
ご存知の方も多いでしょうがプログラミング言語には様々なものがあり、それぞれ難易度も性質も大きく異なります。今回は、ビジュアルプログラミング言語を学び終わり、中学校に進学した時点などでのオススメの初めて学ぶプログラミング言語を紹介したいと思います。

私は大学3回生時にプログラミングを独学で学び、挫折と学習を繰り返し、いくらかの言語を経験してきました。また、中学生の時にC言語と呼ばれるプログラミング言語に挫折したこともあり、何が躓くポイントになり得るかを理解していますので、参考になれば幸いです。

おすすめのプログラミング言語

Pythonについて

今はプログラミング言語と聞いてPythonの名前が上がらないことは考えられないでしょう。10年程前までは日本でPythonは習得すべきプログラミング源として見向きもされていませんでした。しかしながら、近年、人工知能(機械学習)に対して世間の注目が多く集まっており、実際に多くの人工知能のモデルはPythonで記述されています。なぜPythonが使われているかというと、Pythonから使用可能な数値計算のライブラリ(利用可能な便利ツール)が非常に豊富であり、GoogleFacebookなどが実装言語としてPythonを採用しているため非常に大きなコミュニティが形成されています。

f:id:takamizawa46:20200328133010p:plain

プログラミング言語の選択として大きなコミュニティが存在していることは非常に重要です。コミュニティが大きければ大きいほど、多くのライブラリが開発され、機能が日々アップデートされていきます。また、入門者向けのコンテンツが非常に充実しやすいという点があります。

その点ではPyhtonは初心者が様々なレベルで学び始めるための整備が非常に進んでいます。日本語の情報も多く、思ったように動作せずに困っている時に役立つ情報が見つかりやすいという点もあります。

Pythonの文法について

Pythonはインデントという空白を使用して記述するため、多言語の{を使用して記述する形式と比べて可読性が良いと言われています。実際にサンプルのコードを用意したいのでご覧になってみてください。{を使用するGolangPythonで同じ処理を記述しました。

Golang

package main
import "fmt"
func main(){
    // Your code here!
    
    for n := 0; n < 5; n++ {
      fmt.Println("-->", n+1, "回目の実行を開始します")
      for m := 0; m < 3; m++ {
        fmt.Println("ただいま", n+1, "回目の",  m+1, "番目の実行です")
      }
      fmt.Println("------------")
    }
}

Python

# 5回の処理を繰り返す -> 合計15回
for n in range(0, 5):
  print(f"--> {n+1}回目の実行を開始します")
  # 10回の中で3回ずつ繰り返す
  for m in range(0, 3):
    print(f"ただいま{m+1}回目の{n+1}番目の実行です")
  
  # 見やすくするために出力
  print("-------------")

どちらも以下のような結果を出力します。

--> 1 回目の実行を開始します
ただいま 1 回目の 1 番目の実行です
ただいま 1 回目の 2 番目の実行です
ただいま 1 回目の 3 番目の実行です
------------
--> 2 回目の実行を開始します
ただいま 2 回目の 1 番目の実行です
ただいま 2 回目の 2 番目の実行です
ただいま 2 回目の 3 番目の実行です
:
:
--> 5 回目の実行を開始します
ただいま 5 回目の 1 番目の実行です
ただいま 5 回目の 2 番目の実行です
ただいま 5 回目の 3 番目の実行です
------------

Pythonでは正しいインデントがされていないとエラーになります。そのため、誰か書いても似たような構造になるため、方針が大きく逸れしまうことがなく安心と言えます。

先ほどのコードを不適切なインデントで記述した場合にエラーが出る

# 5回の処理を繰り返す -> 合計15回
for n in range(0, 5):
print(f"--> {n+1}回目の実行を開始します") # <- わざとインデントを削除
  # 10回の中で3回ずつ繰り返す
  for m in range(0, 3):
    print(f"ただいま{m+1}回目の{n+1}番目の実行です")
  
  # 見やすくするために出力
  print("-------------")

エラーログ

  File "Main.py", line 3
    print(f"--> {n+1}回目の実行を開始します")
        ^
IndentationError: expected an indented block

様々なライブラリが公開されている

先ほども記述したようにPythonには大きなコミュニティがあり、様々なライブラリが毎日のように実装公開されています。ゲームを作るためのライブラリから、デスクトップアプリを作るたいめのライブラリ。はたまたドローン制御のライブラリに人工知能(機械学習)を作るためのライブラリと非常に幅広いです。

大げさな言い方ではりますが、Pythonを使えば記述不可能なものは少ないでしょう。また、公開されているライブラリを使うことでシンプルに早くストレスなく記述することが出来ます。

Python 作りたいもの」の組み合わせでGoogleで検索をしてみれば、きっと探しているライブラリが見つかります。

ライブラリの使い方が難しい場合であっても、使い方を解説するようなブログ記事、書籍が広く公開されているのは、やはりPythonのコミュニティの大きさ故でしょう。

Pythonの始め方

では実際にどのようにビジュアルプログラミングを卒業した後にPythonを学び始めていけばいいのかを簡単にご紹介します。前提として、ビジュアルプログラミングに取り組んで以下3つのことが理解出来ているとします。

  • 変数の定義
  • 分岐処理(もし〇〇ならば...)
  • 繰り返し処理(〇〇を何度繰り返す)

プログラムは上記の3つの組み合わせによって大抵の処理を記述することが出来ます。今は覚える必要はありませんが、データ構造やアルゴリズムなど、よりパワフルな知識を身につけることで、より効率的で美しい処理を記述出来るようになりますが、それはまだ先の話です。

まずはPythonで上記3つの記述方法は最低限覚えましょう。Progateを使っても良いですし、個人ブログの記事を使っても良いですし、書籍を購入するのも良い選択肢でしょう。

ProgateであればPythonの2章まで進めれば十分です。
prog-8.com

練習問題

ここまで来たら一度、自分の力でコードを書いてみましょう。すでに様々な処理を記述出来るようになっていることを知り自信を持ちましょう。例題を3問用意したので、好きなものを試してみてください。もちろん、全てに取り組んで頂いても構いません。回答例は「おまけ」に貼っておきます。

Q1

1から100の数字で50より大きな数字であれば「big!」、小さな数字であれば「small!」と出力してください。

ヒント:

  • 出力に使用する構文はprint("hello!")です。
  • 繰り返し処理と分岐処理を上手く組み合わせてみましょう。

Q2

1から10までの数字の合計値を求めて、合計値が10より大きければ、合計値を10倍した数値を出力してください。

ヒント:

  • 合計を求めるためには合計値を求めるための変数を用意します(例: total = 0)
  • 先ほどの変数に値を足していく処理を記述しましょう。(例: total = total + 1)
  • 足し算には+を掛け算には*を使います

Q3: ちょっと難しい

プログラムの有名な入門問題fizzbuzzに挑戦してみましょう。1から100の数字で15で割り切れる時にfizzbuzzと出力し、3で割り切れる時にはfizzと出力し、5で割り切れる時にはbuzzと出力してください。

ヒント:

  • Pythonで割り算の余りを求める時には%を使います(例: 10 % 2)
  • 15は5と3の公倍数であることに注意しましょう

次のステップ

練習問題はどうでしたか。どれも繰り返し処理分岐処理を組み合わせて、基本的な数値データに対して何かしらの処理をするというものです。プログラムの最も基本的な部分を抑えた問題になるように作成したつもりです。

この時点でPythonを最低限、記述するための全ての部品が揃いました。この先の進め方は大きく2種類に分かれます。

1つはこのままPythonの文法の理解を進める学習をすることです。変数の定義繰り返し処理分岐処理と大きく3つのテーマを扱いましたが、まだ触れていない面白いものがたくさんあります。この先に学んでいくものとしては...

  • 関数定義
  • データ構造(配列やタプル、ディクショナリー)
  • クラス定義と継承
  • ライブラリのimport

このようなものがあります。全てを今すぐ理解する必要がありません。ただ、学んでおくことで書籍のサンプルコードなどをスラスラと読み進めることが出来るでしょう。

もう1つは作りたいもののを作るという学習方法です。基本的な部分の学習は完了しているので、すでに挑戦することが出来るでしょう。先ほども記述しましたが、Python 作りたいものの名前」Google検索をすれば、よほどマニアックなことでなければ、何かしらのブログ記事、書籍がヒットするでしょう。

その中でおそらく、関数定義データ構造に対して躓く部分があるかと思います。その度に、知らないことを調べて理解することで、必要な知識だけを頭に入れつつ、作りたいものを作ることが出来るため、モチベーションをキープすることが出来ます。

なので私は圧倒的に後者の学習法「作りたいものを作る」方法をオススメしています。調べてみたけど作り方がよく分からなかったという場合は私のTwitterやココナラ(より深く回答します)などに連絡頂ければば相談可能です。

twitter.com

coconala.com

まとめ

ビジュアルプログラミングを卒業した後に学ぶプログラミング言語として私はPythonをオススメしています。理由は名前が広く知れ渡り、大きなコミュニティがあり、数多くのライブラリが公開されているためです。また可読性も良いです。

Pythonの始め方としては、まず基本的な変数の定義繰り返し処理,、分岐処理の書き方を理解すること。そして、実際に処理を記述してみて、「もうPythonが書けるんだ!」ということを体験してみてください。

そのあとの学習方法は自由です。引き続き文法の学習をするのも良し、作りたいものを作るも良しです。

おまけ1 Pythonのオススメの入門書籍について

自分が読んだことがあるものだけを紹介します。

Pythonの文法を優しく理解するために非常に良い一冊です。

より優しい内容が良いという方はこちらがオススメです。

おまけ2

Q1の回答サンプル

Q1: 1 ~ 100の数字で50より大きな数字であれば「big!」、小さな数字であれば「small!」と出力してください。

解答例

# 1 ~ 100の数値を対象の繰り返し処理をする
for num in range(1, 101):
  # 50より大きいかを判断
  if num > 50:
  
    # 大きい時
    print("big!")
  else:
    # 小さい時
    print("small!")

Q2の回答サンプル

Q2: 1から10までの数字の合計値を求めて、合計値が10より大きければ、合計値を10倍した数値を出力してください。

解答例

# 合計値を保持するための変数
total = 0

# 1 ~ 10までの数値で繰り返し処理
for i in range(1, 11):
  # 合計値を更新
  total = total + i
  # もしくは total += 1
  

# 合計値が10以上かどうかを判断
if total > 10:
  # 10以上なら10倍して出力
  print(total * 10)

Q3の回答サンプル

ちょっと難しい Q3: プログラムの有名な入門問題fizzbuzzに挑戦してみましょう。1から100の数字で15で割り切れる時にfizzbuzzと出力し、3で割り切れる時にはfizzと出力し、5で割り切れる時にはbuzzと出力してください。

解答例

# 1から100まで繰り返し返し
for i in range(1, 101):
  # 15で割り切れる時
  if i % 15 == 0:
    print(i)
    print("fizzbuzz")
  # 3で割り切れる時
  elif i % 3 == 0:
    print(i)
    print("fizz")
  # 5で割り切れる時
  elif i % 5 == 0:
    print(i)
    print("buzz")

参考文献

【現役エンジニアが物申す】未経験のエンジニアが企業でインターンやアルバイトをする際のNGな言葉5選

この記事の対象読者

  • 未経験からエンジニアになりたい
  • 他業界からエンジニアに転職したい
  • フリーランスを目指したい
  • 自由なワーキングスタイルを確立した
  • リモートワーク, テレワークがしたい

など...
近年はインフルエンサーの影響も強くあり、プログラミングスクールや書籍などを購入&活用してプログラミング学習を進めていらっしゃる方が非常に多いです。ある程度プログラムを自由に記述出来るようになった後に目指すのが企業でのインターンやアルバイト、正社員採用ではないでしょうか。

確かに現場でエンジニアとしてのスキルを磨くことは非常に重要で、現場でしか学ぶことが出来ない、もしくは学ぶことが難しいスキルはあります。
「よし!基礎を学び終えたしエンジニアの採用面接に行くか!」

...と張り切り面接を受けたものの思ったような結果がでない。企業からの返事は大体、以下の様なことが多くはありませんか?

  • 判断を見送った
  • 不採用
  • スキルが足りていない
  • 弊社とは合わない
  • 特に理由がないがお断り

「エンジニアが足りてないんじゃねぇのかよ!」と不満が爆発しそうになりますよね。そんなあなたがこの記事の対象読者です。なぜ毎回、エンジニアの採用面接で不本意な結果に終わってしまうのかのヒントになるはずです。

その前に、なぜ私がこのような記事を書いているのかを知って頂くだめに、簡単な自己紹介をしておきます。早く本題を見たいという方は次の章へとお進み下さい。

筆者について

この記事の執筆時(2020/03/26)で23歳になります。新卒としてベンチャー企業に就職しましてWebのDeveloper(エンジニア)として働いています。とはいったものの、大学時代の選考は土木・建築であり、入学時は公務員を目指していたこともあり、情報学部を卒業したわけではりません。
「じゃあ、どうやってエンジニアになったの?」とよく聞かれますが、皆さんと同じように独学です。Progateから始まり、PyQもやりましたし、関連する書籍も数冊は読みました。当時は1日中、プログラミングの勉強をしていた記憶があります。

パソコン自体は子供の頃からよく触っていたのでブラインドタッチは出来ましたし、Officeに関しては資格を持っていましたが、プログラミングやコンピューターサイエンスに関してはほとんど知識・経験がなく、中学2年生の時に「猫でもわかるC言語」で挫折した過去があります(あれは絶対、猫には分からん)。

ある程度、独学にて学習をした後に現職場にてインターンとして採用頂きまして、多くのスキルを学ばさせて頂きました。そして今に至ります。つまりは、未経験の状態から独学で学習をし、インターンを得てエンジニアとして採用されたというロードマップを経験しております。

伝えてはいけないNGな言葉5選

お待たせしました。
それでは、思っていたとして伝えない方がいい言葉を未経験からエンジニアになった私が、エンジニア採用の現場を覗き見している立場から特に多いものを5つ引っ張ってきました。以下に該当する、もしくは近いような事を言っていたとしたら要注意です。

現場でスキルを磨き、いずれはフリーランスになりたい

よく考えて見てください。
企業からすれば未経験の人を雇うのは結構なコストでありリスクになります。現場の経験がない人を雇うよりも、中途で経験のあるエンジニアを雇った方が良いのは明白です。それでも、未経験の人を雇う理由はシンプルで多くの企業が人手不足だからです。
未経験であってもインターンなどで採用して社内でエンジニアとして育てて、案件を任せられるようになれば、企業にとってはエンジニアが供給され、本人は現場でスキルが学ぶことが出来て、WinWinの関係になりますよね。

しかし、どうでしょうか。最初からフリーランスになりたいです」と言われた時に企業がどう思うのか。
「あ、この人を社内でエンジニアとして育てたとしてもスキルが身についたら居なくなっちゃうじゃん」

...と思われてしまいます。ただでさえ未経験の方は育てるコストとリスクがあるのに、スキルが身に付いた途端に居なくなってしまうなんてのは企業にとってはメリットが一つもありません。仮に居なくならない可能性があるとしても、リスクがかなり高いですよね。

そんな旨が履歴書に記述されていたら、面接するまでも無くと不採用と判断されてしまうでしょう。本心では「いずれフリーランスになりたい」と思っていても、その思いを伝えてはいけません。

企業側もボランティアで育成をやってくれるわけではないということを留意しましょう。

〇〇について現場で教えてもらいたい

教えてもらう前提のいわゆる待ちの姿勢は現場では好かれません。案件を進める中で分からない事は当然あるでしょうし、いくらググっても解決しない時、初めて社内のエンジニアに質問したり相談したりするのが現場では当たり前というかマナーです。

「無意味に相手の時間を奪ってはいけない」ということを多くのエンジニアが留意しています。

あれもこれも質問していては他のエンジニアの業務に支障が出てしまいます。何でも質問するのが悪いというよりは、先ほども説明した通り、結果的に無意味に相手の時間を奪ってしまうことになるからです。そんな人と一緒に仕事がしたいと思うでしょうか。

また、「この人は自分で調べたり、学習しない人なんだな」と思われ、将来性がない、いわゆるエンジニアリング能力が低いと判断されてしまうでしょう。

〇〇をやったことはありませんが、〇〇に興味があり業務に携わりたい

この言葉に対して思う事は1つだけで

「興味があるなら何でやらないの?」

...だけです。売り文句としてしょうがなく書いている人もいるでしょうが、自分で調べてその単語までたどり着いていたのであれば、非常に勿体無いないですね。最近だと「人工知能に興味があります」と無意味に記入する人が非常に多いです。

「でも、第一歩の踏み出し方が分からない」という方が多いですが、企業でのインターン、アルバイトは第一歩を踏み出す場所ではなく、第一歩を踏み出した人が、さらに一歩を踏み進めるために活用する場所です。第一歩を踏み出していない人に親切に教えてあげるというのがどれだけの時間とコストを生み出すのかを考えて見て下さい。 繰り返しお伝えしますが、企業はボランティアではありません。

自分の興味を伝えるためにも、ここは最低限パスして来るべきです。ただプロである必要はありません。未経験なので変に気張る必要はありません。どんな形でも自分の興味があるものに対して何かしらのアプローチをして来た事が大切です。
「〇〇に興味があるので自分で作って見ました。こういう部分が難しかったですが、業務ではどうやっているんですか?」という質問が出来る人と「〇〇に興味はありますが、やったことはないです。でも仕事はしたいです」という人のどちらに興味が湧きますか。当然、前者ですよね。

あなたと同じように未経験の方が多く、同じように面接を受けていると考えてください。例えばお互いがスクールの卒業生であれば、同じ技術で同じ課題を、同じポートフォリオを作成しているわけになります。
そんな状況で、差別化が出来る要素がなければ、あなたは有象無象の一人でしかなく、企業に興味を持ってもらうことはきっと出来ないでしょう。

「〇〇(プログラミング言語名)」が書けます

特定のプログラミング言語を書けることを売りにしている人がたまにいますが、ハッキリ言って現場では何かしらのプログラミング言語は書けて当たり前です。日本で日本企業に対して「日本語が書けます!」といって就活をする日本人はまずいないでしょう。

ある特定のプログラミング言語に関しては記述出来ることがスキルになり得ることがありますが、未経験の状態からアサインする場合はそのようなことはまずありません。現場で「繰り返し処理ってどう記述するですか?」なんて聞こうものなら、嫌な顔をされても文句は言えません。

「〇〇が書ける」だけではスキルとしては不十分ですので、先ほど記述したように「△△の分野に興味がある。〇〇を使って実装してみました」というレベルの話まで出来るのがベストです。掛け算できる何かを用意しておきましょう。

未経験からのスタートであればプログラミング言語は複数記述出来るよりも1つの言語に対しての理解を深めた方が良いです。あれもこれもほぼ初心者であると扱える分野が非常に狭くなります。1つの言語で深く学んでおけば、次に新しく覚える言語で同じことを文法を書き換えるだけで記述出来ることに気付くことになるでしょう。

特にやりたい事はないがエンジニアになりたい

こういう人もかなり多いです。なぜか世の中ではエンジニアのヘンテコなイメージばかりが先行してしまっています。1000万円稼げるだの、自由な時間に仕事が出来るだの、学歴は関係ないだの...実際どうなのかは一旦、置いておいて..._。

「早く自分もそのボジションに行きたい」という気持ちは伝わってきますが、目指す理想像がなく企業としては対応に困ります。「ウチじゃなくてもいいんじゃないの?」と思われてしまったら負けです。
今から面接を受けようとしている企業の理念や自社開発製品は理解していますか。その企業が開発に採用している言語やフレームワークが何か理解していますか。最低限、それらを知っておくのはマナーですし、知らないのであれば相手の時間を無駄に奪う結果になり、失礼な人だと判断されてしまうでしょう。

また採用されたは良いものの、特にやりたいことがないために、フロントエンド(html/css/JavaScript etc..)に挑戦したものの、自分には向いていないなーと思い、次はバックエンドに挑戦したものの、またまた自分には向いていないなーを繰り返し...企業での時間を無駄にしつつ、ダメなやつという評価を受けてしまう可能性があるため、自分にとってもマイナスになってしまうでしょう。

「エンジニアになりたい!」と思ったのなら、エンジニアがどんな仕事なのかを徹底的に調べてみて下さい。ひとえにエンジニアといっても、様々な分野が存在しています。自分がどの分野に進みたいのか、どのような技術に触れてみたいのか。それを事前に調べて理解せずに、仕事を始めることに恐怖を感じてください。

興味のない分野に進んで1000万円稼ぐのと、事前に念密に調査をして興味のある分野に進んで1000万円稼ぐのと、どちらが良いでしょうか。

まとめ

一言で言うならば「企業もボランティアでやっているわけではない」ということです。それでもインターンやアルバイト、正社員を未経験の方からも募集しているのは一番に人手不足であるからです。たとえ、未経験の方であっても才能溢れるダイヤモンドの原石のような方がちょくちょくいらっしゃいます。企業からすれば、その原石を磨き上げて、社内で活躍してもらうことが1番の利益なわけです。
今回紹介した言葉に該当する考え方を持つことは自由ですが、伝えないように伝わらないようにするのが賢い戦略でしょう。磨き上げた直後に爆発して粉々になってしまうと分かっているダイヤモンドを磨く人はおそらくいませんから。

せひ、一度、自分のプロフィールを見返して見て下さいね。

おまけ: 企業インターンの探し方について

そもそも、インターンの募集を見つけることが出来ないという方のために当時、私がどのようにしてエンジニアの企業インターンを見つけたのかをご紹介しておきます。インターンを見つけるのには「Wantedly」というサービスを使用しました。

www.wantedly.com

検索して「面白そうだな」と思ったら「話を聞いてみたい」をクリック。何も連絡が返ってこないことも多いですが、運が良ければ企業から返信が来て、面接の日程調節などに進むことが出来ます。

Wantedlyでは希望するものが見つからなかった場合には企業に直接、インターンをさせてほしいという営業をすると良いです。公開はされていないものの案外すんなりとインターンを受け付けてくれる企業がありますが、インターンのお願いの仕方、マナーには気をつけましょう。

【モジュールとの比較】Elixirで無名関数を使って再帰処理を記述する方法

無名関数では再帰処理が難しい

Elixir再帰関数を記述しようと思った際には、defmodule Fooと定義して、そのモジュール内部にdef barのように関数を定義して、パターンマッチもしくは、分岐処理によって再帰関数を処理するのが一般的。

defmodule Sample do
  def sum([], acc), do: acc
  def sum([head | tail], acc) do
    sum(tail, acc+head)
  end
end

Sample.sum([1,2,3], 0)
|> IO.puts()
# 6

しかし、わざわざモジュールに定義したくない時、対して使い回す予定もなく、1回きりの再帰処理が書きたい場合には上記のようにモジュールを定義して、再帰関数を記述するという方法は煩わしいとも言える。とすると、使い切りの関数として候補にあがるのは無名関数ということになる。
しかしながら、工夫なしに記述した無名関数では再帰処理を行うことが出来ない。それはなぜか。以下のコードを元に話を進める。

sum_ = fn lst, acc -> 
  case lst do
    [] -> acc
    [head | tail] -> sum_.(tail, acc+head) # <- ここでerror
  end
end

sum_.([1,2,3], 0)

実行結果

warning: variable "sum_func" does not exist and is being expanded to "sum_func()", please use parentheses to remove the ambiguity or change the variable name
  Main.exs:4

このerrorから読み取るに4行目に記述しているsum_.(tail, acc+head)が実行不可能であるということ。それはなぜかというと、sum_/2関数のスコープが無名関数の内部からは参照出来ないからということになる。つまり、Elixirの無名関数は自身を自身で参照することが出来ないということになる。それに対してモジュールに定義された関数は自身のスコープを参照可能ということになっている。

ということで一工夫

色々と試行錯誤して以下の形に落ち着いた。レシピとしては無名関数の内部で別の無名関数を定義して、その無名関数自身を引数に渡して、再帰的に呼び出すことで先ほどのスコープ対象外になる問題を解決した。全く勉強したことがないが、ラムダ計算の分野からの知恵を借りた。

sum_ = fn arg_lst ->
  # 自分自身を引数に受け取ることでスコープ問題を解消
  sub_sum = fn lst, acc, own_func ->
    case lst do
      # 終了条件を記述
      [] -> acc
      # 自分自身を呼び出し、引数にも自分自身を渡す
      [head | tail] -> own_func.(tail, acc+head, own_func)
    end
  end
  # 内部で定義した無名関数を実行
  sub_sum.(arg_lst, 0, sub_sum)
end

# ついでにアキュムレーターを内部化
sum_.([1,2,3])
|> IO.puts()
# 6

無名関数の内部で別の無名関数を定義して、その無名関数自体を、その無名関数の引数に渡すという頭がおかしくなりそうな一手間を加えることで無名関数でも再帰処理を行うことが出来る。合計値を求めるだけのシンプルな処理なので可読性は何とか保てているが、より複雑な処理を行うとなると、無名関数での再帰処理には首を傾げることになる。コードの行数もシンプルにしようとしているのに行数が増えてしまっている。

「使うな!」とまでは言わないが、趣味用であり、チーム開発での使用は避けるべきだと感じた。こういうの好きだけど。せっかく覚えた知識なので、いくつかサンプルを記述した。モジュールとの比較もしているので可読性の悪さを感じてみてほしい。

サンプル

リストの各要素をN倍に

無名関数ver

n_times = fn n, lst -> 
  sub_n_times = fn lst, acc, own_func ->
    case lst do
        [] -> acc
        [head | tail] -> own_func.(tail, acc ++ [head * n], own_func)
    end
  end
  sub_n_times.(lst, [], sub_n_times)
end

n_times.(2, [1,2,3])
|> IO.inspect()
# [2, 4, 6]

モジュールver

defmodule Sample do
  def n_times(p, lst) do
    _n_times(lst, p, [])
  end
  defp _n_times([], _, acc), do: acc
  defp _n_times([head | tail], p, acc) do
    _n_times(tail, p, acc ++ [head * p])
  end
end

Sample.n_times(2, [1,2,3])
|> IO.inspect()
# [2, 4, 6]

というかEnum.map/2で良いのでは説。

enum_map_ver = fn p, lst ->
  Enum.map(lst, fn n -> n * p end)
end

enum_map_ver.(2, [1,2,3])
|> IO.inspect()
# [2, 4, 6]

クイックソート

以前の記事で記述したクイックソート(先頭要素をpivotとして取得するversion)を無名関数の再帰処理を用いてリライトしてみた。書いた感想といてはモジュールの方が楽だなというのが正直な感想。書き切った時は嬉しいが、後に見返してみるとそれぞれの関数の引数のスコープがどこまで参照可能なのかが分かりにくく、やはり可読性に欠ける。

無名関数ver

quick_sort = fn base_lst -> 
  append = fn sorted_lst, pivot ->
    sub_append = fn lst, acc, own_func -> 
      case lst do
        [] -> acc ++ [pivot]
        [head | tail] -> 
          if pivot > head do
            own_func.(tail, acc ++ [head], own_func)
          else
            acc ++ [pivot, head] ++ tail
          end
      end
    end
    sub_append.(sorted_lst, [], sub_append)
  end
  Enum.reduce(base_lst, [], fn p, acc -> 
    append.(acc, p)
  end)
end

quick_sort.([8,2,6,5,7,3,1,4,9])
|> IO.inspect()
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

モジュールver

defmodule Algo do
  def quick_sort(lst), do: _quick_sort(lst, [])
  defp _quick_sort([], accum), do: accum
  defp _quick_sort([head | tail], accum) do
    res = append(head, accum, [])
    _quick_sort(tail, res)
  end
  def append(num, [], accum), do: accum ++ [num]
  def append(num, [head | tail], accum) do
    case num > head do
      true -> append(num, tail, accum ++ [head])
      false -> accum ++ [num] ++ [head] ++ tail
    end
  end
end

おまけ

こんな風に記述しても無名関数で再帰処理を行うことが出来るが、使用者に対して、引数に無名関数を渡すことを意識させる必要があるため、ナンセンスだとは思うが、分かっていて使うのであれば、コード量は減るので悪くはないと思う。

sum_ = fn lst, acc, func -> 
  case lst do
    [] -> acc
    [head | tail] -> func.(tail, acc+head, func)
  end
end

sum_.([1,2,3], 0, sum_)
|> IO.puts()
# 6

参考文献

【使用例あり(for文vs内包表記)】Pythonでデコレーターを使って関数の平均実行時間を測定する

実装したデコレーター用の関数

全体のコードはこちら
github.com

すでに似た様な記事が多く記述されているが、指定回数の平均実行時間を出力したいのは自分が初めてだと信じて実装してみた。pythonデコレーターを使用して、指定回数の平均実行時間を算出することが出来る。デコレーターに関する詳しい説明はすでに多くの記事が存在しているのでそちらにおまかせします。

./bench.py

import time
import logging

# benchmark用の関数
def benchmark(try_num):
    """
        TASK: デコレーターを行い関数に対して指定回数の実行を行う
        lst: []int, []float -> 対象の配列
        try_num: int -> 実行回数の合計数
        return ([]float, float) -> (実行結果を格納した配列, 平均値)
    """
    def _timer(exec_func, *args, **kwargs):
        """
            TASK: デコレーター経由で受け取った関数を実行し、実行時間を返す
            exec_func: function -> 実行したい関数
        """
        start = time.time()
        exec_func(*args, **kwargs)
        end = time.time()
        return end - start

    # デコレーターで装飾した関数を受け取る関数
    def _benchmark(exec_func):
        """
            TASK: 関数を受け取り、指定回数実行するサブ関数
            exec_func: function -> 実行したい関数
            return []float
        """
        # 装飾した関数の引数を受け取りベンチマークを実行する関数
        def _sub_bencmark(*args, **kwargs):
            # 計測結果
            logging.info(f"Start benchmark  「{exec_func.__name__}」 ...")
            messured = [_timer(exec_func, *args, **kwargs) for _ in range(0, try_num)]
            average = sum(messured) / len(messured)
            logging.info("Result: Average execution time -> {:.5f} s (total exec {})".format(average, try_num))
            return exec_func(*args, **kwargs)

        return _sub_bencmark

    return _benchmark

サンプル

from bench import benchmark
import logging

# loggerの設定(defaultでDEBUG, INFOは出力は出力されないため必須)
formatter = '%(levelname)s : %(asctime)s : %(message)s'
logging.basicConfig(level=logging.INFO, format=formatter)

@benchmark(100)
def sample(s):
    return s

sample("nice")

実行結果

INFO : 2020-03-14 20:38:35,967 : Start benchmark  「sample」 ...
INFO : 2020-03-14 20:38:35,967 : Result: Average execution time -> 0.00000 s (total exec 100)

@benchmark(10)と対象の関数の上部に記述することで デコレーターとなり、対象の関数を引数で受け取ることが出来る。ただ、今回は実行する回数を指定して、平均値を算出したいので実行回数を受け取る専用の関数(benchmark)でwrapしている。関数の内部化についてはやや複雑になっているが、

  • 実行回数を受け取る
  • 実行したい関数を受け取る
  • 実行したい関数に渡す引数を受け取る
  • 指定回数だけ実行して平均実行時間をlogに出力

という流れで実行しているだけなので大したことはやっていない。注意としては結果は従来の関数の戻り値に影響を与えないためログに出力している。

使用例: for文内包表記map関数での速度比較

よく「Pythonfor文は遅い。内包表記を使おう」と言われるが、それが本当なのかを先ほど作成したベンチマーク用のデコレーターを用いて検証してみよう。ついでなので、map関数も比較対象に追加した。

配列の各要素を2倍するという処理をfor文, 内包表記, map関数のそれぞれを用いて実装した。それぞれ実行した関数に対して先ほど作成したデコレーターを記述してやる。今回は10,000回実行での平均速度を比較してみよう。

./funcs.py

from bench import benchmark

TRY_NUM = 10000

@benchmark(TRY_NUM)
def for_double(lst):
    """
      TASK: forを使った配列の要素を全て2倍にする関数
      lst: []int, []float -> 対象の配列
      return []int, []float
  """
    res = list()
    for n in lst:
        res.append(n * 2)
    return res

@benchmark(TRY_NUM)
def comprehension_double(lst):
    """
      TASK: 内包表記を使った配列の要素を全て2倍にする関数
      lst: []int, []float -> 対象の配列
      return []int, []float
  """
    return [n * 2 for n in lst]

@benchmark(TRY_NUM)
def map_double(lst):
    """
      TASK: map関数を使った配列の要素を全て2倍にする関数
      lst: []int, []float -> 対象の配列
      return []int, []float
  """
    return list(map(lambda x: x * 2, lst))

これで関数側の用意は完了。次に実行ファイルを作成する。

./main.py

from funcs improt (
  for_double,
    comprehension_double,
    map_double
)

import logging

# loggerの設定(defaultでDEBUG, INFOは出力は出力されないため必須)
formatter = '%(levelname)s : %(asctime)s : %(message)s'
logging.basicConfig(level=logging.INFO, format=formatter)

def create_data(total_num):
    """
      TASK: テスト用の指定要素数を持つ配列を作成する関数
      total_num: int -> 配列要素数
      return []int
  """
    return [n for n in range(1, total_num+1)]

if __name__ == "__main__":
    # 各種の関数を実行
    data_for_benchmark = create_data(10000)

    # benchmarkを実行
    for_double(data_for_benchmark)
    comprehension_double(data_for_benchmark)
    map_double(data_for_benchmark)

では、結果を確認してみよう。

$ python3 main.py

INFO : 2020-03-14 20:53:47,441 : Start benchmark  「for_double」 ...
INFO : 2020-03-14 20:54:04,680 : Result: Average execution time -> 0.00172 s (total exec 10000)
INFO : 2020-03-14 20:54:04,692 : Start benchmark  「comprehension_double」 ...
INFO : 2020-03-14 20:54:13,866 : Result: Average execution time -> 0.00091 s (total exec 10000)
INFO : 2020-03-14 20:54:13,868 : Start benchmark  「map_double」 ...
INFO : 2020-03-14 20:54:28,890 : Result: Average execution time -> 0.00150 s (total exec 10000)

おお、やはり内包表記はかなり速い。数値としては約2倍ほど速いようだ。以外だったのはmap関数for文とほとんど同速であったということで、この結果の他に何度か検証を行なったが、for文が速い時もあればmap関数が速い時もあった。しかしながら内包表記は常に一番速かった。

こんな感じで、今回、実装したデコレーター用のベンチマーク関数を用いることで関数の平均実行速度を気軽に測定することが出来るので、ぜひ使ってくみて下さいね。

おまけ1: for文, 内包表記, map関数の別結果

INFO : 2020-03-14 20:53:17,841 : Start benchmark  「for_double」 ...
INFO : 2020-03-14 20:53:27,750 : Result: Average execution time -> 0.00099 s (total exec 10000)
INFO : 2020-03-14 20:53:27,753 : Start benchmark  「comprehension_double」 ...
INFO : 2020-03-14 20:53:33,386 : Result: Average execution time -> 0.00056 s (total exec 10000)
INFO : 2020-03-14 20:53:33,389 : Start benchmark  「map_double」 ...
INFO : 2020-03-14 20:53:44,338 : Result: Average execution time -> 0.00109 s (total exec 10000)
INFO : 2020-03-14 21:01:10,190 : Start benchmark  「for_double」 ...
INFO : 2020-03-14 21:01:22,041 : Result: Average execution time -> 0.00118 s (total exec 10000)
INFO : 2020-03-14 21:01:22,045 : Start benchmark  「comprehension_double」 ...
INFO : 2020-03-14 21:01:28,320 : Result: Average execution time -> 0.00062 s (total exec 10000)
INFO : 2020-03-14 21:01:28,328 : Start benchmark  「map_double」 ...
INFO : 2020-03-14 21:01:40,712 : Result: Average execution time -> 0.00124 s (total exec 10000)

おまけ2: 内包表記map関数が好きな人へ

Pythonの内包表記は関数型言語であるHaskellからの輸入品かつ、map関数も同じく関数型言語からの輸入品として有名です。少しでも興味があれば私が押しているElixirという言語について、ぜひ覗いてみて下さい。きっと気に入って頂けると思います。

www.okb-shelf.work

www.okb-shelf.work

参考文献

【サンプルコード有り】golangで三項演算子っぽいものを記述する方法について

嫌だなぁと感じる場面

業務を進める中で、以下の様なコードを書くことが多かった。

drink := ""
if orderNum {
    drink = "green tea"
} else {
    drink = "tea"
}

orderNumの様な関数の引数で指定される値であったり、http経由で指定されたqueryによって値をセットする処理なのだが、事前にdrinkという同じ型に該当する初期値となる変数を用意しておく必要があり個人的には好きではない。しかしながら、これがgolangの言語デザインでもあり、QiitaRui Ueyamaさんも言及している。

三項演算子があればなぁ...」と思い、何とかならないかなとあがいた結果が以下になる

無名関数を使った記述方法

先ほどのコードを無名関数を使った三項演算子っぽいものに書き換えてみた。

func Sample(orderNum int) string {
    // 無名関数を即時実行する
    drink := func(n int) string {
        if n == 1 {
            return "green tea"
        } else {
            return "tea"
        }
        // 即時実行のために引数を指定
    }(orderNum)

    // 無名関数の実行結果を返す
    return drink
}

やっていることは大したことはなくて、無名関数を定義して、その無名関数の戻り値を任意の変数に代入にしているだけ。わざわざ、外部に関数を定義するまでもないので無名関数で良くないかという判断に至った。nの値によって戻り値のパターンを増やしたいのならswitch文の採用も考える。無駄な変数を宣言する必要がないため、割と気に入っている。

パフォーマンスについて

気になるのは、無名関数を使った記述方法にすることで、どれだけオーバーヘッドになってしまうのかという点。普通に考えて、無名関数を使った方がパフォーマンスは悪いだろうなと思うが、考えても分からないので測定してみた。
それぞれ無名関数を使用する記述と、従来の記述とで1万回ずつ測定して、平均値を算出した。

比較のためにそれぞれを関数内に内包して、それぞれの関数の実行速度を比較するとする。

func AnonymousFunctionSample(orderNum int) string {
    // 無名関数を即時実行する
    drink := func(n int) string {
        if n == 1 {
            return "green tea"
        } else {
            return "tea"
        }
        // 即時実行のために引数を指定
    }(orderNum)

    // 無名関数の実行結果を返す
    return drink
}

func NormalSample(orderNum int) string {
    drink := ""
    if orderNum == 1 {
        drink = "green tes"
    } else {
        return "tea"
    }
    return drink
}

検証に使用したコードはこちら。
github.com

実行結果

$ bash benchmark.sh

[info] clear go test cached...
[info] start benchmark
=== RUN   TestSample
[info] average for anonymous function:  9.854730000000441e-08
[info] average for normal exec:  1.0573470000000177e-07
--- PASS: TestSample (0.01s)
PASS
ok      ternary_operator/src    0.015s

1万回平均で見ると、無名関数を使用した記述方法の方が速いようだ。
可読性は好き嫌いが分かれる所だと思うが、良ければ使ってください。

参考文献