Rust part20

■ このスレッドは過去ログ倉庫に格納されています
2023/03/03(金) 00:45:28.73ID:vTVY069B
公式
https://www.rust-lang.org/
https://blog.rust-lang.org/
https://github.com/rust-lang/rust

公式ドキュメント
https://www.rust-lang.org/learn

Web上の実行環境
https://play.rust-lang.org

※Rustを学びたい人はまず最初に公式のThe Bookを読むこと
https://doc.rust-lang.org/book/

※Rustを学ぶ際に犯しがちな12の過ち
https://dystroy.org/blog/how-not-to-learn-rust

※Rustのasyncについて知りたければ「async-book」は必読
https://rust-lang.github.io/async-book/

※次スレは原則>>980が立てること

前スレ
Rust part19
https://mevius.5ch.net/test/read.cgi/tech/1673926892/

ワッチョイスレ
プログラミング言語 Rust 4【ワッチョイ】
https://mevius.5ch.net/test/read.cgi/tech/1514107621/
2023/07/22(土) 02:40:42.91ID:htX2UcZa
>>784
アロケーションやフラグメンテーションはRustの言語システムの範囲外
例えばOSがない環境も含めてアロケータを自作もできるようになっている
普通にOSなどある環境を使うとRustはそこでの標準のアロケータをそのままグローバルアロケータとして用いる
つまり環境毎に異なるものが使われていてRustとは全く関係がない問題であることがわかる

もちろんRustでは自分でアロケータを変えることもできる
グローバルアロケータを丸ごと変える方法とBoxやVecなどで個別にアロケータを指定する方法がある
使用環境の標準のアロケータで何か問題が起きていると感じたらその例のように別のものと交換して比較してベストなアロケータを選べばよい

もう一つ全く別のアプローチとしてそれらアロケータに頼るのではなくプログラムのレベルでまとまったメモリの割り当て管理する方法もある
この方法は全て自分の制御下におけるため様々な特性のあるクレートがありもちろん自作もできる
2023/07/22(土) 02:50:52.30ID:htX2UcZa
>>784
後半の質問はもちろん再割り当てしてくれる
どの環境の標準アロケータでもそのような普通のことは当然している
だからアロケータの変更などは何か問題が起きてから考えればいい
質問の挙動ならばアドレスを表示させればわかる

fn main() {
// aを割り当て
let a = foo(123);
println!("a: {:p}", &*a);
// bを割り当て
let b = foo(456);
println!("b: {:p}", &*b);
// aを解放
std::mem::drop(a);
// cを割り当て
let c = foo(789);
println!("c: {:p}", &*c);
// aとcは同じアドレスになっている
}

fn foo(init: i32) -> Box<[i32; 1000]> {
std::boxed::Box::new([init; 1000])
}
2023/07/22(土) 08:56:00.85ID:40PxJ+ku
やはりそうですね
Rustはヒープエリアのメモリ管理は言語システムでは提供しない、その性能については規定しない立場なんですね
なので先の質問のような場合「Rustでは必ずうまいことやってくれる」とは言えず「コンパイラ××、実行環境××で実測したらうまく行った」程度しか言えず、同じソースを別の環境に持ってやったらダメだったもありうるんですね
結局ヒープを使わざるを得ない場合には「自分でメモリ割り当てをうまいことやる」しかないんですね
今やってる例だとスタックには入らないって怒られるし、ヒープ使うなら、うまくいくかはやってみるまでわからないというのはちょっと面白くないなぁ
2023/07/22(土) 09:24:29.44ID:htX2UcZa
>>787
OS環境などが提供するアロケータ(=Rustのデフォルトのアロケータ)をそのまま使う時だけ環境依存になる
これはRustに限らずそれをそのまま使う全てのプログラミング言語で起こるためRustの問題ではない
しかもRustは次のように解決策がある言語だ

