絞り込み

最新の投稿

プログラミング Polygon
プログラミング

UITextViewでキーボードがカーソルを隠さないようにする明快な方法

内容霽月です。Swiftちょっと分かる人向けの記事です。 Appleのメモ帳アプリなどでは、改行しまくってキーボードに迫ったとしても、キーボードの下にカーソルが隠れることなく、自動的にスクロールされることで、カーソルが常に見える位置に存在するようになっていますよね。 その機能を再現する方法についてネットで少し調べてみましたが、あまり良い解決策は見つかりませんでした。ということで、この記事では、単純で分かりやすくその機能を実現する方法について紹介します。 環境macOS Ventura 13.2.1シミュレータ: iPhone 14 Pro(iOS16.2)Xcode14.2Swift5.7.2 UITextViewを配置まずはUITextViewを画面に配置します。import UIKit class ViewController: UIViewController {     private let textView = UITextView()          override func viewDidLoad() {         super.viewDidLoad()                  view.addSubview(textView)         textView.frame = view.bounds         textView.text = String(repeating: "これは UITextView を使ったテストです。\n", count: 100)     } }実行すると適当な文章が100行表示されます。 キーボードの高さを取得次に、キーボードが開かれた時に、キーボードの高さを取得するようにします。 保存先の変数を作って、    private var keyboardHeight: Double = 0.0以下をviewDidLoadの中に書いて、キーボードが表示された時にメソッドが呼び出されるように登録し、        NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillShow(_:)),             name: UIResponder.keyboardWillShowNotification,             object: nil)キーボードの高さを取得する処理を書きます。    @objc func keyboardWillShow(_ notification: Notification) {         guard let userInfo = notification.userInfo else { return }         guard let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }         keyboardHeight = keyboardFrame.height     }実行してエラーがないことを確認して、次の段階に進みます。 textViewDidChange関数textViewに変更が加えられた時に呼び出される関数を作ります。 textViewのdelegateをselfにしておいて、        textView.delegate = selftextViewDidChange関数の中に書いていきます。extension ViewController: UITextViewDelegate {     func textViewDidChange(_ textView: UITextView) {              } } 目的の機能を作成必要な情報は「現在のスクロール具合」「カーソルがある矩形領域」「キーボードが開いてても見える矩形領域」です。この3つの情報を取得します。        var contentOffset = textView.contentOffset         let cursorPosition = textView.selectedTextRange?.start ?? textView.beginningOfDocument         var cursorRect = textView.caretRect(for: cursorPosition)         let visibleRect = CGRect(x: 0, y: 0, width: textView.bounds.width, height: textView.bounds.height - keyboardHeight)         cursorRect.origin.y -= contentOffset.y順に「contentOffset」「cursorRect」「visibleRect」という名前で取得しました。 カーソルの下端がvisibleRectの下端より下に行かないようにします。        if cursorRect.maxY > visibleRect.maxY {             contentOffset.y += cursorRect.maxY - visibleRect.maxY         }最後にcontentOffsetの変更を保存して終わりです。        textView.setContentOffset(contentOffset, animated: true) 実行して確認問題なく実行できました。カーソルが見えない場所に行ってしまう現象は解決され、とても便利なUITextViewの完成です! と、思いきや? textVIewのコンテンツの下端に余白がないために、下の方を編集しようとすると物凄くやりにくいことになってしまいました。 キーボードが開いている時は下端にキーボードの高さ分の余白を開けるようにしてみましょう。 keyboardWillShowに以下を追加します。        textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)         textViewDidChange(textView)キーボードが閉じた時の通知も登録して、        NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillHide(_:)),             name: UIResponder.keyboardWillHideNotification,             object: nil)キーボードが閉じた時に余白を元に戻す処理を書きます。    @objc func keyboardWillHide(_ notification: Notification) {         textView.textContainerInset = UIEdgeInsets.zero     }これで完璧です。 最終的なコードの全文import UIKit class ViewController: UIViewController {     private let textView = UITextView()          private var keyboardHeight: Double = 0.0          override func viewDidLoad() {         super.viewDidLoad()                  view.addSubview(textView)         textView.frame = view.bounds         textView.delegate = self         textView.text = String(repeating: "これは UITextView を使ったテストです。\n", count: 100)                  NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillShow(_:)),             name: UIResponder.keyboardWillShowNotification,             object: nil)         NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillHide(_:)),             name: UIResponder.keyboardWillHideNotification,             object: nil)     }          @objc func keyboardWillShow(_ notification: Notification) {         guard let userInfo = notification.userInfo else { return }         guard let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }         keyboardHeight = keyboardFrame.height                  textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)         textViewDidChange(textView)     }          @objc func keyboardWillHide(_ notification: Notification) {         textView.textContainerInset = UIEdgeInsets.zero     } } extension ViewController: UITextViewDelegate {     func textViewDidChange(_ textView: UITextView) {         var contentOffset = textView.contentOffset         let cursorPosition = textView.selectedTextRange?.start ?? textView.beginningOfDocument         var cursorRect = textView.caretRect(for: cursorPosition)         let visibleRect = CGRect(x: 0, y: 0, width: textView.bounds.width, height: textView.bounds.height - keyboardHeight)         cursorRect.origin.y -= contentOffset.y         if cursorRect.maxY > visibleRect.maxY {             contentOffset.y += cursorRect.maxY - visibleRect.maxY         }         textView.setContentOffset(contentOffset, animated: true)     } }

> 内容を見る

プログラミング Polygon
プログラミング

自作AIをAndroidにのっける

