がんばるぞ

がんばります

DynamoDBに入門したい

そろそろDynamoDBを触らないといけなさそうな雰囲気があったので、土日に勉強してみました。 勉強した順にリンクを並べていく

そもそもNoSQLって何

そもそもNoSQLとはってところから説明してくれている。 NoSQLの使い所やNoSQLの種類についてざっくり学べる www.youtube.com

DynamoDBの概要

めちゃくちゃわかりやすくてよかった。 完全にDynamoDBを理解した気持ちになれた dev.classmethod.jp

Dynamo DBの基礎知識から始まり色々説明してくれておる

www.slideshare.net

データ設計まわり

デザインパターン的なのを紹介してくれている www.youtube.com

公式のベストプラクティス集
docs.aws.amazon.com

実際に動かしてみる

ローカル環境

Dockerでいける。npmでGUIも簡単に手に入る。

qiita.com

PHPから扱う場合はここらへんを見ると良い

docs.aws.amazon.com

来週DynamoDBを使って何かしらのアプリケーションを作るぞ〜

ロジックを書くときは抽象度を揃えるように気を付けている話

この記事はスターフェスティバル Advent Calendar 2021 の19日目です。

qiita.com


尽く会社と関係のない話ばっかりしていてアレですね。 抽象度揃ってる方がイイヨーみたいな話をレビューとかでたまにしているので、ちゃんと言語化しよーと思ったのでブログに書くことにしました。

自然言語で考える

抽象度を揃えるというとなんか難しそうな雰囲気がありますが、日常会話で無意識にやっていることをプログラムでもやってみようくらいの感覚です。

例えば今日の予定を家族に伝える時は「今日は友人とご飯に行ってくる」みたいなことを言うかと思いますが、こういうのが抽象度が揃った状態かなーと思います。

じゃあ抽象度が狂うとどうなるのかというと「2021年11月15日11時15分に家を出て11時30分発○○行きの電車に乗って○○駅で中学の頃から仲の良い○○くんと合流した後、東京都○○区○○1−2−3○○ビル6Fにある○○っていう中華料理屋に行ってエビチリを食べてくる」みたいな感じになります。たぶん。

これは抽象度が下がりまくった結果情報量が増えまくって文章が読みにくくなるパターンですね。

逆に抽象度を上げまくってみましょう。 「友人」は生き物として抽象化することができそうですね。「ご飯」はwikipediaによると料理を作ってくれた人の愛を実感するための行為でもあるらしいので、愛を実感する行為として抽象化しましょう。 「今日」は...わからないけど今年とかにしておきましょうか。

そうすると「今年中に生き物と愛を実感してくる」みたいになります。こいつ大丈夫か。

抽象化を繰り返すと情報が減っていくので、この場合は情報が減りすぎてもはや意味がわからなくなっているパターンですね。

日常生活では、伝えたい内容から必要な情報だけを取り出し(抽象化)、それを言語化するというのを無意識に行っているわけですね。たぶん。

めっちゃ極端な例を出してみましたが、プログラムだと意外と似たようなことをやってしまうことがあるんですね。

プログラムで考える

じゃあプログラムだとどうかというと、ユースケースのような手続きっぽくなりがちなクラスだとわかりやすいです。

例えば、会員登録みたいなユースケースを考えてみます。 これを言葉で説明すると 「メールアドレスの重複がないか確認して重複があればエラーを返し、重複がなければパスワードをハッシュ化したのち入力値から会員を作成する」みたいな感じになるのではないでしょうか

<?php

class RegisterUser
{
    public function run($input)
    {
        // メールアドレスの重複をチェック
        // 重複していたらエラーを返す
        // パスワードをハッシュ化する
        // 入力値から会員を作成する
    }
}

ですが、いざコードを書いてみるとこんなことになったりします。

<?php

class RegisterUser
{
    public function run($input)
    {
        $records = $this->database->table('users')
            ->where('email', '=', $input['email'])
            ->execute();

        if ($records !== []) {
            throw new ValidationException('メールアドレスが重複しています。');
        }

        $hashedPassword = password_hash($input['password'], PASSWORD_DEFAULT);

        $this->database->table('users')
            ->insert([
                'name' => $input['name'],
                'email' => $input['email'],
                'password' => $hashedPassword,
            ]);
    }
}

これを読んでみると「usersテーブルからメールアドレスが一致するレコードを取得し、レコードが空じゃなければエラーを返し、空であればパスワードをPHPのデフォルトアルゴリズム(bcrypt)でハッシュ化し、入力値からusersテーブルにレコードを追加する」のようになるので、最初の説明と随分かけ離れてしまいました。