アロケータ含めて同じソースを持っていってそれを使うならば動作も同じになるので大丈夫
例えば先ほどのケースのようにjemallocをアロケータとして指定して使えば環境依存ではなくなる
よほど特殊なケースでの特殊な効率化を目指さない限り自分でアロケータを書く必要はない
2023/07/22(土) 09:54:09.20ID:JFG0S3Tm
まぁそうですね
Rustにしたら今までのプログラミング言語で常に問題視されてたアロケーション問題が魔法のように解決するなどという事があるはずはないですね
2023/07/22(土) 09:59:59.87ID:htX2UcZa
何を問題にしているのか明確にせよ
Rustはアロケータすら自由に入れ替えられる
つまり理論上可能なことはRustならば解決できる
791デフォルトの名無しさん
垢版 |
2023/07/22(土) 10:40:08.11ID:S6pKsqQS
複オジーww
2023/07/22(土) 10:45:39.31ID:xU//sEEH
問題は普通のプログラミング言語の“メモリ割り当て問題”です
別にRust特有の問題ではなく、どんなプログラミング言語でも発生する問題ですよ
Rustではどういう戦略で当たってるのかなと
もちろんこの戦略に関して“どんな場合でもうまくいくまほうのような解決策”などありません、そして多くの場合この処理は大変重たい処理でとても重大な問題を引き起こします
画像処理、動画処理、AIの機械学習の処理などで大きなデータの割り当て、更新、消去は処理中かなり頻繁に起こり、データサイズによってはフラグメンテーションが大きな問題になる事などしょっちゅうです
結局プログラマはほとんどの場合自前でアロケーション問題を解決させられるわけでそこにもはや“言語のデフォルトに任せておけばいける”幻想は抱いてません
しかしRustはどうしてるんだろうと、もしRustでやれといわれた場合があったと妄想して自分にそれができるようにしときたいなと思って自習中なんです、まぁ絶対なさそうですが
今まで色々資料読んだ範囲内だと感覚的には「Rustはメモリ管理に関しては言語内部的には頑張らない、外部環境で処理するか、可読性を犠牲にガチャガチャしろ」というスタンスで提供されてる感じはしてます
2023/07/22(土) 11:31:54.16ID:l0FUWY0y
>>792
Rustは可読性を一切犠牲にすることなく、ソースコードそのままで、グローバルアロケータ指定だけを追加すればアロケータを変更できる
この件に関してRustは最も融通が効く優れた言語の一つ
2023/07/22(土) 11:35:08.26ID:nQhdUCcc
この人はそういう人なので適当に切り上げることをおすすめします
2023/07/22(土) 11:38:03.03ID:xU//sEEH
まぁ無理です
できないことをできると言い張るのはもはや学問ではなく信仰です
2023/07/22(土) 11:49:43.86ID:YLqzZrt5
A造る
B造る
A消す
C造る
AのサイズよりCのサイズが小さいとき
A-Cのサイズ分のフラグメントが発生
2023/07/22(土) 12:06:07.26ID:24bvSiNd
>>792
>> 今まで色々資料読んだ範囲内だと感覚的には「Rustはメモリ管理に関しては言語内部的には頑張らない、外部環境で処理するか、可読性を犠牲にガチャガチャしろ」というスタンスで提供されてる感じはしてます

メモリ管理の階層の違いを理解できていなさそうだが
メモリアロケータの話ならばRustはC++と同様に交換自由でライブラリ選択可能
可読性を犠牲にガチャガチャする必要はなくRustでは#[global_allocator]で指定するだけで交換可能
2023/07/22(土) 13:41:17.13ID:7Mz5wCRP
理解できてなかったらしい
もういいや
2023/07/22(土) 13:57:51.76ID:u4m8T///
>>792はC言語でたとえるとmallocを使ったメモリ管理とmallocの中のメモリ管理の違いをわかっていない初心者かな
2023/07/22(土) 13:58:55.74ID:NWfMWzFP
そうです
初心者でした
お騒がせして申し訳ありませんでした
801デフォルトの名無しさん
垢版 |
2023/07/22(土) 14:25:05.31ID:AxAuiZIS
>>796
AのサイズよりCのサイズが大きいときは
フラグメントは発生しないのかな?
2023/07/22(土) 14:42:32.24ID:QLIAp4G5
>>801
そんなに単純じゃないよ。
確保しようとする大きさによって違う領域から割り当てるような実装も普通。
順序良く割り当てるわけではなく、
そのときの状況によって効率的になるように采配する。