動機近年、人工知能を利用したサービスが増えている。 例えばChat GPT。言語処理をしてくれるAIだが非常に精度がいい。APIも公開されており、気軽にサービスに組み込むことができる。他にも様々なAIが公開されており、人工知能を用いたサービスというのは珍しく無くなってきた。 しかし、提供されたAPIを叩いただけでは人工知能を使った感が薄く感じる。 自分でAIを用意してサービスに組み込んでみたいと思う人は少なくないはず。 自分でサーバーを用意してAPIを公開してもいいが、サーバーの維持費、通信費用などがかかってしまう。端末上で動かすことができればこの問題も解決できる。 そう思い探してみたところ、「Tensorflow Lite」に行き着いた。そこで、実際にこのライブラリを用いてAndroidアプリを作ってみたい。 Tensorflow Liteとは機械学習を行うためのライブラリとして「Tensorflow」というものがある。比較的自由に機械学習を行えるため世界中で愛されているが、モバイル端末上で実行するには少し重たすぎる。 そこでTensorflow Liteである。このライブラリを使うことでモバイル端末上(iOS、Android、Raspberry pi等)で利用できるくらいに軽量化される。 同じようにモバイル端末上で人工知能を利用するためのライブラリとして「ML Kit」というものも存在するが、これだと自分で作成したモデルを利用できない。その点ではこちらの方が(手間はかかるが)自由度が高い。 使い方下の方で小難しく書いているが使い方はものすごく簡単である。 1. tfliteモデルを用意するtfliteとはTensorflow Lite専用の型。 自分で作ってもいいし、ネットから拾ってきてもいい。ここでみんな配布してるのですぐ手に入る。 2. Androidにぶち込むimport機能を使って指定の場所にtfliteモデルを入れる。 3. 使う入れればAndroid側が勝手にモデル用のクラスを作ってくれる。だからその関数を叩けばいい 例えば以下のコード。 mnistModel.process(target.toTensorBuffer()) // これを叩くだけで識別結果が返ってくる。 このmnistModelというのが用意されたモデル用クラス(のインスタンス)。ここに生えているprocess関数に入力値を渡してやれば出力が得られる。 これだけで完成。 ものすごく簡単にできることがわかると思う。 アプリ作成実際にAndroidアプリを作ってみる。 作ったアプリは以下の通り。 数字を手書きしてボタンを押せば人工知能で識別してくれるアプリ。 返送速度もいい感じ(これはエミュレーターだが、実機でやればもう少し早くなる)。 こんな感じのアプリを作成していく 1.モデルの作成まずは人工知能のモデルづくりから始める。 Tensorflow Liteでは.tfliteという独自の拡張子のモデルを用意する必要がある。これには以下の方法が存在する。 1. Tensorflow Hubから有志が作成したモデルを取得する 2. TFLite Model Makerを使って事前学習済みのモデルを学習させて取得する 3. Tensorflowでモデルを作成してそれをtflite形式に変換する 今回は3の方式で行う。 ここからはPythonで記述する。 まずはTensorflowでモデルを作成する。  # モデル構造の設定  model = tf.keras.models.Sequential([   tf.keras.layers.Conv2D(48, kernel_size = (4, 4), activation="relu", input_shape=(28,28,1)),   tf.keras.layers.BatchNormalization(),   tf.keras.layers.Conv2D(96, kernel_size = (4, 4), activation="relu"),   tf.keras.layers.BatchNormalization(),   tf.keras.layers.Flatten(),   tf.keras.layers.Dense(200),   tf.keras.layers.BatchNormalization(),   tf.keras.layers.Dense(10),  ])    loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)  model.compile(optimizer='adam',         loss=loss_fn,         metrics=['accuracy'])  model.fit(train_input, train_label, epochs=5) このモデルにMnistのデータセットを入れて学習させたところ、識別率98.84%になった。 これを一度SavedModel形式に変換する。  model.save("mnist_model") そこからconverterを用いてtflite形式に変換する  converter = tf.lite.TFLiteConverter.from_saved_model("mnist_model")  tflite_model = converter.convert()  with open("mnist_model.tflite", 'wb') as o_:    o_.write(tflite_model) tfliteに変換することで、モバイル端末でも問題なく動作するように軽量化が行われる。 ただし、軽量化する中で精度が低下することがある。 そこで、以下のコードで精度を計測する。 # tflite形式のモデルのインスタンス化 interpreter = tf.lite.Interpreter("MnistModel.tflite") interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() # 識別結果が正しい個数 correct_num = 0 for i in range(len(test_input)): # テストデータを入力の型に合わせる input = test_input[i].reshape(input_details[0]['shape']).astype(np.float32) interpreter.set_tensor(input_details[0]['index'], input) interpreter.invoke() preds_tf_lite = interpreter.get_tensor(output_details[0]['index']) # 正答率を計算 correct_num += np.argmax(preds_tf_lite) == test_label[i] print(f"accuracy: {correct_num / len(test_input)}") 識別率は98.84%で、低下は見受けられなかった。 こうしてモデルを用意できた。 詳細なコードは以下に示す。 https://colab.research.google.com/drive/18J0DxrKAMGnMvu0C2AYZlqxREkglFfNS?usp=sharing 2. アプリ部分の作成次にアプリの部分を作成する。 様々な記事を見てみると、識別部分まで全てActivityに記述しているものが散見された。 お試しで使う分にはこれで十分ではあるが、今回は普通にアプリに組み込んでみて問題なく使えるかをみてみたい。 そこで以下のアーキテクチャに沿って開発をした。 TFLiteModelは技術的要素が多く、バックエンドよりの処理であるためRepository層に押し込んでいる。 また、Bitmap型はAndroid独特の型である。今回はビジネスロジックが特定の技術に依存するのもどうかと思い一度Imageに変換している。ただし、その場合TensorBufferやTensorImageへの変換に苦労することになる。 TFLiteではBitmap -> TensorImageへの変換関数も用意してくれているため、Bitmapに依存するように書いても十分問題ないと思われる。 後は(TFLiteModel以外の部分を)この仕様書に合わせて頑張って実装する。 3. モデルとアプリの接続まずライブラリの依存関係を記述する。 version catalogに以下を記述。  # Tensorflow Lite  tensorflow-lite = { group = "org.tensorflow", name = "tensorflow-lite", version.ref = "tensorflow-lite" }  tensorflow-lite-support = { group = "org.tensorflow", name = "tensorflow-lite-support", version.ref = "tensorflow-lite-support" }  tensorflow-lite-metadata = { group = "org.tensorflow", name = "tensorflow-lite-metadata", version.ref = "tensorflow-lite-support" } tensorflow-lite-gpuを導入することでGPUを使用することもできるようになるが、今回は省いている。 次にModelをAndroidのライブラリに入れていく。 以下の画像のように選択。 クリックすると以下のようなモーダルが出てくるため記述。 するとmlファイルが出現し、ここに格納される。 こちらのファイルを見ると、使い方が書かれている。 こちらに従ってコードを記述していく。 まずはModelのインスタンスの作成。 Dagger Hiltを使ってDIできるようにしておく。  @Module  @InstallIn(SingletonComponent::class)  class ModelProvider {    @Provides    fun providesMnistModel(      @ApplicationContext context: Context,    ): MnistModel = MnistModel.newInstance(context)  } その後、Repositoryに以下を記述する。    override suspend fun classify(         target: Image,     ): ApiResult<ClassifyResult, DomainException> = withContext(dispatcher) {         mnistModel             .process(target.toTensorBuffer())             .outputFeature0AsTensorBuffer             .floatArray             .let { buffer ->                 ClassifyResult(                     zero = buffer[0],                     one = buffer[1],                     two = buffer[2],                     three = buffer[3],                     four = buffer[4],                     five = buffer[5],                     six = buffer[6],                     seven = buffer[7],                     eight = buffer[8],                     nine = buffer[9],                 )             }.let { ApiResult.Success(it) }     }          private fun Image.toTensorBuffer() = TensorBuffer         .createFixedSize(intArrayOf(1, 28, 28), DataType.FLOAT32)         .also { buffer -> buffer.loadArray(this.pixels.toFloatArray()) } ぱっと見色々書いているように見えるが、識別自体はprocess関数を叩くだけでできており、非常に手軽である。 mnistModel.process(target.toTensorBuffer()) // これを叩くだけで識別結果が返ってくる。 ただデータの変換だけめんどくさい。実際残りのコードは全てデータの変換である。 Modelにmetadataを付与することでここら辺の記述はもう少し簡単にできるが、今回はこのまま行った。 以上で完成。結構簡単にできる。 完成したコードはこちら https://github.com/Wansuko-cmd/mnist-application 感想モデル作るのはそこまで難しくない。最悪既存やつ使えばいいアプリとの接続は面倒。入力されたものを頑張ってBufferに変換していく必要がある。(これはTensorImageを使えば解決できそう)モデルがでかい。今回のモデルサイズは37.5MB。一応Firebaseと連携させることで初期インストールしなくてもよくなるが、それでもでかい自分の作ったモデルをアプリに載せるのは結構楽しい気になった人は是非Androidを使って組んでみてほしい。公式チュートリアルに従ってやれば一日でできるようになる。

> 内容を見る

プログラミング Polygon
プログラミング

懐かしの高校物理をPythonで解こう ~ただし空気抵抗は考えるものとする~