先ほどの例の具体的になりすぎて情報量が増えて読みにくくなってるパターンと同じですね。

まぁこの程度であれば読めね〜!ってこともないのですが、アプリケーション全体がこのノリで書かれていると脳のメモリを圧迫されるのでしんどくなってきます。

例えば以下のコードはメールアドレスの重複をチェックする意図を持つロジックですが、作者の気持ちを考えなさいみたいな感じのコードなので、書いた人の意図をがんばって想像しながら「レコードが0件ではないという状況はメールアドレスが重複していることを示すのだ」と自力で翻訳する必要があり、このような翻訳が頻繁に必要になるとコードを読む負荷がグイングインと上がっていきます。

<?php
$records = $this->database->table('users')
    ->where('email', '=', $input['email'])
    ->execute();

if ($records !== []) {
    throw new ValidationException('メールアドレスが重複しています。');
}

このロジックでは「重複チェックをした上で何をするか」という行為に興味があるのであって「具体的にどのような方法で重複チェックをするのか」こういった情報は不要です。(個人的な意見です)
なのでこの場合はちゃんと必要な意味を抜き出し抽象化してあげるとよいと考えています。

<?php

class RegisterUser
{
    public function run($input)
    {
        if ($this->duplicationChecker->isDuplicated($input['email'])) {
            throw new ValidationException('メールアドレスが重複しています。');
        }

        // ...
    }
}

どうでしょうか、こうすれば意図が明確なので作者の気持ちを考えなさいというコードではなくなった上に、他の箇所でも同じロジックを使いまわせるのでDRYの観点からも望ましそうです。

とまぁこんな感じで、このロジックを説明する上で必要最低限の情報ってなんだろうみたいなところに注目してコードを書いていくと、抽象度が揃っていって可読性が上がったり関心の分離ができたりするのかなーと思っております

<?php

class RegisterUser
{
    public function run($input)
    {
        if ($this->duplicationChecker->isDuplicated($input['email'])) {
            throw new DuplicatedEmailException();
        }

        $hashedPassword = $this->passwordHasher->hash($input['password']);

        return new User(
            $input['name'],
            $input['email'],
            $hashedPassword
        );
    }
}

おわりに

抽象度を揃えることを個人的に気を付けている話をしましたが、みんなが気を付けていることも知りたいですね。教えて〜!

1ヶ月間姿勢が悪かったのでPureScriptの勉強をしているよという話をします

この記事はスターフェスティバル Advent Calendar 2021の5日目です。


姿勢

1ヶ月間良い姿勢で仕事した結果、腰痛が治るわ睡眠効率が改善するわ最高!という話をするつもりだったのだけど、この一ヶ月間全くもって姿勢が良くなかった。 一体何が悪かったのだろうか...。いや、悪かったのは姿勢なんだけど、なぜ良い姿勢を保つことができなかったのか。

終わってしまったことを悔やんでも仕方がないので、今日はなにか別の話をすることにします。最近PureScriptで関数指向プログラミングの勉強をし始めたのでそこらへんの話を。 腰が痛いな

ちなみにやっとコードが読めるようになってきたくらいで関数指向とはみたいなところはほとんど理解出来ていないし、モナド周りでめっちゃ苦戦してます。 背中も違和感があるな

PureScript とは?

AltJSの純粋関数型言語Haskellから多くの影響を受けているらしいです。 同じくAltJSで関数指向のプログラミングが出来るElmという言語もありますが、サーバーサイドで動作させることも可能で言語機能も充実しているという点がElmとは異なるようです。

なぜPureScriptなのか

HaskellScalaあたりも候補だったのですが、

  • spagoというコンパイラ&パッケージマネージャが使いやすそうだったこと
  • npmとnodejsがあればとりあえず始められること
  • どうせやるなら純粋関数型まで振り切ってしまった方がいい気がした

あたりを理由にPureScriptにしました。

環境構築周りの話

こちらの記事が大変参考になる上にめちゃくちゃいい感じに要約されているので、こちらを読むとわかりやすいです。 qiita.com

面白かったところたち

PureScriptの解説がちゃんとできるほど理解できてないので、勉強していておもしろーいってなったところをつらつら紹介したいと思います

デフォルトで部分適用される関数

例えば以下のコードは、PHPerであれば one という変数に 1 を代入していると読むと思うのですが、実はこれは1という値を返す関数の定義になります