仮に断片化を意図的に起こしてでも総合的に断片化の確率を減らすような
戦略を持っているのかもしれないし、
よほど深刻な場合を除いて使う側はアロケータの中なんぞ忘れておくのが吉。
2023/07/22(土) 14:54:17.36ID:NWfMWzFP
もちろん発生しますよ
どんな場合でも最高次数にうまくいく魔法のような戦略なんてありません
なのでケースバイケースで場合に応じて戦略を使い分けなければならない
しかしOSの実行環境なんかのアロケーターはプログラムの内部情報を何一つ持ってないから「開きメモリのリストを持っておいて最初に見つかったスキマにアサインする、足りなくなったらGC」以外に戦略の採りようがない、だから従来のプログラミング言語は自分でGCするわけです、少なくともコンパイラはコンパイルする時点でどれくらいの頻度でどれくらいのサイズのアロケーション要求が発生するかある程度情報が得られるからそれを利用してよりよい割り当て戦略をとれるチャンスがあるからです、なんならその情報をコンパイラに教えるnotificationをつけられるように設計する事もできる
しかしそれとて限界がある、だからRustはもうそれすらやらない、アロケーションは自分でやれ、そのためのツールはある程度は言語内部で用意する、ダメなら言語外部のツール使え、という「無理してやってもどうせ“最適”には程遠いからやらない」んでしょう
逆に言えばRustでプログラム組むとき、特にでかいデータの作成、廃棄を繰り返すような場合はプログラマはかなり練度が必要になるんでしょう
立ち位置としてそれくらいの事がこなせるユーザーをプログラマとして想定してるという事です
学習コストが高いんじゃなくてそもそもプログラマに要求されてる水準が高めなんですね
2023/07/22(土) 16:19:13.40ID:2BLjTz4O
>>803
間違い多いな
まず一般的にGCはフラグメンテーションを解決しない
特にマークアンドスイープ方式のGCや参照カウント方式のGCはフラグメンテーションは放置で関与しない

Rustについての記述は間違いだらけでなんとも言いようがない
言語外部のツール使えとはトンデモすぎて何を言ってるのかもわからない

フラグメンテーションを起こし得るメモリアロケーターについてRustはC/C++と同じ立場で同じものを使う
C/C++/Rust共通の話として同じようにjemallocなど別のメモリアロケーターを使うことができる
Rustだけ特別な方法をとったり特別な性質をもったりはしておらずフラグメンテーションについてもC/C++と同じ
2023/07/22(土) 16:41:16.06ID:NWfMWzFP
素人なものですいません
スレ汚しすいませんでした
806デフォルトの名無しさん
垢版 |
2023/07/22(土) 17:02:30.53ID:NRmzieuj
>まず一般的にGCはフラグメンテーションを解決しない
マーク&スウィープ方式は一般的にコンパクションもやってるぞ
2023/07/22(土) 17:07:41.36ID:2BLjTz4O
>>806
コンパクションをするかしないかは独立した問題
マークアンドスイープ自体はコンパクションを伴わない
2023/07/22(土) 17:59:22.96ID:QLIAp4G5
>>807
再配置できないならそもそも論外なんだから
無関係ではないだろ。
2023/07/22(土) 18:05:22.75ID:YLqzZrt5
そういえば君らのメモリ管理の会話でLinkedListの使い道思い出したわ
Rustやその他の言語側じゃなくてOS側のメモリ管理にLinkedList使われてるわ
2023/07/22(土) 18:42:28.71ID:2BLjTz4O
>>808
再配置するのはコピーGCで使用中データ全移動と全ポインタ書き換えとなる
マークアンドスイープGCはその名の通り使用中を辿りマーク付けしてマークされなかったものを再び未使用領域とする
811デフォルトの名無しさん
垢版 |
2023/07/22(土) 19:01:55.55ID:TsQs+vXV
>>807
論点は”一般的”にやってるかどうかだろ
君は一般的にやってないと言ってるわけで
JavaもC#もJavaScriptもみんなGCでcompactionやってる
2023/07/22(土) 19:18:33.46ID:2BLjTz4O
>>811
compactionをする前にマークアンドスイープGC自体は既に終わっていて独立した別の行為
実際にそれら言語では世代別GCが行われていていて新入り若手のオブジェクトはコピーGCを行なうが古いオブジェクトはマークアンドスイープのみで再配置しない方法が一般的
2023/07/22(土) 19:31:27.25ID:nB6v7J6K
隔離スレ行けアスペジジー
2023/07/22(土) 20:04:44.58ID:2BLjTz4O
再配置はデータコピーとそこへのポインタ全て書き換えでコストが大きすぎるから可能な限り避けるのが正しい
世代別GCで若手オブジェクトだけに限ってコピーGCの対象にするのも若手の大半は一時的利用でコピーしなくて済むため

