Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

プレゼン、ボランティアコーチします!

勉強会で話したいけど、プレゼンが初めて、苦手という方に無償でコーチします!

  • スライドのレビュー
  • 録画リハへのアドバイス

Twitter@jyukutyoまでメンションでもDMでも。 デブサミやJJUG CCCなど200人規模で登壇しました。海外での登壇も短いながらあり。デブサミ2017では公募スピーカー1位でした!

オレオレJVM言語を作ろう! How to create a new JVM language #Graal #Truffle

DevoxxUSに参加した際、とてもおもしろかったセッションがありました。Oleg Šelajevの"How to create a new JVM language"というセッションです。

www.youtube.com

OlegはvJUG(virtual JUG: オンライン上の仮想Java User Group)のOrganiser、JRebelやXRebelを作っているZeroTurnaroundの人です。*1

このセッションでは、ANTLRとGraal(とくにTruffle)を使って四則演算する言語を作ろう!というものでした。とてもおもしろかったのですが、1つ問題がありました。

ソースが公開されていない!

OlegのGitHubにもありませんでした。

そこで僕は決めました。セッション動画を見ながら、表示されるコードを写して自分で実装しよう!と…

結果、できました!

f:id:jyukutyo:20170712062605g:plain

ソースはこちらです。

github.com

ただ、動画では公開されていない部分もあり、いろいろ検索してTruffleのサンプルで簡易言語を実装したプロジェクトのソースを参考にしました。見た感じOlegもこれを元にしている気がします。

github.com

概要

簡易な言語として、以下の構成にします。

Module Takes Returns
Lexer Text(Code) Tokens
Parser Tokens AST(Abstract Syntax Tree)
Compiler AST JVM bytecode

つまり、Code -(Lexer)-> Tokens -(Parser)-> AST -(Compiler)-> JVM bytecodeとして、JVMで実行します。

今回Lexer/Parserの部分にANTLRを、それ以降の部分にGraal、Truffleを使います。

ANTLR

ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It’s widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build and walk parse trees.

http://www.antlr.org/

(適当訳)ANTLRはパワフルなジェネレータです。構造化されたテキストやバイナリファイルの読み込みや処理、実行、解釈のためのものです。言語やツール、フレームワーク構築に広く使われています。文法からANTLRはパースツリーを構築し巡回するパーサを生成します。

文法を定義してANTLRに渡せば、LexerやParserを生成します。

Graal、Truffle

GraalとTruffleが何か、まだ僕にはうまく説明できませんが…

Graal is a research compiler. Truffle is … well, that’s kind of hard to explain, because there’s not much to compare it to. The shortest summary I can think of is this: Truffle is a framework for implementing languages using nothing more than a simple abstract syntax tree interpreter.

https://blog.plan99.net/graal-truffle-134d8f28fb69

(適当訳)Graalはリサーチコンパイラだ。Truffleは…うーん、説明が難しい。比較するものがないから。僕が考え得るもっとも短い概要はこうだ。TruffleはASTインタプリタでしかないものを使って言語を実装するためのフレームワークだってことだ。

Graal is a new just-in-time compiler for the JVM focused on peak performance and multi-language support.

http://www.oracle.com/technetwork/oracle-labs/program-languages/overview/index.html

(適当訳)GraalはJVMのための新しいジャスト・イン・タイムコンパイラで、最高のパフォーマンスと多言語サポートに重点を置いています。

またGraalVMというものもあり、これはGraal(JITコンパイラ)やTruffleを持つJVMです。

GraalやGraalVMそのものについては、日本オラクルの西川さんのプレゼンテーション資料が大変わかりやすいです。

www.slideshare.net

今回作成する言語はANTLRで生成したコードが作るASTを、TruffleのAPIを使いインタープリットして、JVM上で処理を実行します。

ソース解説

ANTLR

今回は四則演算の構文をそのままプログラミング言語の文法とします。ANTLRを使ってBNFを定義します。

grammar Math;

prog
    : expr (EOF | NEWLINE)
    ;


expr
    : '(' expr ')'                                   #parensExpr
    | functionName '('argument? (',' argument)* ')'  #invoke
    | '->' expr                                      #asyncExpr
    | left=expr op=('*'|'/') right=expr              #infixExpr
    | left=expr op=('+'|'-') right=expr              #infixExpr
    | value=NUM                                      #numberExpr
    ;

functionName: ID;
argument: expr;