one = 1

引数が必要な場合は以下のように書きます。

add x y = x + y

また、型の書き方もPHPとは全然違います。 先ほどの add 関数であれば、型は以下の通りです。

add :: Int -> Int -> Int

最初の Int が 第一引数の型で、2つ目の Int が第二引数の型で、最後の Int が返り値の型です。 なんで Int Int -> Int じゃないんだ?と僕は思いましたが、当然理由があって、PureScriptでは全ての関数は1つの引数だけ受け取るようになっているので、引数が二つある関数は実際には「関数を返す関数」なんですね。引数が3つある関数は「関数を返す関数を返す関数」です。

具体的には add 1 3 などのようにadd関数を実行すると、 まず add 1 の部分が評価された結果 Int -> Int という関数が返り、その関数に 3 が適用されることで最終的に 4 という数値が手に入る。みたいな感じです。

replなどでその様子を確かめるとわかりやすいです( :t は型を調べるコマンドです)

> :paste
… add :: Int -> Int -> Int
… add x y = x + y
… 
> :t add                  
Int -> Int -> Int

> :t add 1
Int -> Int

> :t add 1 3
Int

> (add 1) 3
4

ここらへんの挙動もPHPでは考えられない世界観で面白いですね。

無理やりPHPで表すとこんな感じでしょうか

<?php
$add = fn (int $x) => fn (int $y) => $x + $y;

$add(1)(3);

たぶんこれのおかげで関数の合成や組み合わせがしやすくなるとか再利用生が上がるとかなんか色々メリットがあるのだと思われです。わかりませんけど。

中置演算子

+ などの記号は実際のところただの関数で、型は forall (a :: Type). Semiring a => a -> a -> a です。 ただ、普通の関数と違い引数を記号の左右に書くことができます。こういったものを中置演算子と呼ぶらしいです。

普通の関数と同じように扱うためには () で囲みます。

> (+) 1 3
4

また、逆に普通の関数を `` で囲むことで中置演算子として書くことができます。

> 1 `add` 3
4

infix という機能を使うと、この中置演算子を自由に追加することができるようです。

> infixl 6 add as 🍎 
> 1 🍎 2
3

自由に演算子を追加できるという世界観もPHPには存在しないので面白いですね。

ただかなり気を付けないと可読性が悪化するだけになりそうなので、フレームワークを作るなどでない限り出番は無さそうな気がしています

パターンマッチ

PHPも最近はmatch式が導入されて便利ですが、パターンマッチはもっと便利です。

例えばEitherという成功(Right)あるいは失敗(Left)を表す型があるのですが、以下のような感じで成功時と失敗時の処理を分岐させることができます。 (関数のオーバーロードに少し似てますね)

data Err = ConnectionErr | Timeout

logError :: Either Err String -> Effect Unit
logError (Right _) = log "success"
logError (Left _) = log "error"

PHPで書くならこんな感じでしょうか

<?php

return match ($either::class) {
    Right::class => fn() => print 'success',
    Left::class => fn() => print 'error',
};

さらに、もっと細かく型を指定した書き方もできます。以下だとConnectionErrだった時だけ違うログメッセージ出すってかんじですね。

これくらいの分岐からPHPだと若干ごちゃついてきますが、PureScriptだととてもシンプルです。

logError (Right _) = log "success"
logError (Left ConnectionErr) = log "connection error"
logError (Left _) = log "error"

そして最高なのが、全てのパターンを網羅できていない場合はビルドできなくなることです。

PHPenumが入ったことだし、PHPStanとかを使えばEnumを使ったswitch文とかmatch式は全部のパターンを網羅できてないことを検知できるようにそのうちなりそうですね。もうなってるのかな

おわりに

ちょっと早い上にとりとめもない感じなのですが娘と寝る時間になったのでもう終わります。 純粋関数型言語の世界観はPHPとは全く違うので慣れるまで大変ですね。全体的な構文もそうですが、do記法とかモナドは意味がわからないし、関数合成も慣れないと挙動が想像しにくいです。

でもこれらを理解することで得られる学びにすごく興味があるので引き続きチビチビがんばっていきたいと思います。

あと来年こそは良い姿勢で過ごすことで体のコリを軽減し睡眠の質を改善したいです。 今年はもう諦めました。

買ってよかったもの2020

今年は色々と物を買った気がするので、買ってよかったものを振り返ってまとめようと思います。 トゥギャッターではリモートワーク手当として毎月3万円が支給される(!!!)ので、自宅の作業環境の改善による生産性の向上を口実に色々と利用させていただきました。(手当の使い方は自由なのだが、性格上気を抜くと貯金ばかりしてしまいそうだったので)