Rustではそんなコストをかけないだけでなくヒープ確保解放のコストすら減らすことと一石二鳥で解決する方法も取られている
例えばbumpalo crateはまとまった領域を持って追記していくだけで個別の解放コストゼロ(=何もしない)と個別の確保コストも最小(=追記のみor足りないとまとまったお代わり)のアリーナアロケータ
つまり未使用領域の再利用をしないことで管理不要となり最高速になるとともにまとめて一気に最高速で返す
具体的にはサーバーまたはバッチ処理で各対象固有データのみそこから確保してその対象を終える時に破棄
これはフラグメンテーションを防ぐのに非常に効果的な方法
815デフォルトの名無しさん
垢版 |
2023/07/22(土) 21:33:49.24ID:kHWj4RWJ
まーた複オジが知ったかぶりして無知晒してるw
初心者かなww
816デフォルトの名無しさん
垢版 |
2023/07/22(土) 22:04:25.70ID:kdu4dn9d
Rust使うくらいならJavaかC#で良いのでは?
2023/07/22(土) 22:08:51.83ID:wR/xGD2g
RustとC#だとどっちがいいの?
2023/07/23(日) 02:44:41.28ID:lmJrnSr9
>>814
GCのないRustが速いわけだ
2023/07/23(日) 11:09:10.60ID:kMNWXVHy
>>814
C/C++でも同じアルゴリズムのアロケータあるで
2023/07/23(日) 11:10:16.97ID:dOw1chPf
>>816
🤮
2023/07/25(火) 06:32:45.09ID:7X7HwnNv
>>819
言語に関係なくそこへ行き着くんだよな
もちろん通常はフラグメンテーションなんか気にせずに普通にコードを書けばよく
稼働時間の長いプロセスでフラグメンテーションが実際に起きた時の解決策の一つ
2023/07/27(木) 13:16:19.02ID:/bGsBsBb
play-rustのコードのコピペのやり方教えて下さい
具体的にはお題スレの

https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=f627f3a5de4a0c467f015a8b1527c141

のコードコピペする方法がわかりません
よろしくお願いします
2023/07/27(木) 14:04:04.46ID:90/I2Z5n
rustは型推論をしてくれると聞いたのですけど、結果どういう型になったか調べる方法はありますか?
Haskellのghciの:tに当たるやつです
例えはghci内で
prelude> let f n = sum [ x^n | x<- [1..5] ]
として
prelude> :t f
とすれば
( Num a, Integral b ) => b -> a
のように返してくれます
rustでコンパイラの型推論の結果どういう型に決定したのか調べる方法はありますか?
2023/07/27(木) 14:28:57.60ID:p+3LvAw4
>>823
LSP 対応のエディタ (VSCode など) を使ってるならそのへんにカーソルを合わせるだけで型は見れるよ。
2023/07/27(木) 21:59:53.82ID:x4QyUuiY
>>823
安直な方法としては関数の戻り型なら -> i32と嘘の型宣言を指定すれば型不一致で正しい具体型をコンパイルエラーに表示してくれる
プログラムで表示したいならこれ
fn get_type<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}
2023/07/27(木) 23:11:30.70ID:sTDTKxns
>>824
>>825
あざっす
2023/07/27(木) 23:12:33.60ID:paov2RlH
rustの型推論って曖昧なことを許容したっけ?
すべて一意にならないとならないと思ってたけど
2023/07/28(金) 19:39:32.86ID:4cjf/6GX
>>827
正しい
ジェネリックであってもパラメタは各々で必ず一意に静的に確定せねばならず
impl Traitで型宣言しても各々で一意に具体的な型に静的に確定する
特にそれが関数の引数ならば生成コードは単相化される