ID
    : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*
    ;


OP_ADD: '+';
OP_SUB: '-';
OP_MUL: '*';
OP_DIV: '/';

NUM: [0-9]+ ('.' [0-9]+)?;
WS: [ \t]+ -> skip;
COMMA: ',';

NEWLINE: '\r'? '\n';

BNF?という人も、内容はつかみやすいと思います。Math.g4とします。

公式サイトANTLRを参考にセットアップしてください。macOSならbrewでインストールできます。

$ antlr4
ANTLR Parser Generator  Version 4.7

$ antlr4 -package grammer Math.g4

このコマンドで、BNFからJavaソースコードを生成します。packageオプションはJavaのパッケージの指定です。

$ cd jvmmathlang/src/main/java/grammer
$ ls -al
-rw-r--r--  1 jyukutyo  staff    691  7  8 11:08 Math.g4
-rw-r--r--  1 jyukutyo  staff    145  7  8 11:09 Math.tokens
-rw-r--r--  1 jyukutyo  staff   3548  7  8 11:09 MathBaseListener.java
-rw-r--r--  1 jyukutyo  staff   4545  7  8 11:09 MathLexer.java
-rw-r--r--  1 jyukutyo  staff    145  7  8 11:09 MathLexer.tokens
-rw-r--r--  1 jyukutyo  staff   3234  7  8 11:09 MathListener.java
-rw-r--r--  1 jyukutyo  staff  15288  7  8 11:09 MathParser.java

これらのソースはANTLRのクラスを利用しますので、依存ライブラリにANTLRを追加します。

    <dependency>
      <groupId>org.antlr</groupId>
      <artifactId>antlr4-runtime</artifactId>
      <version>4.7</version>
    </dependency>
AST

ANTLRは生成したクラスにもあるようにLexerとParserの部分を担当します。処理した結果ASTを構築します。確認してみましょう。

$ mvn clean compile

$ pwd
jvmmathlang/target/classes

$ grun grammer.Math prog -gui
1 + 2
^D

f:id:jyukutyo:20170708122317p:plain

こんな感じでASTが構築されたことがわかります。

IntelliJ Plugin

ANTLRBNFを書くときは、プラグインを入れると書きやすいです。

ANTLR v4 grammar plugin :: JetBrains Plugin Repository

Truffle

Truffleを使うため、依存ライブラリに追加します。

    <dependency>
      <groupId>com.oracle.truffle</groupId>
      <artifactId>truffle-api</artifactId>
      <version>0.26</version>
    </dependency>
    <dependency>
      <groupId>com.oracle.truffle</groupId>
      <artifactId>truffle-tck</artifactId>
      <version>0.26</version>
    </dependency>
    <dependency>
      <groupId>com.oracle.truffle</groupId>
      <artifactId>truffle-dsl-processor</artifactId>
      <version>0.26</version>
    </dependency>

やっとJavaコードです。

public class JvmMathLangMain {

    public static void main(String[] args) throws MissingNameException {
        PolyglotEngine engine = PolyglotEngine.newBuilder().setIn(System.in).setOut(System.out).build();
...
        try(Scanner s = new Scanner(System.in)) {
            String program = null;
            while(true) {
                System.out.print("> ");
                program = s.nextLine().trim();
...
                Source source = Source.newBuilder(program).name("<stdin>").mimeType(JvmMathLang.MIME_TYPE).build();
                PolyglotEngine.Value result = engine.eval(source);
...
            }
        }
    }
}

mainメソッドです。入力(今回の場合数式)を文字列でよみ、Truffle APIのSourceをビルド、それをエンジンのeval()に渡して数式を評価します。

ではこのエンジンから処理を委譲される部分を見ていきます。言語そのもののクラスです。

@TruffleLanguage.Registration(name = "JVMMATHLANG", version = "0.0.1", mimeType = JvmMathLang.MIME_TYPE)
@ProvidedTags({StandardTags.CallTag.class, StandardTags.StatementTag.class, StandardTags.RootTag.class, DebuggerTags.AlwaysHalt.class})
public class JvmMathLang extends TruffleLanguage<JvmMathLangContext> {

    public static final String MIME_TYPE = "application/x-jvmmathlang";

TruffleLanguageを継承します。JvmMathLangContextは自分で作ったクラスで、コンテキスト、いわゆる入れ物のクラスです。

@TruffleLanguage.Registrationで言語名やバージョン、MIMEタイプを指定します。上のGIFにも表示されています。@ProvidedTagsはまだ理解していません。