布団クリーナー

妻がハウスダストだかほこりだかのアレルギーなので購入しました。 実際に使ってみるとすごい。めっちゃホコリがとれる。 こんなホコリだらけの布団にくるまって寝てたんかい......って落ち込むくらいよく取れる。

僕は寝起きにくしゃみをよくしてしまうのだが、こいつを使うようにしてからくしゃみが激減した。 「寝起きはくしゃみが出るもの」程度に考えていたので、ホコリなどが原因だったことに初めて気がついた。妻さん、布団クリーナーを欲してくれてありがとう。

整腸薬

ヤクルトBL整腸薬 36包 [指定医薬部外品]

ヤクルトBL整腸薬 36包 [指定医薬部外品]

  • メディア: ヘルスケア&ケア用品

なんか妻が飲んでたので僕も試しに飲んでみたらアラ不思議。快便になる。 僕は基本便秘気味なのでたまに飲むようにするとこりゃあいいという感じ。リピ決定!!!!妻ありがとう。

冷凍餃子

妻にそそのかされるがまま買ってみると、ウマイっ!!!!!! 特に餃子鍋にすると良かった。 ご飯作るのダリ〜って時にとても便利なので、「俺のバックには大量の冷凍餃子がついてるから、いつご飯作るのダリ〜って思っても良いんだぜ」という心持ちになれるため日々のストレスが減った。 妻と冷凍餃子ありがとう。

カチューシャ

カチューシャはすごい!!!!!!!!!!!!!!!!!!!!いくら髪の毛が伸びても邪魔じゃない!!!!!!!!!!!!!!!!!!!! コロナで人と会うことも減ったし、髪の毛切るのめんどっち〜〜〜〜ってなっていたのでとても助かりました。

ちなみにこれは締め付けがきつい時はグイ〜〜〜て広げると広がってくれるところが良いです

断熱タンブラー

こいつにお湯/冷水を入れるとしっかり保温される。 朝に入れたコーヒーがお昼になっても暖かいままで感動した。 僕は基本的に水分をあまり摂取しない(というか忘れる)ので、仕事中などに「あっ、飲み物の存在忘れてた」となったときにこの保温力があるととても嬉しい。

サーキュレーター

これは衝撃の事実なのですが、僕の作業部屋にはエアコンがない。取り付けることができない。 コロナでリモートワーク化してから、この事実がシャレにならなくなったので藁にもすがる思いで購入。 隣の部屋の空気を作業部屋に送ることでなんとかなった! サーキュレーターありがとう!!!引っ越さなくて済んだ!!!

ウルトラワイドディスプレイ

トゥギャッターのリモート手当を2ヶ月ため込んで購入した。 こいつは94Wの給電もついてるのでMBPの充電ができる! さらにUSBポートもType-Cが1つとUSB3が2つ付いてるので、配線がめっちゃスッキリするという優れもの! 画面がでかいは正義!

Oura Ring

トゥギャッターのリモートワーク手当があまりに最高すぎるので、手当を有効活用して会社に貢献しなくてはと思い調べていたところ発見。 睡眠の質が上がれば生産性も上がるはず!!!!ということで睡眠トラッキングを始めました。 トラッキングをしてみたところ想像以上に睡眠の質がよくないことが発覚したため、諸々がんばって睡眠の質を改善した結果、集中力が以前より向上している...というか安定することが実感できました。

Oura Ringはいいぞ!

セパレートキーボード

肩こりが辛かったため購入。 普通のキーボードと比べて使用感がそこまで変わらず、肩こりが軽減されたのでかなり満足! 左右のキーボードをつなげるケーブルが少し短めなのがちょっと不満!

バロンチェア

腰痛になりつつあったのでオフィスバスターで2万程度で購入。安い!ありがとう!!!!!! 腰がめっっっっっっっっちゃくちゃ楽になったので高い椅子はすごい!!!!!ってなりました。

いかがでしたか?

いかがでしたか?

コンピュータシステムの理論と実装 Chap3

www.amazon.co.jp

これの三章です。

三章の概要

この章では順序回路について学び、フリップフロップレジスタ、メモリなどを実装する。

順序回路

順序回路とは時間に依存した回路のこと。
1章、2章で実装した回路は時間に依存しておらず、組み合わせ回路と呼ばれる。

クロック