ただしdyn Traitはそれらが静的ではなく実行時のオブジェクト生成毎に個別に一意の具体的な型に確定する点で異なる
コンパイル時の静的な生成コードは複数の型で共有となるためvtableを持って対処している
2023/07/29(土) 16:54:29.41ID:Nh9kevR9
なるほど
Rustは最終的にコンパイルが終わって型が完全に推論が終わって単相型が決定した状態でしかわからないのかな
当然get_typeで得られるstrは推論終わって単相型が割り当てられた後の型しかわからないんやな
2023/07/29(土) 18:53:43.03ID:nTghkNr5
コードを書いた時点で型は決まるし書いた人は型を把握できる
ジェネリックな関数だとしても型パラメタ以外の部分は確定している
その関数を呼び出す側が最後のピースとして型パラメタを指定して完全確定する
2023/07/29(土) 19:01:27.37ID:mkN3FcGi
そうなんよねぇ
ML型型理論ならexpression毎に最も一般的な型が決まる
Haskellは型を指定しない場合自動的にその最も一般的な型を確定させてghciとかで調べられる
今のところ上がってる方法ではRustではそれに該当する方法はなさそう
2023/07/29(土) 19:02:57.30ID:mkN3FcGi
イヤまだvscideとかのやつは試してないな
これならできるんかな?
833デフォルトの名無しさん
垢版 |
2023/07/29(土) 20:36:12.76ID:JeVM9tS2
こいつダメだわ
ある意味複オジ2世
2023/07/29(土) 21:40:33.16ID:EPaDIYai
>>829
> Rustは最終的にコンパイルが終わって型が完全に推論が終わって単相型が決定した状態でしかわからないのかな
いやもうrustは変数宣言時に定義した型の変更を許さないのよ(基本的に)
だから型推論は変数宣言時のものを利用したり、関数の場合は返値、手打ちの数字の場合はデフォルトの型に決め打ちされて型推論されるわけ
いちいち型を調べる必要がない
ジェネリックに関しても基本的に場合分け
2023/07/29(土) 21:44:15.48ID:EPaDIYai
pythonとかjavascriptとか勝手に型を変更する(ような振る舞いをする→変更するとは言ってない)
言語なんてサイテーだと個人的には思う
2023/07/29(土) 22:05:07.65ID:2dUASh9D
>>834
どゆこと?
rustの型システムはしゃないの?
2023/07/29(土) 22:10:13.37ID:2dUASh9D
ごめん、誤字った
rustの型システムはMLじゃないの?
いわゆる型の全体と命題論理の全体を一対一に対応させる
で、その命題論理の目モデルを探す事=単相型を決定する事がMLの型推論システムで、Rustもそれ採用してるんだと思ってるんだけど
で単相型が割り当てられる“命題”に対応してるものが多相型と呼ぶんだと教えてもらった事あるけど
2023/07/29(土) 22:19:51.45ID:381IW7f/
も少し書くなら基本的にrustもhaskellも型システムは基本的な原理は同じじゃないのって話
それを説明する用語としてRust特定の説明の仕方をしてるのかもしれないけど説明の仕方や用語の使い方を変えたところでどっちもカリーハワード理論に基づく型システムじゃない?
2023/07/29(土) 22:51:50.32ID:381IW7f/
もし同じならHaskellと同じく例えば

mysum [] = 0
mysum (a:as) = a + ( mysum as )

なら Haskell ならmysumの型は

( Num a ) => [ a ] -> a

と推論されます
RustでもML型システムである限り少なくとも内部的には同じような型が割り当てられてるはずだと思うんですけど
もちろんこれら推論された型を全部総合して実装時には特定の単相型が割り当てられます
この辺のメカニズムはHaskellもRustも同じじゃないんでしょうか?
2023/07/29(土) 23:06:20.41ID:MBm8IaU2
まずRustの数値リテラルはHaskellと違って多相な型を持たないです
2023/07/29(土) 23:25:14.79ID:I6XWshKt
>>839
分かってる気になっておかしなことを書く癖を直したほうがいいと思うが…
2023/07/29(土) 23:36:28.14ID:I6XWshKt
人間としての推論機構が間違っている

基礎を勉強してルールとしてそれを覚えてから
それを基に推論を行わないと意味がないと知るべき
2023/07/29(土) 23:51:52.56ID:nTghkNr5
>>831
最も一般的な型ではダメでRustは唯一に確定しなければならない
Rustは関数(メソッド)の引数も返り値も型宣言が必須だけど
そこでの型はジェネリックで型パラメータを持つものも多く
その型パラメータのトレイト境界などによる制約とそのトレイト実装をする型が複数あることから複数解も生じる
複数解がありうるならばコンパイルエラーとなり明示的な型指定を求められる
唯一の例外が数値リテラルの利用により数値型までは確定している時でそれが整数ならば自動的にi32型に解決される
2023/07/30(日) 00:16:28.33ID:psEFm3Dt
>>840
その説明はよくrustの解説本にあるけど多分字面通りの意味にとってはいけないんじゃないかと
だってプログラム全体を見なければ型なんか決められるはずない
ML型の型システムである限りHaskellのexpressionから定まる最も一般的な多相型持ってると思う、でなければML型型システムと呼べない
さっきあげたmysumなんかその典型、あのmusumはnum classに属するすべての型で使うことができる、もしこのような多相型を持たない理論ならすべての足し算を持つクラス全てにほとんど同じsumを定義させられることになる、それを避けられるのがML型システムの1番の売りなんだから
てか上の例のmysumに相当する書き方Rustでもできるんでしょ?
無理なん?
2023/07/30(日) 00:18:56.60ID:psEFm3Dt
ML型システムと呼べないは言い過ぎかな?
まぁ型宣言省略しても型推論してくれるならML型型システムと呼べなくはないけど、でも流石にさっきのmysumみたいな記述は許されてるんじゃないの?
2023/07/30(日) 00:19:43.44ID:dT6HJfPv
ハスケル!
ハスケル!
ハスケル!