    @Override
    protected CallTarget parse(ParsingRequest request) throws Exception {
        Map<String, JvmMathLangRootNode> functions = parseSource(request.getSource());

        JvmMathLangRootNode main = functions.get("main");

        JvmMathLangRootNode evalMain = new JvmMathLangRootNode(this, main.getFrameDescriptor(), main.getBodyNode(), "main");
        return Truffle.getRuntime().createCallTarget(evalMain);
    }

    private Map<String, JvmMathLangRootNode> parseSource(Source source) throws IOException {
        InputStream inputStream = source.getInputStream();
        CharStream charStream = CodePointCharStream.fromBuffer(CodePointBuffer.withBytes(ByteBuffer.wrap(IOUtils.toByteArray(inputStream))));
        MathLexer mathLexer = new MathLexer(charStream);
        CommonTokenStream tokenStream = new CommonTokenStream(mathLexer);
        MathParser mathParser = new MathParser(tokenStream);

        MathParser.ProgContext prog = mathParser.prog();

        ParseTreeWalker treeWalker = new ParseTreeWalker();
        MathParseTreeListener listener = new MathParseTreeListener();
        treeWalker.walk(listener, prog);

        return listener.getFunctions(this);
    }

エンジンのeval()から処理が始まり、parse()メソッドが呼び出されます(Sourceに指定したMIMEタイプがこの言語のものであるため)。ParsingRequestにはeval()に渡したSourceあります。parseSourceの部分がポイントです。ここはANTLRが生成した処理を呼び出しています。Lexer、Parserを使いASTを作りました。このANTLRのASTをTruffleでのASTに変換しているのがMathParseTreeListenerです。ParseTreeWalkerが巡回したときにリスナーとして処理が呼び出されます。

public class MathParseTreeListener extends MathBaseListener {

    private Map<String, JvmMathLangRootNode> functions = new HashMap<>();

    private JvmMathLangNode node;

    private LinkedList<JvmMathLangNode> mathLangNodes = new LinkedList<>();
...
    @Override
    public void exitNumberExpr(MathParser.NumberExprContext ctx) {
        String text = ctx.value.getText();
        try {
            mathLangNodes.push(new LongNode(Long.parseLong(text)));
        } catch(NumberFormatException e) {
            mathLangNodes.push(new BigDecimalNode(new BigDecimal(text)));
        }
    }

継承元のMathBaseListenerはANTLRで生成したクラスです。LinkedListはスタックとして使います。JvmMathLangRootNodeやJvmMathLangNodeはTruffleで使うASTのノードです。ツリーでNumberExprのとき、つまり単なる数値のノードならTruffleでの数値ノードを生成します。

@NodeInfo(language = "JVMMATHLANG", description = "")
public class JvmMathLangRootNode extends RootNode {

    /** The function body that is executed, and specialized during execution. */
    private JvmMathLangNode bodyNode;

Truffleのノードには@NodeInfoアノテーションを付与します。ルートノードなので、実行対象のノードを持っています。

@TypeSystemReference(JvmMathLangTypes.class)
@NodeInfo(language = "JVMMATHLANG", description = "")
public abstract class JvmMathLangNode extends Node {

    public abstract Object executeGeneric(VirtualFrame frame);

すべてのノードの親クラスです。この四則演算言語が利用する型をJvmMathLangTypesを通じて指定します。

@TypeSystem({long.class, BigInteger.class, JvmMathLangFunction.class})
public abstract class JvmMathLangTypes {}

longとBigDecimal、それに独自定義のJvmMathLangFunctionを利用します。Functionについては省略します。

で、数値ノードです。

@NodeInfo(shortName = "const")
public class LongNode extends JvmMathLangNode {

    private final long value;

    public LongNode(long l) {
        this.value = l;
    }

    public long executeLong(VirtualFrame frame) {
        return this.value;
    }

    @Override
    public Object executeGeneric(VirtualFrame frame) {
        return this.value;
    }
}

これはlongのノードですが、BigDecimalもほぼ同様です。

同様に、演算子のノードも作成します。

@NodeInfo(shortName = "+")
public abstract class AddNode extends BinaryNode {

    @Specialization(rewriteOn = ArithmeticException.class)
    protected long add(long left, long right) {
        return Math.addExact(left, right);
    }

