rootユーザでtarを展開するとファイルの所有者が一般ユーザになりうる
結論
rootユーザがtarコマンドでtarファイルを展開するとき、デフォルトではtarファイルの作成時の所有者情報を引き継ごうとする。
他のホストで作成されたファイルだとしてもその所有者情報を引き継ごうとするため、引き継ぎたくない場合は、 --no-same-owner
を付ける。
例:
tar zxv --no-same-owner -f file.tar.gz
環境: Debian bullseye
man tar
man tar
より、root user(superuser)では --same-owner
が付与された状態と等価なので所有者情報を引き継ごうとする。root userの場合のみ --no-same-owner
を付与すれば所有者情報を引き継がなくなる。
--no-same-owner Extract files as yourself (default for ordinary users). --same-owner Try extracting files with the same ownership as exists in the archive (default for superuser).
きっかけ(余談)
Visual Studio Codeの Remote Container を使っていて、 Creating a non-root user に倣って一般ユーザで動作する以下のようなDockerfileを作成した際に、
FROM debian:bullseye-slim # root user RUN apt-get update && apt-get install -y curl && \ curl -L https://<domain>/foo.tar.gz | tar xzv -C /usr/local/src/ ARG USERNAME=vscode ARG USER_UID=1000 ARG USER_GID=$USER_UID RUN groupadd --gid ${USER_GID} ${USERNAME} && \ useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} && \ echo "done" USER vscode
chown -R vscode /usr/local/src/foo
としていないどころかuseradd前に配置した(展開した)ディレクトリにもかかわらず以下のように所有者がvscodeになっており、混乱した。
$ ls -l /usr/local/src/ total 4 drwxr-xr-x 19 vscode vscode 4096 Apr 26 13:47 foo
foo.tar.gz
を別途調べると、UID=1000で作成されていて、偶然vscode userに所有者情報がマッピングされていた、という顛末だった。
$ gunzip foo.tar.gz $ tar xf foo.tar --to-command 'echo $TAR_UID' | uniq 1000
Raspberry Pi 4×4台でKubernetesクラスタを構成するためにしたこと(Kubernetes v1.20 with kubeadm)
概要
Raspberry Pi 4台でkubeadmを使用したシングルコントロールプレーンクラスターの作成を参考にKubernetesクラスタを構成する際に躓いたところを中心に記録した記事です。(2021年1月3日時点)
構成
- Raspberry Pi 4 ModelB 8GB:4台(Master 1台、Worker3台)
- SDカード:32GB
- kubeadm v1.20(GitVersion:v1.20.1)
- コンテナランタイム:containerd(1.4.3)
- ネットワークアドオン:Flannel
Raspberry Piの設定
以下、Kubernetesクラスタ構成時特有の設定ではないが、計4つのRaspberry Piに行います。
- OS書き込み
- www.raspberrypi.org からインストールできるRaspberry Pi Imagerを使い、Raspberry Pi OS Lite(32-bit)をSDカードに書き込み。
- ホスト名変更
- ディスプレイ、キーボードを接続するなどしてログインし、raspi-configからホスト名を変更(System Optionsより)
- SSH設定
- raspi-configから設定。(Interface Optionsより)
- kubeletのためにswapをオフにする
$ sudo dphys-swapfile swapoff $ sudo systemctl stop dphys-swapfile $ sudo systemctl disable dphys-swapfile
- cgroupを有効にするため、
/boot/cmdline.txt
を次のようにする(cgroup_enable以降を加筆。改行せず、1行に記載する)
console=serial0,115200 console=tty1 root=PARTUUID=eec83cbb-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
kubeadmインストール
kubernetes.io より、 Rasberry Pi OSはDebian(Buster)系のため、iptables設定として
net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1
となるようにし、加えてnftablesバックエンドを使わないように切り替えました。
また、 /sys/class/dmi/id/product_uuid
が存在しなかったため、ユニーク性の検証は省略しています。
containerdインストール
Kubernetes v1.20から、採用するコンテナランタイムの選択肢としてのDockerが非推奨となったため、今回はcontainerdを選択しました。 kubernetes.io
kubernetes.io より、Ubuntu 16.04の手順に倣ってインストールします。
ただし、Raspberry Piではadd-apt-repositoryの実行時に
aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/buster
のようなエラーが出ること、CPUアーキテクチャが異なること、ubuntuではなくdebianを指定するため、以下のように一部手順を変更しました。
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - # GPG鍵追加 # add-apt-repository \ # "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ # $(lsb_release -cs) \ # stable" echo "deb [arch=armhf] https://download.docker.com/linux/debian\ $(lsb_release -cs) \ stable" > /etc/apt/sources.list.d/docker.list #リポジトリ設定追加
cgroupドライバーの設定
cgroupドライバーをcgroupfsではなくsystemdに変更します。 必須の項目ではないですが、
systemdと一緒に cgroupfs を使用するということは、2つの異なるcgroupマネージャーがあることを意味します。 コントロールグループはプロセスに割り当てられるリソースを制御するために使用されます。 単一のcgroupマネージャーは、割り当てられているリソースのビューを単純化し、 デフォルトでは使用可能なリソースと使用中のリソースについてより一貫性のあるビューになります。
ということを踏まえて、systemdへ統一します。
ここで、containerdがsystemdのcgroupドライバを使う方法について、
systemdのcgroupドライバーを使うには、/etc/containerd/config.toml内でplugins.cri.systemd_cgroup = trueを設定してください。
について具体的にどの項目を指すかというところに躓きました。
containerd config default > /etc/containerd/config.toml
で得られた設定ファイルについて、systemd_cgroupという項目があるため、当初はtrueに変更すればよいように思われたのですが、実際はdeprecatedになっていました。
[plugins."io.containerd.grpc.v1.cri"] disable_tcp_service = true stream_server_address = "127.0.0.1" stream_server_port = "0" stream_idle_timeout = "4h0m0s" enable_selinux = false selinux_category_range = 1024 sandbox_image = "k8s.gcr.io/pause:3.2" stats_collect_period = 10 systemd_cgroup = false # ここかと思いきやここではない
runtiime_type= io.containerd.runc.v2
の場合は次のように設定します。
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" runtime_engine = "" runtime_root = "" privileged_without_host_devices = false base_runtime_spec = "" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true # 追加
加えて、kubeletからもsystemdを使用するよう、 /etc/default/kubelet
を作成し、
KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"
を記載しました。この作業は計4台全ノードで必要なものです。
クラスタ作成
マスタノードでクラスタを作成し、残りのノードで作成したクラスタに参加します。
まず、マスタノードで kubeadm init
としてクラスタを作成します。
Flannelを使うために、kubeadm init
時に --pod-network-cidr=10.244.0.0/16
を渡す必要がありますが、設定項目を保存するため、あらかじめ kubeadm config init-defaults > ~/kubeadm_config.yaml
としてから kubeadm init --config kubeadm_config.yaml
とすることとしました。デフォルト設定との差分は以下のとおりです。
< advertiseAddress: 1.2.3.4 --- > advertiseAddress: <IP address> # masterノードのIPアドレス。worker側でkubeadm joinする際にアクセスするためのものも兼ねる < criSocket: /var/run/dockershim.sock --- > criSocket: "unix:///run/containerd/containerd.sock"
また、pod-network-cidrは以下の箇所で指定しました。
networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 podSubnet: 10.244.0.0/16 # 追加
最後に、 kubeadm init --config kubeadm_config.yaml
でクラスタ作成を試みると、以下のエラーで失敗しました。
[ERROR Mem]: the system RAM (1 MB) is less than the minimum 1700 MB
これはkubeadmのメモリ容量計算式のバグで、Raspberry Pi 4 8GBのような32bitシステムかつ4GB以上のメモリを持つシステムでのみ起きる(メモリサイズを誤判定する)挙動のようです。
そこで、(上記GitHub Issueでも示されているように)
kubeadm init --config kubeadm_config.yaml --ignore-preflight-errors=Mem
とすることで作成できました。
※ Issueに対応するPull Requestがマージ済みのため、後のkubeadmでは --ignore-preflight-errors=Mem
は不要になる
最後に、クラスタ作成時に表示された kubeadm join
コマンド(トークンを含む)を他の3ノードでも実行して完了です。
結果
kubectl get pod -n kube-system
としてapiserverなどの各種コンポーネントが確認でき、実際に kubectl run
としてPodが動作したため、ひとまず構築できたようです。
Raspberry Piなのでやはり kubectl get pod
のようなリクエストでも重く、時間がかかるようですが、Kubernetesの実験のために使っていきたいところです。
マクドナルドのクーポン番号のヒント問題を手計算で解いてみた
概要
2020年7月31日に突如公開された以下の問題が興味深かったので解いてみました。
ごめんなさい、、大事なコレ忘れてました🙇♀️🙇♀️
— マクドナルド (@McDonaldsJapan) 2020年7月31日
解いてくれたみなさん、もう一度お願いします! pic.twitter.com/2GPTIfSgPL
今回はプログラムを作成したりWolfram|Alphaのような高性能な計算機(?)を使わずに挑戦してみました。
使う道具:
方針
- に床関数、天井関数を施した値を評価する
- 無限級数の計算を頑張る
に床関数、天井関数を施した値を評価する
まず、問題中の意味深長な床関数、天井関数を評価します。特筆すべきなのは、 と です。
の値
より、 が得られます。 次に、値を見積もるために の値を考えます。
となります。
一方、
、 であることから、
を満たすため、
、つまり が得られます。
の値
前節と同様に、 の値を見積もるために の値を考えます。 今、 であることから を満たすため、 の値と の値を考え、整数部分の大小を見積もります。
について
常用対数表によれば、で、 より大きいため、を満たします。
について
次に、やや大きい値を使って上から抑えます。1
ここで、(常用対数表より)であることから、 を満たすため、 が言えます。
したがって、
より、 、つまり となります。
無限級数の計算
前節までの結果を使って問題の式を整理すると、
となります。 特に無限和の部分に着目すると、
と変形できます。 ここで、
について、
( は自然数 )の場合に の項と打ち消しあう項が存在し、残る項の分母がと続くことから、 2
この項は
となります。
したがって、
が得られます。 これは自然数の逆数の2乗の和()と3の倍数の逆数の2乗の和との差なので。。。といったところで作問者の@mathlavaさんから同様の内容の投稿がなされていました。
マクドナルドさんのクーポンのこたえはコチラ!!
— 数学を愛する会 (@mathlava) 2020年8月1日
こたえは #パイの日 だけに81でした😋
今日はマクドナルドへGO!! pic.twitter.com/o9ShK5SMm3
まとめ
常用対数表とゼータ関数は偉大。
10進数→2進数変換のアルゴリズムで出力を逆に読む理由
概要
手計算で10進数を2進数に手計算で変換するアルゴリズムとして,筆算を連鎖したあと,剰余を逆順に並べて出力とするものがありますが,なぜ逆順でうまくいくのか考えたことがなかったので,メモがてらまとめてみます.
概要としては,
- ビットマスクとビットシフト
- 数式
の両面から納得することができました.
アルゴリズム
10進数表記の数 を2進数表記するとして,この記事で対象にするアルゴリズムは以下のとおりです.
- を2で割り,剰余と商を記録する
- に を代入し,が2を下回るまで,これを繰り返す
- 剰余を逆順に左から並べる
の場合を例に図解すると以下のようになります.
Python(3.6.2)のコード例は次のようになります.(実行例)
bits_list = [] while a > 0: bits_list.append(a % 2) a = a // 2 bits_list.reverse()
この記事では,割り算の方向に対して出力の読み上げが逆方向に行われる理由について考えていきます.
準備
2進数で桁の数は,式のように表されます.
は,番目の桁の値を表し,またはです. 例えば,の場合は,,,,となります.
もちろん,2進数,10進数は同じ数の違う表現というだけなので,
が成り立ちます.
(左辺:10進数表現,右辺:2進数表現)
ビットマスクとビットシフト
このアルゴリズムの要点は,2で割った場合の商と剰余を使い続けることにあります.ここで,2で割ることと2で割った場合の剰余をビット演算で表現することを考えます. このとき,2で割ることは 右に1bitシフトすること に,2で割った場合の剰余は 0b1との論理積 に対応します.
2で割った場合の剰余について
右シフトで商が得られることを用いると,ある数bについて2で割った剰余は a - (a>>1)<<1で求められます.
(正の数において,シフト時に足りないbitは0が補われるため)
ここで,最下桁以外の各桁の値はaと(a>>1)<<1の間で必ず等しくなるので,結果として0b1との論理積と同義です.
剰余を求めて記録し,2で割る操作は,まるで だるま落としのように,最下層の桁を取り出し,桁を落とす操作に対応します. したがって,最下桁が最初に取り出され,最上位桁が最後まで残るため,逆順に出力されます.
ビット演算を用いて前述のコードを書き直すと,次のようになります.(実行例)
bits_list = [] while a > 0: bits_list.append(a & 0x1) a = a >> 1 bits_list.reverse()
数式
2で割った場合の商と剰余を求めることは,数式から2をくくりだして, の形式にすることと同義です. そこで,
を変形すると,
となります.ここで,剰余 を無視して(アルゴリズム上は記録することになりますが), この商の部分に着目すると,再び2でくくることができます. したがって,
となります.
このように,剰余の項を無視して2でくくりだす,を繰り返すことができ,これが 最下桁の値から 剰余として分離することに相当するため,出力を逆順に読むことになります.
まとめ
10進数→2進数変換アルゴリズムの出力を逆順に読み上げる理由について,
- ビット演算を用いてアルゴリズム的に
- 数式を用いて理論的に
意味付けを行いました.
補足
# start def convert_to_bin(n, acc_list=[]): return reversed(acc_list) if n == 0 else convert_to_bin(n // 2, acc_list + [n % 2]) # end a = 29 result = "".join([str(b) for b in convert_to_bin(a)]) print(result)
ユークリッドの互除法を多項式に拡張して,(Rustで)実装する
はじめに
この記事はTUT Advent Calendar 2018 三日目の記事(になる予定だった記事)です. 遅れてしまい申し訳ありません.
二日目はT_T3_M5さんの「けんきうのおはなし」でした. 生き物はどうやってものを「見て」いるか?という観点でしたが, 「人は見たいものを見たいように見る」みたいな言葉を思い出しました.
自己紹介
今年は二つのTUT合同でのAdCということで,開催おめでとうございます.
(そういえば,確かフィンランドの方にもTUTってありましたよね.)
はじめまして(?),「むしぱん」です.東京じゃない方の人間です.
プログラミング,数学が好きです.
よく使うプログラミング言語はRust,Juliaです.最近は全然書いてないですが,Kotlin,Elixirも好きです.
私も「けんきうのはなし」をするか?と思いましたが用意に身元を特定できそうなので避けようと思います.
私はプログラミングと数学が好きなので,この記事では数学とプログラミング,両方に関わる話題として, (自然数の)最大公約数を求めるアルゴリズム「ユークリッドの互除法」を多項式に拡張し,(一般化して)Rustで実装した話をします.
ユークリッドの互除法(自然数)
そもそも,一般に言われるユークリッドの互除法は自然数の最大公約数(Greatest Common Divisor)を求めるアルゴリズムです.
自然数に対して,以下の式が成り立つとします.つまり, をで割ったときの商がで,あまりが ということです.
ここで,最大公約数を求める関数をとおくと,以下の式が成り立ちます.
ここで重要なのは,常にが成立する点です. 上の式を繰り返し適用することで最大公約数を求めます.このとき,であることから,徐々にの値が小さくなり,最終的にあまりとなります. あまりが0となったときのの値が最大公約数 となります.
例えば,の例を考えます.最終的に4割る2を計算することとなり,あまりが0であることから,最大公約数は2となります.
これをプログラムとして記述すると,次の様に記述できます.
Rustにおける実装例ですが,他の言語でも同様の実装になるかと思います.
(だとしても,一度となって大小関係が逆転するのみなので,大小関係のチェックは不要です)
fn gcd(a: u32, b: u32) -> u32 { if b == 0 { a } else { gcd(b, a % b) } }
多項式における最大公約数って?
この記事の目的は,この「ユークリッドの互除法」を多項式に応用することでした.今回は各係数が実数の, についての多項式を考えます.
例えば,や,が挙げられます.
この「(についての多項式)における最大公約数」を考える前に,一般的な(自然数における)最大公約数とはどういう数か考えます.
28と6の最大公約数,280と588の最大公約数()を例に, 両整数の素因数分解と最大公約数に着目すると, 実は, 最大公約数は共通の素因数の積 になります.
多項式における最大公約数も同様に, 各多項式を素因数分解(に対応する操作をした場合の)共通の素因数(に対応するもの)の積 である,とします.
では,この多項式における素因数分解(に対応する操作)と素因数(に対応するもの)は何なのでしょう.
そもそも,自然数における素因数分解は,最大限細かく素数の積で表す操作(分解)でした.そして,素数は,1と自身以外を約数に持たない数,すなわち,1と自身の積以外に分解できない数でした.
多項式における「分解」として,一番最初に学校で習う操作があります.「因数分解」です.因数分解は,ある多項式を最大限細かく別の多項式の積で表す操作でした.
実はこの因数分解したときの各多項式(例:におけると)には「既約多項式」という名前がついています.
そして,自然数における素数と素因数分解が,多項式における既約多項式と因数分解に対応しています. (厳密には対象にしている集合が整数か,自然数か,実数か,によってある多項式が既約多項式であるかどうかは変わってきますが…)
したがって,多項式における最大公約数は,両多項式で共通の既約多項式の積となります.
ユークリッドの互除法(多項式)
では,自然数におけるユークリッドの互除法を多項式に拡張します.つまり,これによって多項式の最大公約数(共通の既約多項式の積)が求められればよいということです.
多項式に対して,以下の式が成り立つとします.つまり, をで割ったときの商がで,あまりが ということです.
自然数同様,最大公約数を求める関数をとおくと,以下の式が成り立ちます.
例えば,次の二つの多項式について,互除法を適用します.各々の因数分解から,最大公約数はであることがわかります.
では,互除法を使って最大公約数が求めます.
互除法によると,最大公約数はだそうです.
あれ?ではない?と思うかもしれませんが実はであるため,両多項式ともによって割り切れるのです.
このように,多項式では最大公約数の定数倍の多項式ではすべて割り切れてしまうため,実際は既約多項式の最高次の係数を1に正規化して扱うことがあります.
例えば,先ほどの互除法の例の最後の商,からをくくり出して,と扱えば,定数倍の差を吸収できます.
最大公約数関数を多項式に拡張,実装する(Rust)
ユークリッドの互除法を行う上で自然数,多項式共通の操作は四則演算と剰余演算,余りがゼロであるという判定です. つまり,四則演算と剰余演算ができて,かつゼロが定義されていれば互除法が適用できることが示唆されます.
そこで今回は 新たに多項式を表すPolynomial型を定義し,Polynomial型を実装し,ゼロの概念と等号判定を導入する ことで自然数,多項式両方に適用できる最大公約数関数を実装します. 詳細の実装の説明は省略して,概要のみ説明します.詳細はこちらのGistを参照ください.(思いの外,数式パートが長引いたので…)
Rustでは,トレイト(他の言語でいうインターフェースのようなもの)を用いて関数の型制約を表現できるので,次の様に実装できます.
fn gcd<T>(a: T, b: T) -> T where T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Rem<Output = T> + Zero + PartialEq + Clone, { if b == <T as Zero>::get_zero() { a } else { gcd(b.clone(), a % b) } }
ここで,型TはAddトレイト,Subトレイト,Mulトレイト,Remトレイト,Zeroトレイト,PartialEqトレイトを実装している必要があることを意味します.そして,各トレイトは次の意味を持ちます.
- Add
- Sub
- Mul
- Rem
- PartialEqは
==
によって同値関係が確かめられること
上記のトレイトは標準ライブラリに搭載されているので,自然数(符号なし整数型)には当然実装されています.したがって,新たにPolynomial型にも実装することとしました.
新たに定義したのはZeroトレイトです.Zeroトレイトは,名前の通りその型における ゼロ を定義したいため,次の様な実装にしました.
符号なし整数(ここでは32bitのみ対象)では0をそのまま返し,Polynomial型においては0次の項の値が0の多項式を返します.
impl Zero for u32 { fn get_zero() -> u32 { 0 } } impl Zero for Polynomial { fn get_zero() -> Polynomial { Polynomial::new(&vec![0f32], false) } }
実行結果
一般化したgcd関数はもちろん32bit符号なし整数型にもそのまま適用可能ですが,ここでは先ほどの二つの多項式
の最大公約数を計算した結果を示します.ここで,^
(ハット)の後ろの数字が指数を表します.
実際に,先ほどと同様に最大公約数を求めることができています.
まとめ
この記事では,最大公約数を求めるアルゴリズム,ユークリッドの互除法を自然数から多項式に拡張して,かつプログラミング言語Rustで実装できることを確認しました. 少なくともRustではトレイトで型の制約を表現できるので, 数学における概念と満たすべき性質 がうまく対応付けられて嬉しいと思っています.
多項式が既約多項式かどうか,といったような概念は有名な理論である「 ガロア理論 」とも関わりが強く, 私がこの実装を試みたきっかけにもなっているので,興味のある方は調べてみてはいかがでしょう.(というか私も教えてほしい.)
本来の四日目の担当はyanteneさんで,「何書くかまだ何も考えてない!遅れたらごめんなさい!」とのことです. この記事自体が遅れてしまったので何も言えませんが期待しています.
Juliaの構造体とジェネリクスについていろいろ勘違いしていた話
Juliaについて
最近、数値計算用のプログラミング言語、Juliaにはまっています。
Juliaは、動的型付けでありながら、ユーザ(プログラマ側)が型を明示したり、関数単位のJIT(Just-In-Time)コンパイルに伴う最適化によって高速に実行可能であるため、注目されています。
Juliaにおいても、多言語のジェネリクスのように静的型付け言語にあるようなサブタイプ、スーパータイプを指定することによる型の制約を設けることが可能です。 (2018年5月29日時点、Juliaのバージョンは0.6) 今回はJuliaの構造体とジェネリクス(?)に少々はまったので記しています。
コンストラクタにおける勘違い
まず、以下のようなコードを考えます。
ここで、TはAbstractFloatのサブタイプであることを意味しており、 Hoge{T}(_a::T) = new(_a)
はコンストラクタの定義を表しています。
また、AbstractFloatは64bit浮動小数型であるFloat64、32bit浮動小数型であるFloat32のスーパータイプとなっています。
struct Hoge{T<: AbstractFloat} a::T Hoge(_a::T) = new(_a) end
このコードを実行すると、型パラメータと制約を明記するよう、警告されます。
Juliaでは型パラメータTをそのまま利用できるわけではないようです。これは 構造体の定義外にもコンストラクタを定義できることから、構文を統一されるためのもの だと推測しています。
(Juliaのドキュメントでは、コンストラクタの定義位置が構造体定義の内外に応じて、Innner、Outerと区別されている)
WARNING: deprecated syntax "inner constructor Hoge(...) around C:\ProjectYukito\julia\famiconlike_sound_spectrum.jl:21". Use "Hoge{T}(...) where T" instead.
そこで、次のように変更します。これにより、警告が無くなりました。
struct Hoge{T<:AbstractFloat} a::T Hoge{U}(_a::U) where U<:AbstractFloat = new(_a) end
ジェネリクスにおける勘違い
次に、Hoge型の変数を宣言することを考えます。
hoge = Hoge(0.5)
一見問題ないように見えますが、 このコードは、以下のエラーのため実行できません。
ERROR: LoadError: MethodError: Cannot `convert` an object of type Float64 to an object of type Hoge This may have arisen from a call to the constructor Hoge(...), since type constructors fall back to convert methods.
=コンストラクタがconvert関数として扱われるが、convert関数がFloat64→Hogeの変換に対応していない(?)
コンストラクタが引数の型に対応していない場合、convert関数でエラーを出力することは型変換に関するJuliaのドキュメントにも記されています。
Conversion and Promotion · The Julia Language
問題は、 Float64に対してHogeのコンストラクタが対応していない点 です。
Float64はAbstractFloatのサブタイプであるはずなので、エラーが出力されることが解せませんでした。そこで、Float64であることを明示してみます。
hoge = Hoge{Float64}(0.5)
これにより、初めて正常に実行されました。
ちなみに、Float64の代わりにAbstractFloatのサブタイプであるFloat32を指定すると、同様に cannnot convert ...
のエラーが出力されます。一方で、AbstractFloatのサブタイプでない型(UInt32など)を指定すると、型制約に伴うエラーが出力されます。
これらのことから、 Juliaでは、引数の型に応じて推論の結果型パラメータの値が定まるのではなく、型を明示する必要がある という結論に至っています。
まとめ
普段Rustばかり書いているせいか、Juliaのジェネリクスについて混乱してしまいました。
- 内部コンストラクタ(Innner-Constructor)において、構造体の定義に用いる型パラメータは直接適用されない
- 変数の宣言時、型パラメータは明示する必要がある(引数の型推論から確定するわけではない?)
WindowsでSource Code Pro をビルドしようと思うも一筋縄ではいかなかった話
最近、エディタをSpacemacsに乗り換えました。
Spacemacsのデフォルト設定では、フォントがSource Code Proなので、Source Code Proが入っていない場合は起動後にWarningが出ます。
おそらく代替フォントで描画されているかと思いますが、特に日本語の描画が大変厳しいようで、お世辞にも綺麗とは言えない状況でした。
そこで、Source Code Proを導入することとしました。
Source Code Pro(GitHub)にもあるように、 リリースページからダウンロードすればよい のですが、 github.com 見事にそのリンクの情報を見逃した私は、ソースコードからビルドすることにしました。
基本的にはREADMEにしたがって行えばよいのですが、私の環境(Windows10)ではbuild.cmdの実行時にエラーが発生しました。
syntax error at "}" missing ";"
当該リポジトリのIssueにも同様の事例が報告されていませんでしたので、困惑していたのですが、別リポジトリのIssueを参考に解決することができました。 つまり、familyTables.feaの2行目のセミコロンを削除する ことで対処できました。
いやわざわざビルドしなくていいのに、と言われればそれまでなのですが。。。