はじめにこんにちは、ごまです! つい最近、Python で微分方程式を解いたり、グラフを描画することを可能にするライブラリがあることを知りました。 自分の知る限りでは、微分方程式を解くためには SymPy というライブラリが有効なようなのですが、正直なところあまり使ったことがありません。 そのため、SymPyの使い方を習得するためにも、今回はこのライブラリと Python を用いて、高校物理の問題を、それも (高校物理では殆どの場合考慮されなかった) 空気抵抗の考慮をしつつ解いてみようと思います。環境Windows 11Python 3.9.7SymPy 1.11.1SymPyについて軽く説明と実演本格的に演習に入る前に、SymPyについての軽い説明と、いくつかの実演を行います。 今回使用するライブラリであるSymPyは、簡単に言うと数式の処理や計算を行えるPythonのライブラリです。 これを用いることで、Pythonを用いての微分積分や因数分解などの様々な数式の処理が可能になります。 前置きはここまでとして、実際にSymPyを使ってみます。本稿ではこれ以降、SymPyをsymとしてimportします。import sympy as sym 因数分解まずは因数分解をやってみましょう。 SymPyで$x$や $y$ などの変数を扱う際は、次のようにsympy.symbols()を用いて定義を行います。x,y = sym.symbols('x,y') もちろん1文字ずつ定義を行うことも可能です。 因数分解はsympy.factor()の引数として、因数分解したい数式を渡すことによって行えます。 今回は $2x^{4}-9x^{3}-x^{2}-9x+2$という数式を因数分解してみます。 具体的には次のように記述します。sym.factor(2*x**4 -9*x**3 -x**2 -9*x +2) 少し記法が分かりづらいですが、この場合の*(アスタリスク)一つは乗算演算子、二つはべき乗演算子です。 例えば $a \times b$ はa*b、 $a^{b}$ はa**bと表現されます。 因数分解の結果は次のようになりました。 どうやら無事に因数分解できたようです。 これも記法が分かりづらいですが、SymPy はsympy.latex() を用いることで、数式を LaTeX の記法で出力してくれます。 そのため次のような関数 output() を定義することで、以降の結果を LaTeX の記法で出力することとします。def output(ans):     print("\n",sym.latex(ans),"\n") 実際に先程の計算を引数にして output() を実行すると次のような出力が得られました。 したがって結果は $ \left(x^{2} - 5 x + 1\right) \left(2 x^{2} + x + 2\right) $ となり、やはり問題なく因数分解できていることがわかります。 微分積分微分積分についても扱っておきます。 まず微分ですが、sympy.diff()を用いることで導関数を求めることができます。 例えば、 $x^{3}$ の $x$ に関しての1階微分は次のように書くことができ、結果として $3x^{2}$ が出力されます。output(sym.diff(x**3,x)) $n$ 回微分をしたければ、 $n$ の数だけ変数を渡すか、変数のあとに $n$ を渡すことによって行えます。 例えば、 $x^{3}$ の $x$ に関しての2階微分は次のように書くことができ、結果としてどちらも $6x$ が出力されます。output(sym.diff(x**3,x,x)) output(sym.diff(x**3,x,2)) 次に積分について説明します。不定積分を行う際はsym.integrate()関数に式と変数を与えればよいです。 例えば、 $6x$ を $x$ について不定積分しようとする場合は次のように書くことができ、結果として $3x^{2}$ が出力されます。output(sym.integrate(6*x)) 積分定数が含まれていませんが、これは微分方程式を解く場合には問題ありません。 定積分については、定積分の下限と上限を与えることで計算が行なえます。 次の計算では結果として48を得ます。output(sym.integrate(6*x,(x,3,5))) 微分方程式今回の記事で最も重要な、微分方程式の計算について説明します。 まず次のように、変数 $x$ に加えて、未定義の関数$f$を宣言します。 関数の宣言はsympy.Function()で行います。x = sym.symbols('x') f = sym.Function('f') そして、関数 $f(x)$ についての微分方程式を次のように宣言します。 今回解く微分方程式は $$\frac{d^{2}}{dx^{2}}f(x)-\frac{d}{dx}f(x)-2f(x)=0$$ です。 eq = sym.Eq(f(x).diff(x, x) - f(x).diff(x) - 2*f(x), 0) 方程式の定義はsympy.Eq()で行います。第一引数は方程式の左辺、第二引数は方程式の右辺です。 右辺は今回は0に設定していますが、勿論他の引数を渡すこともできます( $e^x$ とか)。 そして、微分方程式はsympy.dsolve()関数に先程宣言した方程式eqを引数として渡すことにより解くことができます。output(sym.dsolve(eq, f(x))) その結果、 $$f{\left(x \right)} = C{1} e^{- x} + C{2} e^{2 x} $$ が出力されました。 今回は不定積分のときと異なり、適切に任意定数 $C{1},C{2}$ が用いられています。 本稿ではこれを用いて、運動方程式の解を求めていきます。 今回扱う問題今回は河合出版の「物理のエッセンス 四訂版 力学・波動」の中から、簡単な力学の問題を抜粋して解いていきます。 比較のため、それぞれの問題に対して「空気抵抗ナシ」「空気抵抗アリ」の両方のパターンの解答の流れを示します。 演習放物運動問題質量$m$の質点を床から初速$v_{0}$で角度$\theta$の方向に投げた場合,最高点に達するまでの時間$t_{1}$と最高点の高さ$h$を求めよ。水平到達距離$x$を求めよ。解答(空気抵抗ナシ)1. 水平方向に $x$ 軸、鉛直方向に $y$ 軸を取ることを考えると、鉛直方向の運動方程式は $$m\frac{d^{2}y}{dt^{2}}=-mg$$ となります。 両辺を $m$ で割り $g$ を移項すると $$\frac{d^{2}y}{dt^{2}}+g=0$$ となるため、これをSymPyで記述するとeq1_0 = sym.Eq(y(t).diff(t, 2)+g) これを微分方程式としてsympy.dsolve()を用いて解く際、eq1_2 = sym.dsolve(eq1_0, y(t)) と記述することができ、この解は $$   y{\left(t \right)} = C_{1} + C_{2} t - \frac{g t^{2}}{2} $$ となリます。 実は、SymPyでは微分方程式を解く際に初期条件を課すことができ、ics={}の形で記述できます。 今回、 $y(0)=0$ 、 $\frac{dy}{dt}(0)=v_{0}\sin\theta$ ですから、先程の式はeq1_2 = sym.dsolve(eq1_0, y(t), ics={y(0): 0, y(t).diff(t, 1).subs(t, 0): v0*sym.sin(theta)}) と書き直すことができます。subs()関数は代入を行っていると考えていただければ大丈夫です。 $y(t)$ の1階微分に $t=0$ を代入したとき、その値が $v_{0}\sin\theta$ であるということを表しています( $v_{0}$ と $\theta$ は新たにsympy.symbolsで定義しました)。 そうして改めて微分方程式を解くと、解として $$   y{\left(t \right)} = - \frac{g t^{2}}{2} + t v_{0} \sin{\left(\theta \right)} $$ が得られます。 したがって、これの両辺を $t$ について微分した式は $$ \frac{dy}{dt}=-gt+v_{0}\sin\theta $$ ですから、 $t=t_{1}$ のとき、すなわち最高点における $y$ 方向の速さが0であることから、この式の左辺を0とおいた式を$t$ について次のように解きます。t_1 = sym.solve(eq1_1, t) これによって、解が次のように出力されます。 したがって、 $$   t_{1}= \left[ \frac{v_{0} \sin{\left(\theta \right)}}{g}\right] $$ ということがわかります。 最高点 $h$ を求めるには、この $t_1$ を(4)式に代入すればいいわけですから、h = eq1_2.subs(t, ((v0*sym.sin(theta)/g))) このように記述してやることで、値として $$   y{\left(\frac{v_{0} \sin{\left(\theta \right)}}{g} \right)} = \frac{v_{0}^{2} \sin^{2}{\left(\theta \right)}}{2 g} $$ が得られます。これが$h$ですから、書き直すと $$ h = \frac{v_{0}^{2} \sin^{2}{\left(\theta \right)}}{2 g} $$ となり、少々不格好ですが無事に小問1が解けたことになります。 2.投げ出されてから落下するまでの時間を $t_{2}$ とすると$t=t_{2}$のとき$y=0$ですから、 t_2 = sym.solve(eq1_2.subs(y(t),0), t) と記述してやることで、解として $$ \left[ 0, \ \frac{2 v_{0} \sin{\left(\theta \right)}}{g}\right] $$ が得られます。$t_{2}\neq0$ですから、 $$ t_{2}=\frac{2 v_{0} \sin{\left(\theta \right)}}{g}(=2t_{1}) $$ であることは明らかです。 ここで、質点に対しては水平方向の力は何も加わっていませんから、水平方向の運動方程式は次のようになります。 $$   m\frac{d^{2}x}{dt^{2}}=0 $$ したがって両辺を $m$ で割ったものをこのように記述します。eq2_0=sym.Eq(x(t).diff(t, 2)) これを、微分方程式として、初期条件 $x(0)=0$ 、 $\frac{d}{dt}x(t)=v_{0}\cos\theta$ を課して解くには次のように記述すれば良く、eq2_2 = sym.dsolve(eq2_0, x(t), ics={x(0): 0, x(t).diff(t, 1).subs(t, 0): v0*sym.cos(theta)}) 解として $$ x{\left(t \right)} = t v_{0} \cos{\left(\theta \right)} $$ が得られます。 これに $t=t_{2}$ を代入して解くためにはx_l= eq2_2.subs(t, ((2*v0*sym.sin(theta)/g))) と記述でき、解として $$   x{\left(\frac{2 v_{0} \sin{\left(\theta \right)}}{g} \right)} = \frac{2 v_{0}^{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)}}{g} $$ すなわち水平到達距離 $x$ として $$   x= \frac{2 v_{0}^{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)}}{g} $$ が得られます。解答(空気抵抗アリ)1. $x$ 軸および $y$ 軸を空気抵抗ナシの場合と同様に取ることを考えます。 ここで、空気抵抗の比例定数を $\kappa$ (カッパ)とすると、 $y$ 軸方向の運動方程式は次のようになります。 $$   m\frac{d^{2}y}{dt^{2}}=-mg-\kappa\frac{dy}{dt} $$ 両辺を $m$ で割り移項すると $$   \frac{d^{2}y}{dt^{2}}+\frac{\kappa}{m}\frac{dy}{dt}+g=0 $$ となります。これを記述するとeq1_0 = sym.Eq(y(t).diff(t, 2)+(kappa/m)*y(t).diff(t, 1)+g,0) と書けますから、これを次のようにsympy.dsolve()を用いて、初期条件 $x(0)=0$ 、 $\frac{d}{dt}x(t)=v_{0}\cos\theta$ を課して解きます。eq1_2 = sym.dsolve(eq1_0, y(t), ics={y(0): 0, y(t).diff(t, 1).subs(t, 0): v0*sym.sin(theta)}) すると次のような解が得られます。 $$   y{\left(t \right)} = - \frac{g m t}{\kappa} + \frac{\left(- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}\right) e^{- \frac{\kappa t}{m}}}{\kappa^{2}} + \frac{g m^{2} + \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2}} $$ 空気抵抗ナシの場合に比べて非常に式が複雑になっていますが、解法は特に変わりません。 式自体が長くなったので省略しましたが、式の両辺を微分すると $$ \frac{d}{dt}y(t) = -\frac{g m}{\kappa} - \frac{\left(- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}\right) e^{- \frac{\kappa t}{m}}}{\kappa m} $$ となります。 あとは空気抵抗ナシの場合と同様に、 $t=t_{1} $のとき $dy/dt=0$ となると考えて、直前の式を用いて定義したeq1_1を $t$ について解くと、 sympy.solve()を用いて次のように書くことができ、t_1 = sym.solve(eq1_1, t) 解として $$   t_{1}= \left[ \frac{m \log{\left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa}\right] $$ が出力されます。最高点$h$も空気抵抗アリの場合と同様にして、 $t_1$ を $y(t)$ の式に代入すれば良く、h = eq1_2.subs(t, m*sym.log(1 + kappa*v0*sym.sin(theta)/(g*m))/kappa) と記述すれば、計算結果として $$ y{\left(\frac{m \log{\left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa} \right)} = - \frac{g m^{2} \log{\left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa^{2}} + \frac{g m^{2} + \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2}} \\+ \frac{- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2} \cdot \left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m}\right)} $$ が得られますから、 $$ h= - \frac{g m^{2} \log{\left(1 + \frac{\kappa v{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa^{2}} + \frac{g m^{2} + \kappa m v{0} \sin{\left(\theta \right)}}{\kappa^{2}} + \frac{- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2} \cdot \left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m}\right)} $$ とわかります。 2.こちらの問題も空気抵抗アリの場合と同様に解く、といいたいところですが、数式が複雑化してしまったせいか、 投げ上げてから落下するまでの時間を求める過程でプログラムが停止してしまい計算を完了することができませんでした。 そのため、残念ですがこのあたりは今後の課題とします。 余談sympy.plot()を用いれば、物体が実際にどのような運動をするのか、というグラフをプロットすることができます。 次の図は私が適当に質点の質量やら投げる角度やら空気抵抗定数やらを指定してプロットした結果です。青が真空中での $y-t$ グラフ、オレンジが流体中での $y-t$ グラフです。 (ここで流体と表現したのは、空気抵抗定数あたりの計算を厳密にしなかったせいで現実ではありえない空気抵抗が発生している可能性があるからです。下手したらタイトル詐欺かも...) 終わりに至らない点も多くありましたが、SymPyを用いた計算の方法を習得することができ、個人的には有意義な取り組みだったと思います。 もしかしたらまた同様の記事を書くかもしれません。ここまでご覧いただきありがとうございました。 参考文献maskot1977 . "微分や微分方程式をPythonで理解する" . "Qiita" . 2019-01-09 .https://qiita.com/maskot1977/items/b4395da5f33f70cd4a09, (参照 2023-02-20)大窪 貴洋. "Sympyによる代数計算" . "コンピューター処理 ドキュメント" . 不明. https://amorphous.tf.chiba-.jp/lecture.files/chemcomputer/15Sympy%E3%81%AB%E3%82%88%E3%82%8B%E4%BB%A3%E6%95%B0%E8%A8%88%E7%AE%97/15.html,(参照 2023-02-27).SymPy Development Team. "SymPy". 2021. https://www.sympy.org/en/index.html, (参照 2023-02-28).浜島清利, 物理のエッセンス四訂版 力学・波動, 河合出版, 2013

> 内容を見る

プログラミング Polygon
プログラミング

KotlinのFlow用テストライブラリ「turbine」を使ってみる

動機とあるインターンでテストコードを書いていた時の事。「turbine」というライブラリを使って書き上げてプルリクを作成した。 するとレビュアーから「このライブラリなにこれすごいな」というコメントを頂いた。 結構気に入っているライブラリなのだが知られてなくて悲しかったため記事に書くことにした。 そもそもFlowとはFlowとはKotlinにて非同期で値を返してくれるすごいやつで、近年のAndroid開発において必須の技術である。 例えば以下のコード。 suspend fun countStream(num: Int): Flow<Int> = flow { repeat(num) { count -> delay(100) emit(count) // 0 1 2 3 4 ... 99 } } fun main() = runBlocking { countStream(100).collect { println(it) } } このコードを実行すると、0 ~ 99までの数値が0.1秒毎に出力される。 当然非同期処理なので、main関数にコードを追加すれば同時に実行されるようになる。 詳しくはこの記事が参考になると思う。 Flowのテストの難しさここで、以下のtestTarget関数のテストを書いてみる。 suspend fun testTarget(): Flow<String> = flow { emit("First") delay(1000L) emit("Second") delay(1000L) emit("Third") } fun main() = runBlocking { testTarget().collect { println(it) } } 一秒ごとに文字列を返す関数で、出力結果は以下のようになる。 First Second Third Process finished with exit code 0 テストで確認したい項目は以下の通りFirst、Second、Thridの順番に出力されるこれ以上の値は出力されない これをもとにテストを書くと以下のようなコードが考えられる。 @Test fun testTargetのテスト() = runTest { testTarget().collectIndexed { index, value -> if (index == 0) assertEquals("First", value) if (index == 1) assertEquals("Second", value) if (index == 2) assertEquals("Third", value) if (index >= 3) throw IndexOutOfBoundsException() } } このように記述することで上記の内容をテストできる。 これでもすっきりと記述できているがいくつか気になる点がある。 1.いちいちindexの値を見るコードを書かないといけないこれにより、ただコードを書く行が増えるだけではなく、変更に弱くなっている。例えばFirstとSecondの間にHogeが出力されることになったら大幅に変更する必要がある。 2.throwする内容が統一されない可能性がある現在IndexOutOfBountdException()が投げられている。しかし、多くのテストコードを書くうちにここが不明瞭になる可能性がある。複数人で開発しているときはなおさらである。 以上の問題点が考えられる。 これ以外にも、Flowの関数が複雑になる(例えばMutableStateFlowを用いる等)ごとにどんどん書きづらくなる。 テストが読みづらいコードであるとメンテナンス性が著しく低下するため、もう少し書きやすくしたいところである。 turbineを利用 ここで「turbine」の出番である。turbineはFlow用テストライブラリで、非常にシンプルに記述することができる。 先ほどまでのコードはturbineを使うことで以下のように書くことができる。 @Test fun testTargetのテスト() = runTest { testTarget().test { assertEquals("First", awaitItem()) assertEquals("Second", awaitItem()) assertEquals("Third", awaitItem()) awaitComplete() } } test {}、awaitItem()、awaitComplete()がturbineライブラリで提供される関数である。 これで上記の問題を解決できている。また可読性も向上している。 turbineの使い方build.gradle(.kts)に以下を追記する。 repositories { mavenCentral() } dependencies { testImplementation 'app.cash.turbine:turbine:0.12.1' } これだけで導入可能である。 メソッド紹介(一部)test()turbineを適用するFlowを指定するための関数。 timeout等も指定可能である。targetFlow.test { // turbineのコードをここに記述 } awaitItem()値が流れてくるのを待ち、流れてきたらその値を返す。 複数回呼び出した場合、流れてきた値順に値がかえされることになる。targetFlow.test { val first = awaitItem() // 値が流れてくるまでブロッキング。流れてきたらその値を返す val second = awaitItem() // 次の値が流れてくるまでブロッキング。 } awaitComplete()Flowが閉じたことを確認する関数。 新しく値が流れてきた場合TurbineAssertionErrorを投げる。targetFlow.test { val value = awaitItem() // 値が流れてくるまでブロッキング。流れてきたらその値を返す awaitComplete() // 新しい値が流れてきたらエラーが投げられる。 } awaitError()Flowでエラーが投げられたことを確認し、返り値として返す。 値が流れてきたりFlowが終了した場合、TurbineAssertionErrorを投げる。targetFlow.test { val value = awaitItem() // 値が流れてくるまでブロッキング。流れてきたらその値を返す val throwable = awaitError() // 新しい値が流れてきたらエラーが投げられる。投げられたエラーを返り値として返す } 基本的にはこれらの関数で成り立つ。 詳しくは公式のGithubや資料を参照するとよさげ。 https://github.com/cashapp/turbine https://cashapp.github.io/turbine/docs/0.x/turbine/app.cash.turbine/index.html 終わりにFlowに限らずテストは書くのが億劫になりがちだが、こういったライブラリを有効活用することでかっこよく書くことが可能である。 ぜひ自身のプロダクトでも記述してもらいたい。 ついでにAndroid技術者が全然足りていないのでぜひ勉強してほしい。結構簡単な部類のはずなので・・・

> 内容を見る

プログラミング Polygon
プログラミング

TwitterにDeflate圧縮されたバイナリをアップロードして利用する 🤔

まずはこれらのツイートを見てほしい。echo 'class ${public static void main(String[]a)throws Exception{var b=new int[810000];https://t.co/hd3ce1Xpzc(new https://t.co/vgGGZeOChL.File("/media/0")).getRGB(0,0,900,900,b,0,900);for(int i:b)if((i&255)!=0)System.out.write(i);}}'>$.java java $.*>_.java java _.* #シェル芸 pic.twitter.com/aCxDjD9ZS1— 社畜ちゃん (@Shachiku_nyan) February 23, 2023 次は◉のターンめう~  12345678 A         B         C         D   〇◉    E   ◉〇    F         G         H https://t.co/TZGGgxwtH7— シェル芸bot (@minyoruminyon) February 23, 2023 5F6F6E4F5G6G7G8H3D5H8G3E2E6D6H7H7D4G7E3F2G7F8F8E4H4C5C3C5B2C1C6B7A8C8D7C2D6C7B3G3H2H8B4B5A8A6A1H3A4A1B1A3B2B1E2F1G1F1D2A #シェル芸 https://t.co/mYeyPWMN9Y— 社畜ちゃん (@Shachiku_nyan) February 23, 2023 〇の勝ちめう!  12345678 A◉〇〇〇〇〇〇〇 B◉〇〇〇〇〇〇〇 C◉〇〇〇〇〇〇〇 D〇〇〇〇〇〇〇〇 E〇〇〇〇〇〇〇〇 F〇〇〇〇〇〇〇〇 G〇〇〇〇〇〇〇〇 H〇〇〇〇〇〇〇〇 https://t.co/1Ux236CqEU— シェル芸bot (@minyoruminyon) February 23, 2023 シェル芸bot(@minyoruminyon)とは、それがフォローしているユーザが #シェル芸 等のハッシュタグをつけてシェルコマンドをツイートしたとき、引用RTでその出力をつぶやくbot [注1] 。 つまり、上記のツイートはシェル芸botにjavaコマンドを実行させてオセロをしているということになる。しかしオセロのソースコードは含まれていないように見えるが……? /media/0シェル芸botには、ツイートに添付された画像を /media ディレクトリ下にあるものとして実行する機能がある。1枚ならその画像は /media/0 。 つまり最初のツイートは、画像を読み込んで java.awt.image.BufferedImage.getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) によって埋め込まれたバイナリを取り出しているということになる。 記事タイトルが物々しかったが、「Deflate圧縮されたバイナリ」とは、ただ単に「バイナリを埋め込んだPNG画像」のこと。 ちなみにDeflateはおなじみZIPやgzipにも使われる可逆圧縮アルゴリズム。 ちなみに元ツイートを書いた人によると、埋め込まれているのはJavaのソースらしい [2]。手元で実行してみたらわかる。 扱える画像今どうなっているかは知らないが、どうも数年前時点でのTwitterの画像の仕様はこうらしい [3](読み間違いがあれば申し訳ない)。JPEGは原則、quality85の4:2:0クロマサブサンプリングされたJPEGとして再エンコードされる。ただし次の条件をすべて満たすならば再エンコードしない。画像サイズが4096x4096以下である。5MB以下である。画像の回転が必要であるような情報を持っていない。1ピクセルあたり1バイト未満である。WebPもquality85のJPEGに変換される。PNGは色深度や画像サイズによって異なる。PNG-8は保持される。PNG-24,PNG-32のうち、実際の色数が256色以下であるならばPNG-8に変換される。上記を満たさないPNG-24,PNG-32のうち、画像サイズが900x900以下ならば保持される。上記を満たさないPNG画像は、JPEGに変換される。扱える最大の画像サイズは4096x4096である。 つまり、900x900のPNG-32を使えば約3MBもの情報を使うことができる。4枚使えば4倍(要検証)。 ……4096x4096のPNG-8でも16MBの情報を使えるのでは?などと思ったが多分JPEGに変換されるんだろう。知らんけど。 あとはプロトコルを交換しておけばメモ帳アプリのスクショをツイートするよりも大量の情報を一気に投稿できる(誰にわかるねん)。 おまけ失敗しとるやないかい! Lorem ipsumが出る予定だったのに/media/0: PNG image data, 15 x 10, 8-bit colormap, non-interlaced https://t.co/V6BDW0Wr5q— シェル芸bot (@minyoruminyon) February 28, 2023 ツイート1: 本当なら 8-bit/color RGB になってもらう予定だったが、256色以下だったので colormap に変換されてしまった例!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a%bcdefghijklllllllll https://t.co/MTbx2B4GR7— シェル芸bot (@minyoruminyon) February 28, 2023 ツイート2: WebPに変換される(多分?)とかいう知らない仕様によってASCIIコード表から順番に持ってきたみたいな謎文字列が出ている例 参考文献[1] 社畜ちゃん(@Shachiku_nyan)「echo 'class ${public static void …」 https://twitter.com/Shachiku_nyan/status/1628601778345500674 (2023年2月27日アクセス) [2] 社畜ちゃん(@Shachiku_nyan)「@AAAR_Salmon javaのソースそのまま押し込めてますw」 https://twitter.com/Shachiku_nyan/status/1628699291454963712 (2023年2月28日アクセス) [3] NolanOBrien「Upcoming changes to PNG image support」 https://twittercommunity.com/t/upcoming-changes-to-png-image-support/118695 (2023年2月28日アクセス) [注1] ふるつき「今の所判明しているシェル芸botの仕様」 https://furutsuki.hatenablog.com/entry/2018/07/13/221806 (2023年2月28日アクセス)

> 内容を見る

プログラミング Polygon
プログラミング

スタートメニューからWSLを終了させ、RAM使用率を表示

作るものwsl --shutdown をGUIで叩く。GUIの方がめんどいわという人はこの記事の対象ではない。 例えばDocker DesktopやIDEを終了しても、WSLがかなりメモリを食っていたとする。 ポチッとな! パソコンルームでブラクラを作っていた頃を彷彿とさせるプロンプトが出る。 はいを押すと立ち上がってるディストロが全部落ちる。ついでにRAM使用率も表示する。数字丸めるの忘れた。 そもそも作業中はコマンドを普通に叩くだろうから、なにか作業中にポチッとなすることは想定していない。これからゲームする時とかに使え。 VSCode設定変更PowerShell拡張機能を入れる。   "terminal.integrated.env.windows": {     "PSExecutionPolicyPreference": "RemoteSigned"   }settings.json に上記を追記し、自作スクリプトを実行可能にする。 Add-Type -Assembly System.Windows.Forms $result = [System.Windows.Forms.MessageBox]::Show("WSL全部終了する?","確認","YesNo","Question","Button2") If($result -eq "Yes"){     wsl.exe --shutdown     # https://shellgeek.com/powershell-get-memory-usage/     $CompObject = Get-WmiObject -Class WIN32_OperatingSystem     $Memory = ((($CompObject.TotalVisibleMemorySize - $CompObject.FreePhysicalMemory)*100)/ $CompObject.TotalVisibleMemorySize)     [void][System.Windows.Forms.MessageBox]::Show("現在のRAM使用率: " + $Memory + "%", "WSLを終了しました") } exit 0 VSCodeで上記のスクリプトを書き、適当な場所に保存する。 ただ、MessageBoxはShift-JISしか日本語を受け付けないっぽいので、エンコードを変えて保存し直す必要があると思う。 UTF-8で保存して日本語を出せる方法を誰か教えてくれ~ ショートカット作成 ショートカットを作成。分かりやすい名前を付ける。 ショートカットを無理やり追加本当は上記ファイルをスタートメニューにぶちこみたいんだが、右クリックメニューにないしD&Dもできない。 仕方ないので `%AppData%\Microsoft\Windows\Start Menu\Programs にショートカットを放り込む。 放り込んだショートカットのプロパティを開く。 powershell -ExecutionPolicy RemoteSigned -File "ps1ファイルのパス"ただ単にps1ファイルへのショートカットになっているので、先頭にpowershellを追記し、あとポリシーを変える。 あとは検索すれば出てくるはずなので、スタートメニューにピン留めする。 参考RAM取得する部分https://shellgeek.com/powershell-get-memory-usage/ メッセージボックスhttps://acoustic-groove2.hatenablog.com/entry/2018/01/29/233637 実行ポリシーhttps://zenn.dev/nekocodex/articles/eb3403961ad9b966ff6e

> 内容を見る

プログラミング Polygon
プログラミング

RAMが足りないので、Dockerだけ別PCのWSLに任せてリモート開発する

RAMちゃん「〽ああ〜〜 男の人って〜〜〜 いくつも〜〜〜 RAMを持って〜いるのね〜〜〜」 →持ってねえよ! だが無駄にPCが複数ある!!! DockerのRAM消費をケチるため、PCを2台使って分担する。 --- 概要以下、2台のPCと3種類のターミナルが登場する。WSLマシンホスト: WSLを動かすWindowsゲスト: WSLのLinuxメインPC: 任意のPC。IDEやブラウザはここで動かす WSL用マシンのIPを固定ルータをいじって、WSLのマシンのIPを固定する。 Buffaloだったら「詳細設定→LAN→DHCPリース」から設定できる。 以下、このIPは 固定したIP とするが、流石にスクリプトにハードコーディングはしない。 WSL用マシンの準備 (WSLホスト側)前提として以下を済ませる。Windows Terminalを入れておくストアはめんどいのでGitHubから背景写真とかいじれて楽しいよWSL2の有効化Docker Desktopのインストール俺、これ知らなくてUbuntuにDocker入れちゃったそもそもDocker Desktopのインストーラが全部やってくれるので、WSL側で何かすることはないHyper-VではなくWSL2バックエンドを選択することなおRDP(リモートデスクトップ)やOpenSSHサーバーは不要。まあ画面がしょぼいとかの理由で、RDPがあればベターだが、Proライセンスいるんよな。 wsl.exe -l -v してWSL1だった場合、以下のようにアップグレードする wsl.exe --set-version ディストロ 2 wsl.exe --set-default-version 2 あと不確実性排除のためにディストロ固定する。お使いのディストロに合わせて適宜調整すること。wsl --set-default ディストロ スクリプトを書く (WSLゲスト側)以下、ホストのexe叩くだけなので、任意のディストロで動くはず。 ポートフォワード設定sudo vi /bin/port_forward.sh ユーザ関係ないのでbinに置いたが、適宜変えてくれ!ベターな場所があるはず #!/bin/bash # ポート開放の自動化 # Usage: ログオン時にやれ! # ゲストのIP IP=$(ip route | grep 'eth0 proto' | cut -d ' ' -f9) function forward() { PORT=$1 # 2つめのポート指定なければ同じポートを使う PORT2=${2:-$PORT} # まず削除 netsh.exe interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=$PORT2     netsh.exe interface portproxy add v4tov4 connectaddress=$IP connectport=$PORT listenaddress=0.0.0.0 listenport=$PORT2 } forward 22 2222 # SSHは2222に転送 # 以下はLaravel Sail用なので適宜変更 forward 3306 # mysql forward 6379 # redis forward 7700 # meilisearch forward 8025 # mailhog forward 8000 # web # 自動起動 sc.exe config iphlpsvc start=auto sc.exe start iphlpsvc sudo service restart ssh 上記のスクリプトを書く。以下のことをしているホストのipconfig.exe を叩いてIPを取る。先程固定したが念の為。ゲストのIPを取る。netsh.exe でポートフォワードを設定する。ドキュメントはこちらforward関数に2つ目の引数を渡したら、VMとホストで違うポートを使う(22 → 2222)渡されなかったら単に同じポートを使う。SSHサービス毎回再起動。しないとゲストのIPが変わってreset by peer と怒られる 状況確認netsh.exe interface portproxy show v4tov4 ipv4 をリッスンする:         ipv4 に接続する: Address         Port        Address         Port --------------- ----------  --------------- ---------- 0.0.0.0    2222        WSLのIP   22 # 後略 こんな感じになるはず。この場合 2222 番ポートにSSHすればWSLに入れる。 削除netsh.exe interface portproxy delete v4tov4 listenport=ポート なんか間違えたときはこれで削除。 SSH使えるようにする (WSLホスト側 2)次に、ログオン時にポート開放するタスク設定と、ファイアウォール。 タスクスケジューラタスクスケジューラで以下のタスクを定義する。名前は「WSLポートフォワード」など「ユーザーがログオンしているときのみ」「最上位の特権で実行」トリガーはログオン + 1分遅延を入れるさもないと、何故か実行時にパラメータ不足で失敗する wsl -u root --exec /bin/port_forward.sh操作は上記のコマンド。 多分これでいいはず。 ファイアウォールcontrol firewall.cpl ファイアウォールを開ける。「詳細設定」→「受信の規則」から新しいルールを追加し、ポートTCP2222,3306 という風に必要なポートを開ける。 リモート開発 (WSLゲスト側2)あとは、リモート開発をする。 開けたポートを使うようなコンテナを動かしていく。 cd 任意の開発用ディレクトリ curl -s https://laravel.build/laravel-sandbox | bash 面倒なので一発で立ち上がるやつ。これをやらかすと laravel-sandbox にLaravel一式がクローンされ、勝手にdocker-compose.ymlに書いてあるコンテナのビルドや、.envのコピーまで始めてくれる。一式というと、PHPもComposerもDBもここでビルドするので、割と時間がかかる。 cd laravel-sandbox sed -i 's/APP_URL=http:\/\/localhost/APP_URL=http:\/\/localhost\nAPP_PORT=8000/' .env ./vendor/bin/sail up80番になっているので APP_PORT=8000 に変えて起動。 メインPCvi ~/.ssh/config Host wsl HostName WSLマシンのIP Port 2222 User WSLのユーザ名 ssh wsl これでWSLに入って開発できるはず。VSCodeやJetBrainsのSSH機能も使える。 SSHだけじゃなく、8000番台とかも開放したのでWebアプリの動作確認もできる。 PhpStormがRAM4GB要求してくるJetBrains系IDEはみんなこうなのか試してないが、PhpStormは4GM要求してきた。 俺のWSLマシンのRAMが4GBしかなく、フルにWSLに割り振る必要が出てきた。wsl --exec vi /mnt/c/Users/ユーザー/.wslconfig [wsl2] memory=4GB wsl --shutdown まあ実際は4GB割り振れないんだが、誤魔化せる ▲passwordはpasswordではない(多分翻訳ミス) 参考https://qiita.com/yabeenico/items/15532c703974dc40a7f5 ポートフォワード部分https://zenn.dev/d_hori/articles/128f8976a48b52 ホストのIPhttps://learn.microsoft.com/ja-jp/windows/wsl/wsl-config .wslconfig

> 内容を見る

NoImage Polygon
プログラミング

Nullはなぜ危険なのか

こんばんは、オキリョウです Kotlin 1.7.0がついにきましたね! https://blog.jetbrains.com/kotlin/2022/05/kotlin-1-7-0-beta/ みなさん期待を膨らませていることだと思います。 ところで、このバージョンの目玉として取り上げられているものの中に、max関数及びmin関数があります。 名前を見てわかる通り、これはリストの中の最大、最小の値を取ってくる関数になります。 こんな原始的な機能すらKotlinにはなかった・・・というわけではありません。 代わりにmaxOrNull、minOrNullという関数が存在します。 では何が違うのかというと、最大値及び最小値が存在しないときの挙動です。従来 -> nullを返す今回 -> エラーを投げるとなっています。 最近ではNullは敬遠されがちです。その流れを汲み取ってこの関数を実装したのでしょう。 ものすごく良さげな機能に見えますね。 でも、実は私、あまり良い気がしていません。 とりあえずNullは危険だ!という理由でより危険なこの関数を使う人がいるかも?と思っているからです。 そこで、なぜNullが危険と謳われているのかを解説していきたいと思います。 Nullの危険性Nullの危険性に触れてみるまず、以下のコードを見てみましょう。こちらはJavaのコードになっています。public static void main(String[] args) { Integer value = 5; System.out.println(value * value); }至って普通のコードですね。出力結果は以下のようになります。25 それではこのコードを少し編集してみましょう。public static void main(String[] args) { Integer value = null; System.out.println(value * value); }value変数に入れる値をnullにしてみました。この場合どのような挙動をすると思いますか? そもそも数値型にnullなんか入らなさそうだからコンパイルすら通らなさそうです。 実はこれ、コンパイルが通るんです。 ただ、実行しようとするとエラーを吐いて止まります。Exception in thread "main" java.lang.NullPointerException Nullの正体なぜ上記のような挙動をするのでしょうか? それはnullが何かを理解すればすぐにわかります。 nullは何もないことを表す番地へのポインタです。 先程の実行例を見てみましょう。Integer value = 5;これは、5という値がとあるアドレスに格納されていて、そのアドレスの番地をvalueが保存している・・・という構造になっています。 ではnullの場合はどうでしょう?Integer value = null;これは、何も意味がないというアドレス(通常は0アドレスとからしい)の番地をvalueが保存しています。 ここから、value自身は両者とも同じようにポインタを指していることがわかります。 問題は、そのポインタの先に値がある場合とない場合があるということです。 そのせいで、実行時ではないとエラーがわからないのです。 説明がややこしくてわからん!という人は以下のように考えましょう。 Integer型とは、暗黙的にInteger or null型になっていた これらはもちろん、他の型でも当てはまります。 例外として、プリミティブ型と呼ばれるポインタを使わない型にはnullは使えません。 何が恐ろしいかここまで色々話してきましたが、結局nullの何が恐ろしいのでしょうか? まとめると以下の通りです。実行するまでエラーが出ないため、本番環境で爆発する可能性があるnullを参照した場所でエラーが起きるため、どこでnullが代入されたか(すなわち原因)を特定するのは困難nullを使わないように制御する方法がないため、人力でnullチェックをする必要がある(人力なので忘れがち)これらのことから、エンジニア界隈ではnullを恐れる人が多発する事になりました。 このことから、nullを開発した張本人も後悔の念を表明しています。 https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/ Nullの現在ここまででnullの恐ろしさを十分に理解できたのではないでしょうか? しかし、ここまで恐ろしいと言われているnullをそのまま放置する人類ではありません。 比較的最近作成された言語では十分な対策が取られています。 有名な言語としてKotlinが挙げられます。 ではどのような対策をしているのでしょうか? やっていることは単純です。null非許容型を用意したんです。 実際のコードを見てみましょうval value: Int = null // NGこのように、普通の型にはnullが入らないようにしました nullを使いたい場合は、下のように?をつけることで許容型にする必要がありますval value: Int? = null // OKまた、この?を外さない限り、Int型のメソッドは呼び出せません。 外すにはnullが変数の中に入っていないか(いわゆるnullチェック)をする必要があります。 こうすることで強制的にnullチェックをさせるようにしました。 これで Int = Int or nullみたいなことは無くなったわけです。 もちろん、nullチェックがしやすい構文も用意されており、nullとの付き合いが断然楽になりました。 まとめかつて、nullは非常に恐ろしいものでした。 最も簡単にエラーを引き起こしてしまいますし、かつ原因がわかりづらい。 しかし、現在ではコードによる強制チェックができるようになったため、このようなエラーは随分と減りました。 むしろ、確実にチェックしなくてはならない存在となったのです。 チェックしないとそもそも関数呼び出せないですからね。 そのため、現在のコード上でのnullは結構使いやすい存在になったと個人的には思っています。 かといって乱用するのもどうかとは思いますが・・・ 昔よりはnullを使うデメリットがガッツリ下がったと思ってください。 もちろん、Null非許容型が用意されていない言語の場合、nullは非常に危険です。 例えばJava, PHP等の古い言語、またモダン言語でも昔のDart等が挙げられます。 できる限り使わないようにするか、Option型の導入を検討しましょう。 それでは、お疲れ様でした! 補足冒頭で述べた「nullより例外の方が危険だ」という事について補足しておきます。 例外の何が恐ろしいのかというと、コンパイラによってチェックされない事です。 どこからでも好きに投げれて、try-catchを使ってチェックをしなくてもコードに怒られない・・・ そうですよね? ちょっと怖くないですか? 先程のnullで説明したことと同じような課題が出てきています。 このことから「近代のgoto文だ」と批判する人すらいるほどです。 私もこれには同感です。そこらでポンポンエラーを投げるくらいならnullで表現する方が安全だと思います。 大概のモダン言語上のnullであれば確実にチェックをする必要がありますので想像以上に安心して動かせます。 値が存在しなかったからnullを返すわけですし、結構自然な気もしますしね。 いや、nullじゃ表現力が足りない。型の堅牢さも足りていないから例外は必須だ!という人。良い着眼点です。 そんな人はぜひResult型について勉強してみてください。これを使えば完全に例外のない世界も実現可能です。

> 内容を見る

プログラミング Polygon
プログラミング

MacのChromeで学校用プロフィールのショートカットを作る

Chromeのプロフィール選択で、Windowsならデスクトップショートカットを作るボタンがあるんですけど、Macにはないので自作します。 時々プロフィール選べない問題 皆さんChromeで「前回開いていたページを開く」にしていますか?賛否あると思うんですが、基本的にはこれが一番便利だと思います。 もちろんこの場合、学校用プロフィールを作っておかないと事故ります。 つまり、「前回開いていたページを開く」人は、「起動時に(プロフィール選択を)表示する」にチェックを入れないとやばいです。 しかしですね、Macだけかもしれませんが、Chromeがプロフィール選択画面をすっ飛ばす謎の現象が多々発生するんですよ。 講義室で開く用のショートカットを作る ということで、「最初から学校用のプロフィールが開くショートカット」を作ります。 https://superuser.com/questions/377186/how-do-i-start-chrome-using-a-specified-user-profile Super UserのLinuxオタクによると、Application Supportにプロフィールがおいてあるらしいですよ。それで--profile-directory を付ければいいらしいです。 デフォルトの写真を確認$ qlmanage -p ~/Library/Application\ Support/Google/Chrome/Default/Google\ Profile\ Picture.pngまずDefault のプロフィール写真を見ましょう。ほとんどの場合、個人用Googleアカウントの写真が出るはずです。この写真が学校用だったら次の手順は飛ばしてください。 余談ですがqlmanageで立ち上がるプレビューは「プレビュー.app」ではないのでcontrol+Cで死にます。ちょっと確認したいときはこっちを使いましょう。(参考) $ PROFILE_NAME=$(ls ~/Library/Application\ Support/Google/Chrome | grep ^Profile | head -n 1) $ echo $PROFILE_NAME # Profile 1複数のプロフィールを作ると、Application SupportにProfile nというディレクトリができます。Default以外は学校用プロフィールしか作ってない場合、Profile 1 が学校用になるはずです。 $ cat <<EOT > ~/Desktop/学校用Chrome.sh #!/bin/bash open -b com.google.Chrome -n --args --profile-directory="$PROFILE_NAME" EOT $ chmod +x ~/Desktop/学校用Chrome.shスクリプトを作ります。エディタ宗教戦争に「エディタを使わない」という手段で抵抗。あとmacでしか使わないからbashでいいでしょ別に。 -b でappファイルの名前でなくバンドル識別子を使います。こっちのほうが確実。あと-n でアプリを無理やり増やします。これを付けないとすでに起動してるときに動作しない。(参考) はいこれで学校用ショートカットができました。 ターミナル.appの挙動について 「正常に終了したら閉じる」を選んどけば、勝手にexit; が入って閉じられるようです。 Raycast使った場合https://www.raycast.com/ MacならRaycast使いましょ Script Commandsという機能を使います。公式のリポジトリをクローンして、_enabled_commands に以下のスクリプトを入れて下さい。#!/bin/bash # Required parameters: # @raycast.schemaVersion 1 # @raycast.title 学校のメアド (Chrome) # @raycast.mode silent # Optional parameters: # @raycast.icon 🏫 # Documentation: # @raycast.author temasaguru /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --profile-directory="<さっき調べたプロファイルフォルダの名前>"Raycastを開いて、「学校のメアド (Chrome)」を選ぶだけで学校のプロファイルが起動します。かんたん!

> 内容を見る