    @Specialization
    @TruffleBoundary
    protected BigDecimal add(BigDecimal left, BigDecimal right) {
        return left.add(right);
    }

    @Specialization
    @TruffleBoundary
    protected BigDecimal add(Object left, Object right) {
        BigDecimal l = left instanceof BigDecimal ? (BigDecimal) left : BigDecimal.valueOf((long) left);
        BigDecimal r = right instanceof BigDecimal ? (BigDecimal) right : BigDecimal.valueOf((long) right);
        return l.add(r);
    }
}

これは加算演算子のノードです。BinaryNodeを継承しています。

@NodeChildren({@NodeChild("leftNode"), @NodeChild("righrNode")})
public abstract class BinaryNode extends JvmMathLangNode {

演算子ノードはすべてBinaryNodeを継承します。@NodeChildrenの内容は、演算子であればその左右にそれぞれノードがあるので、左のノードと右のノードを処理メソッドの引数として受け取れるようにします。

さて、加算に戻ります。メソッドに@Specializationをつけています。これでTruffleから呼び出される処理としてマークしている感じです。JavadocではDefines a method of a node subclass to represent one specialization of an operation.とありました。

@TruffleBoundaryは境界となるメソッドにつけます。longのadd()にはありません。これは、longのadd()でArithmeticExceptionが起こった場合、フォールバックのような感じでまた違うadd()が呼び出されるようにするためです。long以外のadd()は境界なので、フォールバックせず通常の例外処理となります。

AddNodeは抽象クラスです(抽象メソッドはありませんが)。具象クラスはどこにあるかというと、これは自分では実装しません。TruffleDSL annotation processorが具象クラスを生成します。

減算、乗算、除算もほぼ同様です。

またMathParseTreeListenerに戻ります。

public class MathParseTreeListener extends MathBaseListener {
...
    @Override
    public void exitInfixExpr(MathParser.InfixExprContext ctx) {
        JvmMathLangNode right = mathLangNodes.pop();
        JvmMathLangNode left = mathLangNodes.pop();

        JvmMathLangNode current = null;
        switch (ctx.op.getType()) {
            case MathLexer.OP_ADD:
                current = nodes.ops.AddNodeGen.create(left, right);
                break;
            case MathLexer.OP_DIV:
                current = nodes.ops.DivNodeGen.create(left, right);
                break;
            case MathLexer.OP_MUL:
                current = nodes.ops.MulNodeGen.create(left, right);
                break;
            case MathLexer.OP_SUB:
                current = nodes.ops.SubNodeGen.create(left, right);
                break;
        }
        mathLangNodes.push(current);
    }
...
    public Map<String,JvmMathLangRootNode> getFunctions(JvmMathLang jvmMathLang) {
        functions.put("main", new JvmMathLangRootNode(jvmMathLang, new FrameDescriptor(), node, "main"));
        return functions;
    }
}

nodes.ops.AddNodeGenもannotation processorが生成するクラスです。このクラスを呼び出して、ノードを生成します。

最後にgetFunctions()を呼べば、全ノードを持つルートノードが取得できます。また言語クラスのparseに戻ります。

@TruffleLanguage.Registration(name = "JVMMATHLANG", version = "0.0.1", mimeType = JvmMathLang.MIME_TYPE)
public class JvmMathLang extends TruffleLanguage<JvmMathLangContext> {

    @Override
    protected CallTarget parse(ParsingRequest request) throws Exception {
        Map<String, JvmMathLangRootNode> functions = parseSource(request.getSource());

        JvmMathLangRootNode main = functions.get("main");

        JvmMathLangRootNode evalMain = new JvmMathLangRootNode(this, main.getFrameDescriptor(), main.getBodyNode(), "main");
        return Truffle.getRuntime().createCallTarget(evalMain);
    }

ルートノードを、TruffleランタイムのメソッドcreateCallTarget()に渡し、CallTargetを生成して戻り値として返せば完了です。

Truffle 0.25での変更点

Truffleは0.25で大きくAPIが変わったようです。Olegのプレゼンのコードのままではdeprecatedとなる部分があります。

