自作OSで学んだRustのあれこれ
by だいみょーじん
自己紹介
目次(自作OSで学んだRustのあれこれを5つ紹介!)
cargo clippy
そのブール式,もっと簡単にできるよ!
x.map(..).flatten()はx.flat_map(..)でいいよ!
cargo clippy
推奨された書き方の利点
cargo doc
bitfield_struct クレート
OnceCell
RustをHaskellの観点から見てみる
| 返り値の依存先(返り値が何によって決まるのか) |
純粋関数(関数) | 引数のみ |
不純関数(IO) | 引数,スタティックなローカル変数,グローバル変数,ユーザの入力,etc. |
RustをHaskellの観点から見てみる
C言語の表記 | C言語の表記(カリー化) | Haskellの表記(括弧付き) | Haskellの表記(括弧無し) |
f(x) | f(x) | f x | f x |
f(x, y) | f(x)(y) | (f x) y | f x y |
f(x, y, z) | f(x)(y)(z) | ((f x) y) z | f x y z |
RustをHaskellの観点から見てみる
RustをHaskellの観点から見てみる
| 文脈 | 発生源となる計算 |
Maybe | 存在するかどうか分からない値 | 失敗するかもしれない計算 |
リスト | いくつあるか分からない値 | 解の個数が分からない計算 |
関数 | 引数を与えると返ってくる値 | 解を得るための材料が足りない計算 |
IO | 実行してみないと分からない値 | ユーザの入力や現在時刻に依存する計算 |
RustをHaskellの観点から見てみる
RustをHaskellの観点から見てみる
RustをHaskellの観点から見てみる
divide :: Float -> Float -> Maybe Float
divide 0 b = Nothing
divide a b = Just (b / a)
divide 4 (divide 3 (divide 2 1)) ←divideは引数としてMaybe Floatを受け取れないので✕
divide 4 <$> (divide 3 <$> divide 2 1) ←括弧の部分の評価値がMaybe Maybe Floatなので✕
RustをHaskellの観点から見てみる
Nothing >>= divide 3 == Nothing
Just 1 >>= divide 0 == Nothing
Just 1 >>= divide 2 == Just (1/2)
divide 2 <$> Just 1
Just 1 >>= divide 2
↑計算
↑入力
↑計算
↑入力
RustをHaskellの観点から見てみる
divide 2 <$> Just 1
divide 2 `flip (>>=)` Just 1
↑計算
↑入力
↑計算
↑入力
flip f x y == f y x
x `flip (-)` y == y - x
RustをHaskellの観点から見てみる
(<$>) :: (Functor f) => (a -> b) -> (f a -> f b)
flip (>>=) :: (Monad m) => (a -> m b) -> (m a -> m b)
RustをHaskellの観点から見てみる
RustをHaskellの観点から見てみる
Nothing >>= divide 3 == Nothing
Just 1 >>= divide 0 == Nothing
Just 1 >>= divide 2 == Just (1/2)
RustをHaskellの観点から見てみる
divide 2 1 >>= divide 3 >>= divide 4 == Just (1/24)
RustをHaskellの観点から見てみる
divide 2 1 >>= divide 3 >>= divide 4
== divide 2 1 >>= divide 3 >>= divide 4 >>= return
RustをHaskellの観点から見てみる
divide 2 1 >>= divide 3 >>= divide 4 >>= return
== divide 2 1 >>= (\a -> divide 3 a) >>= (\b -> divide 4 b) >>= (\c -> return c)
divide 2 1 >>= (\a ->
divide 3 a) >>= (\b ->
divide 4 b) >>= (\c ->
return c)
RustをHaskellの観点から見てみる
divide 2 1 >>= (\a ->
divide 3 a) >>= (\b ->
divide 4 b) >>= (\c ->
return c)
do
a <- divide 2 1
b <- divide 3 a
c <- divide 4 b
return c
糖衣化
RustをHaskellの観点から見てみる
divide 2 1 >>= divide 3 >>= divide 4
do
a <- divide 2 1
b <- divide 3 a
c <- divide 4 b
return c
等価
do記法はこんな感じに見える
実際には文脈に包まれた�世界で計算が行われている
さらに>>=の中身を見てみると
RustをHaskellの観点から見てみる
main :: IO ()
main = putStrLn $ show answer
answer :: Maybe Float
answer = do
a <- divide 2 1 - - 1を2で割ったものをaとする
b <- divide 3 a - - aを3で割ったものをbとする
c <- divide 4 b - - bを4で割ったものをcとする
return c - - cを返す
divide :: Float -> Float -> Maybe Float
divide 0 b = Nothing
divide a b = Just $ b / a
実行結果
Just 4.1666668e-2
↑「$」は開き括弧と同じ.
対応する閉じ括弧は行末にあることになる.
行末の「))))」を防ぐことができる.
RustをHaskellの観点から見てみる
extend :: String -> [String]
extend string = (: string) <$> filter (`notElem` string) ['A'..'C']
extend "" == ["A", "B", "C"]
extend "A" == ["BA", "CA"]
extend "AB" == ["CAB"]
RustをHaskellの観点から見てみる
RustをHaskellの観点から見てみる
main :: IO ()
main = putStrLn $ show answer
answer :: [String]
answer = do - - do記法では計算は分岐せず,一直線に進むと見なしてよい
a <- extend "" - - 空文字列の先頭に1文字追加した文字列をaとする
b <- extend a - - aの先頭に1文字追加した文字列をbとする
c <- extend b - - bの先頭に1文字追加した文字列をcとする
return c - - cを返す
extend :: String -> [String] - - 1つの文字列が複数の文字列に分岐する
extend string = (: string) <$> filter (`notElem` string) ['A'..'C']
実行結果
["CBA","BCA","CAB","ACB","BAC","ABC"]
RustをHaskellの観点から見てみる
do
a <- extend ""
b <- extend a
c <- extend b
return c
extend "" >>= (\a ->
extend a) >>= (\b ->
extend b) >>= (\c ->
return c)
脱糖
改行削除
extend "" >>= (\a -> extend a) >>= (\b -> extend b) >>= (\c -> return c)
ラムダ削除
extend "" >>= extend >>= extend >>= return
>>=return削除
extend "" >>= extend >>= extend
RustをHaskellの観点から見てみる
extend "" >>= extend >>= extend
RustをHaskellの観点から見てみる
([] >>= f) == []
(x:xs >>= f) == (f x ++ (xs >>= f))
↑リスト同士の結合
↑先頭要素xとし,それ以降をxsとするリストのパターン
["A", "B", "C"] >>= extend
"A" : ["B", "C"] >>= extend
extend "A" ++ (["B", "C"] >>= extend)
["BA", "CA"] ++ ("B" : ["C"] >>= extend)
["BA", "CA"] ++ extend "B" ++ (["C"] >>= extend)
["BA", "CA"] ++ ["AB", "CB"] ++ ("C" : [] >>= extend)
["BA", "CA"] ++ ["AB", "CB"] ++ extend "C" ++ ([] >>= extend)
["BA", "CA"] ++ ["AB", "CB"] ++ ["AC", "BC"] ++ []
["BA", "CA", "AB", "CB","AC", "BC"]
RustをHaskellの観点から見てみる
(>>= f) == concat . (f <$>)
["A", "B", "C"] >>= extend
(>>= extend) ["A", "B", "C"]
(concat . (extend <$>)) ["A", "B", "C"]
concat (extend <$> ["A", "B", "C"])
concat [extend "A", extend "B", extend "C"]
concat [["BA", "CA"], ["AB", "CB"], ["AC", "BC"]]
["BA", "CA", "AB", "CB", "AC", "BC"]
RustをHaskellの観点から見てみる
RustをHaskellの観点から見てみる
do記法はこんな風に見える
実際は文脈の世界で計算している
さらに>>=の中を見てみると
do
a <- extend ""
b <- extend a
c <- extend b
return c
RustをHaskellの観点から見てみる
Haskell | Rust |
Maybe | Option |
Nothing | None |
Just x | Some(x) |
リスト | Iterator |
↑VecではなくIterator
遅延評価で,評価時に有限になれば評価前に長さ無限であっても良いなど�共通点がVecよりも多い
RustをHaskellの観点から見てみる
Haskell | Rust |
Maybe文脈における<$> | Optionのmapメソッド |
リスト文脈における<$> | Iteratorのmapメソッド |
「f <$> x」または「fmap f x」 | 「x.map(f)」 |
「関数fをxの文脈において適用する」�「関数fをxの文脈の世界に投影する」 | 「xの中の値に関数fを適用する」 |
RustをHaskellの観点から見てみる
Haskell | Rust |
Maybe文脈における>>= | Optionのand_thenメソッド |
リスト文脈における>>= | Iteratorのflat_mapメソッド |
RustをHaskellの観点から見てみる
main :: IO ()
main = putStrLn $ show answer
answer :: Maybe Float
answer = do
a <- divide 2 1
b <- divide 3 a
c <- divide 4 b
return c
divide :: Float -> Float -> Maybe Float
divide 0 b = Nothing
divide a b = Just $ b / a
fn main(){
println!("{:?}", answer());
}
fn answer() -> Option<f64> {
divide(2.0, 1.0)
.and_then(|a| divide(3.0, a))
.and_then(|b| divide(4.0, b))
.and_then(|c| Some(c))
}
fn divide(a: f64, b : f64) -> Option<f64> {
match a {
0.0 => None,
a => Some(b / a),
}
}
Haskell
Rust
RustをHaskellの観点から見てみる
main :: IO ()
main = putStrLn $ show answer
answer :: [String]
answer = do
a <- extend ""
b <- extend a
c <- extend b
return c
extend :: String -> [String]
extend string = (: string) <$> filter (`notElem` string) ['A'..'C']
use std::iter;
fn main(){
let answer: Vec<String> = answer();
println!("{:?}", answer);
}
fn answer() -> Vec<String> {
extend("")
.flat_map(|a| extend(&a).collect::<Vec<String>>())
.flat_map(|b| extend(&b).collect::<Vec<String>>())
.flat_map(|c| iter::once(c))
.collect()
}
fn extend<'a>(string: &'a str) -> impl Iterator<Item = String> + 'a {
('A'..='C')
.filter(move |c| string
.chars()
.all(|c_in_string| c_in_string != *c))
.map(move |c| format!("{}{}", c, string))
}
Haskell
Rust
RustをHaskellの観点から見てみる
まとめ