その毎度のハスケルおじさんに構うのは止めとけよ
Rubyおじさんと変わらないんだからさあ
自覚のない荒らしだよ
2023/07/30(日) 00:19:47.09ID:psEFm3Dt
>>843
そうなん?
じゃあさっきのmysumみたいな書き方はRustではできないのあ?
2023/07/30(日) 00:21:02.70ID:dT6HJfPv
Haskellおじさんは自分の思いをここに書かないと死んじゃうの?
なんでRustをRustとして扱いたくないの?
2023/07/30(日) 00:21:05.69ID:psEFm3Dt
>>846
目障りなら無視してくれていいけど純粋に新しいプログラミング言語に挑戦してるだけだから邪魔しないでくれる?
君は一生ひRustに捧げればいいやん
2023/07/30(日) 00:22:04.96ID:dT6HJfPv
ハスケルおじさんは壁に向かってハスケルハスケル行ってればいいのに
2023/07/30(日) 00:24:01.50ID:dT6HJfPv
マクロしらんの?
2023/07/30(日) 00:24:09.72ID:psEFm3Dt
>>848
すでに習得した言語の対比して新しい言語に挑戦するのは当たり前やろ?
Haskellの型システム理解するのにどれだけの時間をかけて教科書を読み、論文を読み、プログラミングを組んでみたか、その資産を使いたいとおもうのがそんなに悪いんか?
黙っといてくれよ
2023/07/30(日) 00:24:40.66ID:dT6HJfPv
>>852
悪いに決まってるだろ?
そんなこともわからんの?
2023/07/30(日) 00:26:23.01ID:dT6HJfPv
論文読まないとプログラム言語が理解できないんだから困ったやつだろ
推論システムが変
2023/07/30(日) 00:29:41.31ID:dT6HJfPv
まずは"真面目にRust学習"しろ
そして習得しろ
そして疑問を解決しろ
2023/07/30(日) 00:32:06.97ID:psEFm3Dt
>>854
すまんけどオレは論文なんか読まなくても理解できるような天才じゃないんだよ
君は論文なんぞ読まなくても何もかもわかる天才なのかもしれんけど世の中そんな天才ばかりじゃないんだよ
オレみたいに寝食忘れて努力に努力を重ねてやっと話しが分かる鈍才もいるんだよ
鈍才が悪あがきするの邪魔せんでくれ
2023/07/30(日) 00:33:26.30ID:psEFm3Dt
>>855
もうオレに関わらないで
その方が双方幸せじゃないか?
858デフォルトの名無しさん
垢版 |
2023/07/30(日) 02:16:35.97ID:ArPWIRVu
大先生3人に共通するのは
自分は分かってるという根拠のない思い込みと
何度指摘されても全く反省せず同じことを繰り返す傍迷惑な行動
2023/07/30(日) 02:39:34.14ID:g1ghpAt+
>>847
consセルを使った簡易なリスト操作表記をRustは言語仕様レベルではサポートしていない
だからそのHaskellのmysumをその表現のまま移植するのは本質的ではないため
意味だけを重視してリストをイテレータで表現し再帰定義している点を尊重すると

use std::ops::Add;
use num::Zero;

fn mysum<T>(mut iter: impl Iterator<Item = T>) -> T
where
T: Zero + Add<Output = T>,
{
match iter.next() {
Some(n) => n + mysum(iter),
None => T::zero(),
}
}

一例としてこのようにmysumはジェネリックに書ける
mysumに最低限必要なトレイト境界(Zero + Add)のみを課している
もちろんこの関数だけで以下のように機能して計算結果も合う
assert_eq!(15, mysum([1, 2, 3, 4, 5].into_iter()));
2023/07/30(日) 02:43:03.72ID:g1ghpAt+
>>859のmysumでトレイト境界はZeroとAddさえ満たしていれば通常の数値でなくても構わないので
例えば以下のようなHoge型を定義して必要なトレイト境界(ZeroとAdd)を実装すると