時間の経過をモデル化したやつ
こいつが各回路に信号(タイムユニット)を送り続けることによって、時間に依存した回路を実装することができる。

フリップフロップ

順序回路の中で最も基本的な回路。
いくつかある種類のうち、本書では D型フリップフロップ(DFF) を採用している。
こいつのおかげで回路の同期的な計算が可能となる

DFF は 1bit の入力とクロック入力という2つの入力と1bitの出力があり、振る舞いとしては一つ前のタイムユニットの入力を出力するだけの回路である
この回路を元にさまざまな記憶素子を実装していく

レジスタ

データを格納したり読み出したりすることができる記憶装置
多ビットを記憶できるレジスタが持つ値はワードと呼ばれる。

メモリ(RAM)

任意の長さのワードを記憶できる装置。
物理的な記憶位置に関係なく、同じ時間で直接値にアクセスできる

カウンタ

タイムユニットが進むごとに任意の整数が加算されていく回路。
任意の整数は 1 が用いられることが多い。

順序回路の実装

2章までに実装した回路と予め用意されているフリップフロップ回路を用いて、レジスタ・メモリ・カウンタなどの順路回路を実装する。

以降は僕が実装したコードが出てくるので、ネタバレされたくない人は読まない方がよいです

1bitレジスタ

in と 制御ビットの load の2つを入力として受け取り、 load が1の場合に保存された値を読み込み、 load が 0 の場合に in の値を書き込めばOK

Mux(a=loop, b=in, sel=load, out=mux);
DFF(in=mux, out=out, out=loop);

16bitレジスタ

1bitレジスタの16bit版。
なので、1bitレジスタを16個並べるだけで良さそうだったのでそうした

Bit(in=in[0], out=out[0], load=load);
...
Bit(in=in[15], out=out[15], load=load);

RAM(8|64|512|4K|16K)

inload に加えて address の3つの入力を受け取り、 address の値によって読み書きするレジスタを選択出来ればOK。

各16bitレジスタの出力を Multiplexor で振り分ければいけそうだったのでそうした。 また、指定されたレジスタ以外に書き込みが発生するとまずいので、 入力の loadDemultiplexor で振り分けてる

以下のコードは RAM8 だけど、それ以外のRAMも同じようなロジックでいける。

DMux8Way(in=load, sel=address, a=load1, b=load2, c=load3, d=load4, e=load5, f=load6, g=load7, h=load8);
Register(in=in, out=out1, load=load1);
Register(in=in, out=out2, load=load2);
Register(in=in, out=out3, load=load3);
Register(in=in, out=out4, load=load4);
Register(in=in, out=out5, load=load5);
Register(in=in, out=out6, load=load6);
Register(in=in, out=out7, load=load7);
Register(in=in, out=out8, load=load8);
Mux8Way16(a=out1, b=out2, c=out3, d=out4, e=out5, f=out6, g=out7, h=out8, sel=address, out=out);

カウンタ

in と制御ビットである inc , load , reset の4つの入力を受け取り、 制御ビットに合わせて動作を変えればOK
inc が 1 の場合は格納されている値をインクリメントし、 load が 1 の場合は格納されている値を in で上書きし、 reset が 1 の場合は格納している値を 0 にする。
値の格納にはレジスタを使う

ALU と同じような実装で良さそうだったのでそうした。

// inc
Inc16(in=loop, out=incremented);
Mux16(a=loop, b=incremented, sel=inc, out=in1);

// load
Mux16(a=in1, b=in, sel=load, out=in2);

// reset
Mux16(a=in2, b[0..15]=false, sel=reset, out=in3);

Register(in=in3, load=true, out=out, out=loop);

感想

うお〜メモリを実装したぞ〜〜

コンピュータシステムの理論と実装 Chap2

www.amazon.co.jp

前回に引き続きこれです

2章概要

2進数、符号付き2進数、2の補数、ブール算術など理解し、加算器・算術論理演算機を実装する

2進数

0と1のみを使って数を表現するやつ

符号付き2進数

正の数と負の数を表現できる2進数

2の補数

符号付き2進数を実現するために使われているやつ。
最上位bitの状態で正か負かを判断できる。
補数を求めるための数式は 2**n - x ※ n= bit数

たとえば 8bitで -5 を表す場合は 2**8 - 5 = 256 -5 = 251 となり、2進数に直すと 11111011 となる
これに8bitの5である 00000101 を加算してみると 00000000 となり (繰り上がった9bit目はオーバーフローで無視) 答えが0になっているので補数であることが確認できる。