  • com.oracle.truffle.api.ExecutionContextがdeprecatedになりました
  • TruffleLanguage#parse(Source code, Node context, String… argumentNames)がdeprecatedになりました

また実行時にエラーとなることもありました。

[ERROR] /Users/jyukutyo/code/jvmmathlang/src/main/java/nodes/ops/DivNode.java:[11,17] Element BinaryNode at annotation @NodeChild is erroneous: No generic execute method found with 0 evaluated arguments for node type JvmMathLangNode and frame types [com.oracle.truffle.api.frame.VirtualFrame, com.oracle.truffle.api.frame.Frame].

ノードに引数なしのexecuteGeneric()が必要なようです。JvmMathLangNodeクラスにメソッドを定義しました。

感想

プログラミング言語と言えるほどのものではないですが、文法に従ったテキストを解析して処理を実行するところまででき、非常に楽しかったです。ほとんどのことはANTLRとTruffleがやってくれるので、自分では何もしていないなと感じる部分もありますが…

文法をより作り込めば、メソッド呼び出しなどさまざまなことができるのだと思います(そこまでの意欲は今のところないですが…)。上述のsimplelanguageでさえ、内容は全然simpleじゃないですw

Graal/TruffleはJavaOneで聞いてからとてもおもしろそうに感じていました。少しだけ理解が深まってよかったです。今後も注目していきたいです。

*1:これらの製品はサムライズム社で! http://samuraism.com/products/zeroturnaround/jrebel

フリュー株式会社を退職します #退職エントリ

私ことじゅくちょーは、7月末でフリュー株式会社を退職することにしました。約5年半の在籍でした。 現在は有給消化中です。また、次の勤務先も決まっています。関西です!!

得たこと

アジャイル開発のチームリーダ

フリューではアジャイル開発がベースにあります。この場合のアジャイルが指す意味は、XP/スクラムといった具体的な手法というよりも、純粋に"態度"を指します。なので、プロジェクトの特性に合わせて、つまりは部署やチームごとに開発プロセスにかなり違いがあります。私の部署は入社時XPベースのプロセスを採用していました。

私はフリューに入るまで、客先常駐で典型的なウォーターフォール、マネジメントなどない!という開発を経験してきて、アジャイル開発に憧れ入社しました。もちろんアジャイルだからすべてがうまくいくわけではありません。ただ、自分たちでうまくいくようにプロセス自体を変えていける、まさに"アジャイルな態度"は望んでいたものでした。在籍中のほぼ全期間チームリーダで、"納期/品質/コスト間のバランスを取りつつよりうまくやるには"ということを考えて考えて、短いイテレーショントライアンドエラーしながらプロセスやプラクティスを導入/変更し続けました。

たとえば、チームではほとんどすべての実装でペアプログラミングペアプロ)にしました。すでに利用者が1000万人以上であり今後も長く運用開発するシステムと考えると、ペアプロでの知識共有やある程度のコードレベルが確保できることは重要と判断しました。

プラクティスという手段だけでなく、チームが目指すマインドの形成にも取り組みました。僕は"ビジネス観点と技術観点のトレードオフを考慮してバランスを取る"タイプのリーダでした。技術、ビジネスのどちらかを100%満たすのではなく(そうするともう片方の満足度がすごく低くなる)、どちらも70,80%満たすという傾向があります。企画者の要件をいかに早くリリースするかも重視しましたが、一方で以下のスライドのような技術的要素にも取り組んでいました。

www.slideshare.net

www.slideshare.net

上長も本当にすばらしい人でした。やりたいことに対して論理ある説明であればGO、そうでなければNOです。鋭い観点から指摘をもらうこともありました。いろいろなことにチャレンジさせてもらえました。常駐時代の悶々とした思いはなくなり、自分で考えて仕事をする楽しさがありました。

自社サービス運用開発

もう1つの目的が、事業会社で自社サービスに関わることでした。常駐時代はPGチームのリーダでした。そこにはPGに対しSEやプロパーといったポジションの差、稼働時間による売上とみながwin-winになりづらい環境があります。ある種の本質的でないことに神経をすり減らすのではなく、自社サービスであればサービスの売上が命題であり、より本質的なことに取り組めると考えたのです。

その考えは正しかったと感じています。もちろん難しさもありました。常駐時代は"敵"を見つけるのが簡単だったのです。SE、プロパー、仕様変更…自分と自社のメンバーを守るためにはねのけるだけでした。自社サービスには敵はいません。さまざまな考え方の個人、さまざまな目標を持つ組織間で協力して目標に近づいていく難しさです。

エンジニア以外とやり取りをしたことがない私は、"エンジニアとしか話せない。エンジニア以外の人とうまくやり取りできるはずがない。"と自分に対し思っていました。でも、そうではありませんでした。企画者からいい言葉をもらえることが何度もありました。やりやすい、難しい用語を使わず説明してれる、ただ単にできないとか難しいと言わず違う方法を提案してくれる、など…これは本当に自信になりました。自分では気づいてなかった能力があるのだ、と。

エンジニアでない企画者と、飲みニケーションのためではなく仲間として楽しく飲みに行くこともありました。渋谷出張時に飲みに行ってあんまりにも楽しくて、新幹線の終電に間に合わず、宿泊して妻に怒られる、なんてこともありました。

そう、自社サービス開発には敵はおらず、仲間全員がwin-winになることを目指すものです。サービスの売上が増えれば利益が増えるというモデルは、受注金額が決まっておりいかに開発費を減らすことで利益を増やすか、というモデルとは異なります。誰も抑圧されたり損をしたりすることがなく増やしていけるというのは、やはりよいものなのだと感じます。

国内/海外カンファレンスへの出張

フリューに来るまでは、カンファレンスは有給を取って自費で東京往復、宿泊するものでした。フリューでは業務で、出張で参加できました。記憶では行きたいと言ったものは全部行かせてもらえたと思います。私はJavaエンジニアですので、Java Day、JJUG CCC、デブサミ、Spring Day、ScalaMatsuriといったものです。

それどころか、会社がカンファレンススポンサーになっていきました。JJUG CCCは継続していますし、他でもなっていました。自分が所属する会社がスポンサー、そしてスポンサーセッションをするというのは、エンジニアに対する会社の見方にもつながっているように感じます。

そしてJavaOneに、サンフランシスコにも出張で行かせてもらいました。自分の人生を変えた出来事の1つです。このことで会社に何かを直接的に還元できたわけではないかもしれません。ですが、やはりエンジニアには必要と感じます。バックアップしてくれる企業が増えればいいなと思っています。

海外カンファレンスは人を変える力があります。

ではなぜ退職するのか

ここまで読んで、こんないい会社をじゃあなぜ退職するの?と疑問に思われるでしょう。要因は1つではなく、何かはっきりしたきっかけや理由があるわけではありません。当然ここにも書けることと書けないこともあります。ただ、"チャレンジしてみたい"というのが、集約した理由になりそうです。

フリューではとても高い評価をしてもらえました。過大なくらいと個人的に思うくらいでした。責任と権限を得ると同時に、チームを率いているがために自分が表出させたいものを自分で抑圧しているように感じられました。それを解放してみたい、目一杯やってみたいと思いが日に日に強くなりました。

自分の決断が、リスクのあることだということは理解しています。だからといってチャレンジしないままでいるということもできませんでした。

社員旅行

退職が決まっているにもかかわらず、先日社員旅行に参加しました。300名以上が参加した旅行ですが、多くの人に声をかけてもらいました。惜別の情を述べてくださった方、激励をくださった方…とてもうれしいことでした。こういう人たちと仕事を一緒にできたからこそ、この5年半で自分で実感できるくらいに成長できたんです!!!もうここで働けないことがとても寂しく感じました。

転職活動

上記のような理由でしたので、自分から積極的に活動しているわけではありませんでした。最終的にはいくつかの転職サイトやエージェントにも登録しましたが、今回はあまり効果がありませんでした。

知人が社員ということもあり、ビズリーチに経歴を登録してみました。ヘッドハンターから多くの連絡が来ましたが、企業から直接連絡いただくことも多く、それをきっかけにお話をうかがいに行くという風に進めました。いわゆるコピペではなく、私の経歴を読んでの文面であれば、ほぼすべての企業に返信しました。この方法は、今まで知らなかった企業と接点ができ、広く選択肢を持てたので、活動を終えるにあたり充実感と満足感があります。ただ、基本的に連絡を待つことになりますので、活動は長期となります。

カジュアル面談から始まることが多かったです。また、改めて履歴書が必要な企業もほぼありませんでした。

次の会社は

落ち着いたら、いろいろと公開します!

本当にどの企業も魅力的で、最後の最後まで悩みました。次は、今とは別の部分でとても魅力的な会社と感じています。不安もありますが、それは今のまま、そのままでいつつ新しい仕事をしようと考えているからだと言い聞かせています。今ある強みを活かしつつ、やはり適応、変化していかなくてはなりませんね。自分がどう変わるか楽しみなんだと思うことにします!!

Amazonほしいものリスト

載せてみます! amzn.asia