#[derive(PartialEq, Debug)]
struct Hoge(usize);
impl Zero for Hoge {
fn zero() -> Self { Hoge(0) }
fn is_zero(&self) -> bool { self.0 == 0 }
}
impl Add for Hoge {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Hoge((1 << (self.0.trailing_ones() + rhs.0.trailing_ones())) - 1)
}
}

先程と同じように「15 = 1 + 2 + 3 + 4 + 5」をHoge型で表現することで
Hogeについてもmysum()は以下のようにきちんと計算できていることがわかる
assert_eq!(Hoge(0b111111111111111), mysum([Hoge(0b1), Hoge(0b11), Hoge(0b111), Hoge(0b1111), Hoge(0b11111)].into_iter()));

Haskellに全く詳しくないので逆に質問というか教えてほしい
このHoge型をHaskellで定義(実装)するにはどのようなコードになるのか?
そしてこのHoge型に対してもHaskell版>>839のmysumで使えるようにするにはどうするのか?
たぶん型クラスNumの代わりに型クラスZeroとAddを指定するだけで済むのだろうけど
もしHaskellに型クラスZeroやAddがなければそれを定義する形になるのだろうか?
よろしくお願いします
2023/07/30(日) 10:57:59.43ID:TSIasAnA
>>860
Haskellではこうなります
https://ideone.com/PKy694

mysum [] = 0
mysum ( a : as ) = a + ( mysum as )

hogePlus a 0 = a
hogePlus a b | even b = 2 * (hogePlus a ( div b 2 ) )
hogePlus a b = 1 + 2 * (hogePlus a ( div b 2 ) )

data Hoge = H Integer deriving Show

instance Num Hoge where
( H x ) + ( H y ) = H ( hogePlus x y )
fromInteger = H

main = do
print $ mysum [ 1..5 ]
print $ hogePlus 3 7
print $ mysum [ H 1, H 3, H 7, H 15, H 31 ]
2023/07/30(日) 10:58:19.77ID:TSIasAnA
この例だとmysumの型は
mysum :: ( Num a ) => [ a ] -> a
と推論されます
すなわちmysumは

 aがNum class に属する型aに対して[a]型の値に対してa型の値を返す関数の型

です
よってmysumをある型Hogeで利用するにはHogeにNumインスタンスを導入しなければなりません、すなわちRustでは
impl Num for Hoge { ... }
のように書く部分です( >> 860 のAdd,Zeroを実装してる部分に該当します)
本来はNumのminimal complete definition全部実装するべきでHaskell1990までは“定義が揃ってない”とおこられましたが。2010以降は必要な分だけ実装すれば良い、ただし実行時エラーは覚悟しろという風に変更されてるようです(未確認、コンパイラがエラー吐かなくなったので)
2023/07/30(日) 12:33:59.93ID:g1ghpAt+
>>861
RustのAddトレイト実装によって足し算(a + b)のみを他の型でも自由に使えるようにできる機能をHaskellは持っていないということ?
そのためHaskellではやむを得ない代替処置としてHoge型を型クラスNumへ入れてしまうことで解決したということ?

例えばRustではStringについてもAddトレイト実装があり
let s = String::from("abc");
assert_eq!(s + "xyz", String::from("abcxyz"));
といったこともできます
同様に自作の型に対しても足し算のみの実装が可能です
Haskellではこのようなケースでもオーバースペック気味にNumを使うしかないのでしょうか?
2023/07/30(日) 12:37:15.22ID:g1ghpAt+
>>862
Haskellでは実行時エラーを覚悟しろという点は非常に困りますね
今回のケースも無理やりにNumへ入れてしまったのに足し算しか実装していないから引き算や掛け算などで実行時エラーとなるわけですよね

ちなみにRustのnumクレートでは
trait Num: Zero + One + NumOps + PartialEq とトレイト境界があり
impl NumOps for T where T: Add + Sub + Mul + Div + Rem {} の実装があります
つまり自作の型で使う場合は全ての演算を実装した上で更に加えて
impl Num for Foo とするとようやくNumになれます
したがってRustでは不備があればコンパイル時エラーとなり安心して安全に使うことができます
2023/07/30(日) 12:47:58.55ID:wjjxPYUe
そろそろ隔離スレ行ってろカスども
2023/07/30(日) 14:02:34.57ID:iwDEPHME
Haskell 的には演算子オーバーロードを目的として型クラスを使うことはしない。
Num な性質を持つ値の操作として + という名前の演算子もあるというだけで、
演算子として + を使いたいから型クラスの実装を定義するということはしないってこと。
それに、 Num はいくつかの演算子を使えるというだけではなく
それらの演算子の関係で満たさなければいけない性質があるので個々の演算子 (に結び付く型クラス) として分解できない。
https://hackage.haskell.org/package/base-4.18.0.0/docs/Prelude.html#t:Num