符号付き2進数において2の補数での表現を採用するメリットは、減算を加算と同じ手順で計算できるためシンプルな実装になることらしい。
たとえば4bitの 3-2 であれば 0011 + 1110 となり、計算結果は 0001 (繰り上がった5bit目は無視) なので、正しく計算できていることがわかる。

加算器とALUの実装

1章で実装した論理ゲートを用いて、いくつかの加算器と算術論理演算機(ALU=Arithmetic Logical Unit)を実装する。

以降は僕が実装したコードが出てくるので、ネタバレされたくない人は読まない方がよいです

半加算器

2つの入力の和を出力すればOK。
出力パターンは 00, 01, 10 の3つ

最下位ビットは2つの入力がそれぞれ 0 と 1 だった場合にのみ 1 を出力し、それ以外は 0 を返せばよく、これは Xor の挙動と同じなので Xor を使い、
最上位ビット(=繰り上がり) は2つの入力が両方とも 1 だった場合にのみ 1 を出力すればいいので And を使った。

Xor(a=a, b=b, out=sum);
And(a=a, b=b, out=carry);

全加算器

3つの入力の和を出力すればOK。
出力パターンは 00, 01, 10, 11 の4つ

半加算器を2つ使用して最下位bitの和を求めて、どちらかで繰り上がりが発生していた場合に最上位bitに1を出力すれば良さそうと思ったのでそうした

HalfAdder(a=a, b=b, sum=tmp, carry=carry1);
HalfAdder(a=c, b=tmp, sum=sum, carry=carry2);
Or(a=carry1, b=carry2, out=carry);

16bit加算器

2つの16bitの入力の和を出力すればOK。
オーバーフローは検出しない

半加算器と全加算器を並べるだけで良さそうだったのでそうした

HalfAdder(a=a[0], b=b[0], sum=out[0], carry=carry1);
FullAdder(a=a[1], b=b[1], c=carry1, sum=out[1], carry=carry2);
...
FullAdder(a=a[14], b=b[14], c=carry14, sum=out[14], carry=carry15);
FullAdder(a=a[15], b=b[15], c=carry15, sum=out[15]);

インクリメンタ

与えられた16bitの入力に1を加算すればOK

先ほどの16bit加算器を使うと楽そうだったのでそうした

Add16(a=in, b[0]=true, b[1..15]=false, out=out);

ALU

2つの16bitの入力といくつかの制御ビットをもとに計算する。
出力は計算結果の out , out が 0 であるかどうかを表す zr , outが負の数であるかどうかを表す ng の3つ。

制御ビット 説明
zx 入力 x をゼロにする
nx 入力 x を反転する
zy 入力 y をゼロにする
ny 入力 y を反転する
f True であれば加算、 False であれば And演算 する
no 出力を反転する

If文使いてえ〜と思いながら Multiplexor を使ってそれぞれの制御ビットを適用した計算結果を選択していく感じで実装した。

out は制御ビットを適用したあとの数値をそのまま出力し、
zr は out の全てのbit が 0 だった場合に 1 を出力すればよいため Or8Way (8bitの中に1つでも 1 があれば 1 を出力する論理ゲート) を2つ使い
ng は最上位ビットが 1 であれば 1 を出力し、そうでなければ 0 を出力すればいいため、 out の15bit目をそのまま出力した

変数名が無になって辛い...。

// zx, nx
Mux16(a=x, b[0..15]=false, sel=zx, out=x1);
Not16(in=x1, out=x2);
Mux16(a=x1, b=x2, sel=nx, out=x3);

// zy, ny
Mux16(a=y, b[0..15]=false, sel=zy, out=y1);
Not16(in=y1, out=y2);
Mux16(a=y1, b=y2, sel=ny, out=y3);

// f
Add16(a=x3, b=y3, out=xAddY);
And16(a=x3, b=y3, out=xAndY);
Mux16(a=xAndY, b=xAddY, sel=f, out=f1);

// no
Not16(in=f1, out=notF1);
Mux16(a=f1, b=notF1, sel=no, out=out, out[0..7]=out0to7, out[8..15]=out8to15, out[15]=ng);

Or8Way(in=out0to7, out=zr1);
Or8Way(in=out8to15, out=zr2);
Or(a=zr1, b=zr2, out=zr3);
Not(in=zr3, out=zr);

感想

ALUはもうちょっと良い実装があるんだろうなーと思ったけど特に浮かばなかったのが残念。
模範解答的なやつがあれば見てみたいなー