価値観が違う。

なんせ Haskell では記号を組み合わせていくらでも演算子を作ることが出来て
たとえば <$> とか >>= とかいう変な演算子を必要なだけ作れるので少ない演算子を取り合う必要がない。

Num の性質の中で + を使うってのと、
なんらかの足し算っぽい操作は別の名前で定義されているべきだし、
Haskell ではそれで困らない。

価値観が違う。
2023/07/30(日) 18:06:30.12ID:mgfeU+D6
新設の見慣れぬ演算子の種類を勝手に増やして各々が異なる意味付けすることは可読性を下げるのみで百害あって一利無し

>>862
コンパイラがエラーを吐かずに実行時エラーとなるのはマズいな
Haskellの価値観はよくない
2023/07/30(日) 19:58:32.75ID:Llc746TG
すいません、筆が滑りました
もちろんコンパイル時にエラー吐きます
以前のHaskellだと例えそのプログラムで使われてない場合でも実装しないとエラー吐いてました
しょうがないのでwhere句に使いもしないのに
_ * _ = undefined
signum _ = undefined
....
を書いてました
流石に無意味すぎて使わないのは書かなくて良いことになったようです
使ってるのに未実装はもちろんコンパイラが見つけてくれます
2023/07/30(日) 20:06:02.24ID:iwDEPHME
どうでもいいけど signum という名前を見るとヴォルケンリッターを連想してしまう……
2023/07/31(月) 08:16:09.72ID:G+nEO2Oo
どうでもいいなら・・・煽りたくなるな
2023/07/31(月) 10:23:47.98ID:8wbRk2dY
use Hoge::prelude::*; って良く観るがダサいなー
2023/07/31(月) 10:35:07.69ID:i3Lje9zm
でもまあ機能ごとにモジュールを整理しても
それとは別によく使う集合があるのも現実だし。
2023/07/31(月) 11:07:00.50ID:sgBBFIN2
global 禁止(キリっ
2023/07/31(月) 12:25:28.59ID:lZja90Kc
>>871
preludeは邪道で非推奨
自動適用される標準ライブラリのprelude以外は使わない&設けない
例えばtokio::preludeも廃止された
2023/07/31(月) 12:34:21.81ID:eCR2qI4e
Rustで、Vecに要素を追加したとき、メモリ不足で
メモリ確保に失敗した場合、どうなるんですか?
C++の場合は「例外」がthrowされますが。
2023/07/31(月) 14:38:56.18ID:uDpaCeqo
pqnic
2023/07/31(月) 16:40:54.67ID:cE0Z6rmj
ピクニック?
2023/07/31(月) 17:53:43.20ID:1dCFbVL1
fn っていちいち略さなくても function でよくない?

書き込める変数の宣言に mut ってつけるのもダサい
2023/07/31(月) 18:05:43.81ID:+bjI2PCn
mutはガチでダサい
何かいい記号はなかったのかとw
2023/07/31(月) 18:13:53.64ID:+bjI2PCn
英語圏の人たちはmut見てcoolに感じるとか日本人と感性が違う可能性がある
2023/07/31(月) 18:38:57.32ID:9nT3Yxeb
>>878
感性が古いよ
2023/07/31(月) 18:48:05.02ID:STr6yd2M
今までミュートと読んでいた
2023/07/31(月) 18:49:54.52ID:A3cMstwB
unicode解禁にして変にすればいい
884デフォルトの名無しさん
垢版 |
2023/07/31(月) 20:49:15.69ID:X0OEUfKN
>>881
短い略語を使いたがるのはメモリ容量が少なかった昔の習性・感性だろ。
昔に作られた言語のFortranとPascalですらfunctionは略さなかったのに、
今になって変な略語を採用したRust開発者は工学的センスも美的センスもない。

比較的新しい言語のF#はmutと略さずにmutableと書く。
■ このスレッドは過去ログ倉庫に格納されています
5ちゃんねるの広告が気に入らない場合は、こちらをクリックしてください。

ニューススポーツなんでも実況