
6か月間、主にGoでプログラミングしました。 そして、私は失望しています。 次の2つの理由から:
- Goは、機能的なパラダイムを順守するのが特に困難です。 実際、この言語は関数型プログラミングを妨げています 。 これは私を失望させました。なぜなら、私が書いた命令型コードには、多数のテンプレートの断片があるからです。 さらに、この場合、機能的な抽象化の使用とは対照的に、エラーのリスクが高いように思えます。
- Goはチャンスを失っていると信じています。 プログラミング言語では、(特に型チェックと型推論の分野で)驚くべき革新が現れ、コードをより安全で、速く、きれいにします。 Googleがこれらのアイデアの一部をサポートするためにその影響力を利用したいと思います。
このようにGoを認識するのは私が初めてではありません。 私の印象を共有している他の人々は次のとおりです。
以下に私の考えを追加します。 Goを改善する方法を示すために、Rustと比較します。
GoとRustの作業はほぼ同時に始まりました。Goは2009年に、Rustは2010年に発表されました。 どちらの言語も同様のアプローチを使用します。
- 高速なネイティブバイナリへのコンパイル。
- 構成を優先して継承を回避します。
- 命令型プログラミングのサポート。
- 誤った結果の明示的な送信を支持して、例外をキャッチすることを拒否します。
- マルチスレッド化を重視。
- 静的型チェック。
- モジュール性をサポートする最新のパッケージシステム。
どちらの言語もおそらくC ++を置き換えることを目的としていました。Go開発者は 、主な動機はC ++の複雑さに対する不満であると主張しました。 Servoは、MozillaのコアRust製品の1つです。 これは、C ++で記述されたGecko HTMLレンダリングエンジンの後継となる可能性があります。
私の意見では、これらの言語の主な違いは次のとおりです。
- Rustは、高性能で強力なサウンドの抽象化により適しています。 (健全性は、型によって作成された「ステートメント」がプログラム実行全体を通して尊重されることが保証されている場合の型システムの特性です。言語が健全であれば、型エラーは発生しません-実行時型エラー。)
- Goはアクセスしやすく、コンパイルが簡単で高速です。
つまり、Rustは必ずしもGoに取って代わるものではありません。 Goを使用してRustに切り替えることはお勧めしません。 Rustはリアルタイム操作をサポートしており、必要に応じてスタックメモリのみを処理できます。 Rustには洗練された型システムがあり、たとえば、コンパイル中に共有データへの同時アクセスを通じて問題を特定できます。 これにより、Rustプログラムの複雑さが増します。 同じ借用チェッカーは学習曲線で有名です。 しかし、特定のコンテキストでRustと比較して、Goを改善するためのオプションを示したいと思います。 Rustは他の言語から多くの優れたアイデアを借用し、それらを適切に組み合わせています。 そして、Goが同じアイデアを採用した場合、それは彼にとって良いことだと思います。
この記事のコード例はこちらから入手できます 。 そこで実行可能なテストと実験を行うことができます。
いいね
インターフェイスを使用してデータを構造化するには、Goが優れた方法であることがわかりました。
データ自体からの動作の分離が好きです。構造はデータを保存し、メソッドは構造内のデータを操作します。 これは、状態(構造)と動作(メソッド)の明確な分離です。 継承のある言語では、この違いはそれほど明白ではないと思う。
学ぶためだけに行ってください。 その中のオブジェクト指向のアイデアは、他のオブジェクト指向言語に精通したプログラマーがよりアクセスしやすくなるように修正されます。
多くの場合、かなりシンプルな「ゴースタイル」ソリューションが使用されます。 たとえば、Pythonについても同じことが言えます。 このような永続的なイディオムの出現は、Goプログラマーがおそらく他のGoプログラマーによって書かれたコードを理解することを示唆しています。 これはSimplicity and Collaborationで説明されている単純化の哲学の一部です。
Go標準ライブラリには、多くの精巧な機能があります。 私のお気に入りの1つ:
fiveSeconds := 5 * time.Second
, , . . Go : Erlang Scala (actors). Rust (concurrent) .
Rust, , Go. Rust , . — , enum’ (traits). , , . Go, Rust , , (type safety), . : Rust , .
Go. .
Go.
nil
null- , . , nil
(bottom-ish type), , (pass-by-reference type).
, nil
— null-. , nil
. Understanding Nil , , nil
, . Go nil
. : , , , nil
. runtime'a. , , , .
null
, , (, Fantom Flow). null. Flow , null, React:
function LoginForm(props) {
// `?` `HTMLInputElement` , `emailInput` `null`.
let emailInput: ?HTMLInputElement
// JSX- HTML
return <form onSubmit={event => props.onLogin(event, emailInput)}>
<input type="email" ref={thisElement => emailInput = thisElement} />
<input type="submit" />
</form>
}
function onLoginTake1(event: Event, emailInput: ?HTMLInputElement) {
event.preventDefault()
// ! `value` , , , `null` `undefined`.
dispatch(loginEvent(emailInput.value))
}
function onLoginTake2(event: Event, emailInput: ?HTMLInputElement) {
event.preventDefault()
if (emailInput) {
// , Flow , `emailInput` `null` `undefined`.
dispatch(loginEvent(emailInput.value))
}
}
null nil
, . Go , User
. , nil pointer dereference
:
func validate(user *User) bool {
return user.Name != ""
}
Go , , : «… nil
». , null, , .
nil
Go , nil
. (interface value) - , nil
, nil true. , nil
: , nil
. . --nil , (receiver value) nil
.
(zero value)? , nil
? , — .
Go — , . , , . , Go : , . ++ , . — . (, , ++ , .) , Go ++ . ! , : Rust, Flow . , .
: nil
, . (sensible) , nil
— . .
: Go (domain-specific types). , (soundness) . (, runtime-) . (pointer types) nil
-. , (sensible behavior) . . , .
Go :
. , , - , . , , (, ).
Rust
Rust null-
, nil
-. enum’. , , , , — . , enum . Option- (Option Pattern). Option
Rust:
pub enum Option<T> {
None, //
Some(T), //
}
None
Some
— : , Option<T>
. Some
, None
. Option<T>
, (pattern matching), , . (read back) . Some(x)
, x
.
Option- ():
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
if divisor == 0 {
// `None`
None
} else {
// `Some`
Some(dividend / divisor)
}
}
#[test]
fn divides_a_number() {
let result = checked_division(12, 4);
match result {
Some(num) => assert_eq!(3, num), // (bind) `num`
None => assert!(false, "Expected `Some` but got `None`")
};
}
Option- , null, , None
Some(None)
. , , , None
, . Some(None)
, — None
.
Option- , Java. , . Rust Option-, (zero-cost abstractions). Option<T>
(reference type), runtime None (null pointer). Some
None
. , null
-.
Option<i32>
, i32
. , , Some
None
. , .
« Rust» .
Go Option-. match
, . «» (). , Option
.
Go :
func doStuff() error {
_, err := doThing1()
if err != nil {
return err
}
_, errr := doThing2() // Error not propagated due to a bouncy key
if errr != nil {
return err
}
return nil
}
Rust Result<T,E>
, Option<T>
. , «» enum’ Result<T,E>
— ( E
). Result<T,E>
Ok(value)
( ) Err(err)
( ).
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Option Result - (successful) . . (first-class result values) , , .
Go:
func fetchAllBySameAuthor(postID string) ([]Post, error) {
post, err := fetchPost(postID)
if err != nil {
return nil, err
}
author, err := fetchUser(post.AuthorID)
if err != nil {
return nil, err
}
return fetchPosts(author.Posts)
}
Rust fetchAllBySameAuthor
. , , Option Result, — :
fn fetch_all_by_same_author(post_id: &str) -> Result<Vec<Post>, io::Error> {
let post = match fetch_post(post_id) {
Ok(p) => p,
Err(err) => return Err(err),
};
let author = match fetch_user(&post.author_id) {
Ok(a) => a,
Err(err) => return Err(err),
};
fetch_posts(&author.posts)
}
match
(pattern-match block). (pattern) , , . - Go, switch
. Rust . . : , .
Rust , Go. , Result<T,E>
Option<T>
nil
. Rust , .
Rust try!
, , . :
fn fetch_all_by_same_author(post_id: &str) -> Result<Vec<Post>, io::Error> {
let post = try!(fetch_post(post_id));
let author = try!(fetch_user(&post.author_id));
fetch_posts(&author.posts)
}
try!
. , try!(fetch_post(post_id))
fetch_post
match
Ok
Err
.
try!
, Rust : , ?
. , let post = try!(fetch_post(post_id));
let post = fetch_post(post_id)?;
. ?
, .
Go . , Result- . , , (combinator methods):
fn fetch_all_by_same_author(post_id: &str) -> Result<Vec<Post>, io::Error> {
let post = fetch_post(post_id);
let author = post.and_then(|p| fetch_user(&p.author_id));
author.and_then(|a| fetch_posts(&a.posts))
}
and_then
— Result<T,E>
. (successful result), , Result<U,E>
. — (error result), and_then
. and_then
then
Javascript.
, ? map_err
, .
let post = fetch_post(post_id)
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, e));
, : , — . DRY - . : Rust- . - (recovery code) .
Result<T,E>
« » , Option<T>
, enum . , Go. Go . Rust T
E
(, ), , Ok(value)
Err(err)
.
enum’ Rust , Result<T,E>
, . Result- Go? , Go (. . ), . , : Go, , ( Go — ). . , .
Go : , . Rust :
fn map<A, B, F>(callback: F, xs: &[A]) -> Vec<B>
where F: Fn(&A) -> B {
(input slice), , . , (iterator types) Rust , . . , , . , .
Go . , , (top-type) []interface{}
. :
func Map(callback func(x interface{}) interface{}, xs []interface{}) []interface{} {
ys := make([]interface{}, len(xs))
for i, x := range xs {
ys[i] = f(x)
}
return ys
}
. (, []int
) (type-compatible) []interface{}
. Map []int
. []interface{}
, for int
. Map , , . Map
, (runtime type assertion) (type switch) .
(type-compatible) interface{}
. interface{}
, :
func Map(callback interface{}, xs interface{}) interface{}
. . , API runtime , . Writing type parametric functions in Go. , . , .
: Filter
, Take
, Reduce
. . , — . Go , Map
, , Go. Go .
, , Go . . Javascript, Python Ruby . , . . , Javascript -, . Go : , , .
— — «» . « - ». , , , , the. ( . — . .)
Go (list abstractions). Go:
// `count`
func LatestTitles(docs []Document, count int) []string {
var latest []string
for _, doc := range docs {
if len(latest) >= count {
return latest
}
if !doc.IsArchived {
latest = append(latest, doc.Title)
}
}
return latest
}
, , - , . , filter
, map
, take
. Rust:
fn latest_titles(docs: &[Document], count: usize) -> Vec<&str> {
docs.iter()
.filter(|doc| !doc.is_archived)
.map(|doc| doc.title.as_str())
.take(count)
.collect()
}
Rust , . Rust . , . , . .
Go. : «, , ». , . Rust «» (lazily-evaluated) . filter
, map
, take
. filter
map
. , take(count)
, . , iter
, filter
, map
, take
collect
— , . , , filter
map
. « Rust» .
, , . , — . map
, , : « (mapping function)». . , for
.
(parallel-fetch). Go, :
func (client docClient) FetchDocuments(ids []int64) ([]models.Document, error) {
docs := make([]models.Document, len(ids))
var err error
var wg sync.WaitGroup
wg.Add(len(ids))
for i, id := range ids {
go func(i int, id int64) {
doc, e := client.FetchDocument(id)
if e != nil {
err = e
} else {
docs[i] = *doc
}
wg.Done()
}(i, id)
}
wg.Wait()
return docs, err
}
WaitGroup
, , , — , - .
, docs
. , docs
. , . (models.Document
, error
), Go …
Rust , , , (thread-unsafe) . , , . , Rust (concurrency) .
Go Rust, futures:
fn fetch_documents(ids: &[i64]) -> Result<Vec<Document>, io::Error> {
let results = ids.iter()
.map(|&id| fetch_document_future(id));
future::join_all(results).wait()
}
// `fetch_document_future` — .
Rust , Go: , . , . , Rust , . , Rust , , Go .
: map
, . .
Rust , fetch_document
Future
. future::join_all
Future
. Future Javascript: . Future, wait
. , Go, , Future Rust .
Future Stream
-. , Stream
.
—
Go «» make
. , , . Go, . , , (map) . make
, . , :
mySlice := make([]int, 16, 32)
. , .
, (overload) make
, . .
range
. , , :
for idx, value := range values { /* ... */ } // `range`
for idx := range values { /* ... */ } //
, range
. . . .
, ==
, >
, . .
, . Go. , , (collection types) .
Rust . , , Rust , . Rust , .
, make
range
— , Rust: , . Rust : . (trait method implementation) , , (, (equality trait) , equals
) .
make
Rust:
use std::sync::mpsc::{Receiver, Sender, channel};
// `trait`
trait Make {
fn make() -> Self;
fn make_with_capacity(capacity: usize) -> Self;
}
// `impl` .
// `Make` `Vec<T>`.
// `Vec<T>`.
impl <T> Make for Vec<T> {
fn make() -> Vec<T> {
Vec::new()
}
fn make_with_capacity(capacity: usize) -> Vec<T> {
Vec::with_capacity(capacity)
}
}
// `Sender` `Receiver` — (channel types) Rust.
// /.
impl <T> Make for (Sender<T>, Receiver<T>) {
// Rust . // , ( ).
// , impl.
fn make() -> (Sender<T>, Receiver<T>) {
channel()
}
fn make_with_capacity(_: usize) -> (Sender<T>, Receiver<T>) {
Make::make()
}
}
#[test]
fn makes_a_vec() {
// , `make`.
// , `make` .
let mut v: Vec<&str> = Make::make();
v.push("some string");
assert_eq!("some string", v[0]);
}
#[test]
fn makes_a_sized_vec() {
let v: Vec<isize> = Make::make_with_capacity(4);
assert_eq!(4, v.capacity());
}
#[test]
fn makes_a_channel() {
// , .
let (sender, receiver) = Make::make();
let msg = "hello";
let _ = sender.send(msg);
let result = receiver.recv().expect("a successfully received value");
assert_eq!(msg, result);
}
. : (crate), . ( — Rust.) Rust make
, .
make
make_with_capacity
, Rust (method overloading). Go .
Go . - , «».
,
Scala . Scala (tagged union) . : , .
Rust enum:
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread;
// , .
// , enum,
// .
#[derive(Clone, Copy, Debug)]
pub enum CounterInstruction {
Increment(isize), // `isize` — , platform word size
Reset,
Terminate,
}
pub type CounterResponse = isize;
use self::CounterInstruction::*;
pub fn new_counter() -> (Sender<CounterInstruction>, Receiver<CounterResponse>) {
// /
let (instr_tx, instr_rx) = channel::<CounterInstruction>();
let (resp_tx, resp_rx) = channel::<CounterResponse>();
//
thread::spawn(move || {
let mut count: isize = 0;
// , `recv()` `Err`
// `Ok`, .
while let Ok(instr) = instr_rx.recv() {
// ,
// . `enum` (sealed),
// .
// runtime-
// , .
match instr {
Increment(amount) => count += amount,
Reset => count = 0,
Terminate => return
}
// `send` `Result`, -
// . `_`,
// .
let _ = resp_tx.send(count);
};
});
//
(instr_tx, resp_rx)
}
#[test]
fn runs_a_counter() {
let (tx, rx) = new_counter();
let _ = tx.send(Increment(1));
let _ = tx.send(Increment(1));
let _ = tx.send(Terminate);
let mut latest_count = 0;
while let Ok(resp) = rx.recv() {
latest_count = resp
}
assert_eq!(2, latest_count);
}
Rust . Drop
. , - (cleanup code) . , , resp_tx
.
:
match instr {
Increment(amount) => count += amount,
Reset => count = 0,
Terminate => return
}
instr
, CounterInstruction
. CounterInstruction ; match
, .
Go , . Go :
switch instr := <-instrChan.(type) {
case Increment:
count += Increment.Amount
case Reset:
count = 0
case Terminate:
return
default:
panic("received an unexpected message!")
}
. . Rust , , . , , , , , . Rust- , , .
Go , . , , . Go (sealed): , , , , . ( ) , .
Go . interface{}
, . , . . , (unchecked type casts). () , . , .
runtime, . 100%- : , (code path) (type error).
, , , . Rust , (resolves types) . enum’ . , enum’ . .
:
unsafe
, Rust . . unsafe
, . unsafe
. .- Rust -. (resolve) . , . - Go, , -
nil
. , -, , , , .
,
Rust (resolve) . , Rust . , — -, , Go. , Rust -. Rust . . « Rust»: , -.
Go . , runtime (runtime reflection), .
Rust . (borrow-checking). Haskell 10 Go. Haskell , Rust Go. ( Rust — (type classes) Haskell.)
Go . , (composition over inheritance) , . . , Go, .
Go . ( Rust) , «». , . Go .
Make
new_counter
. :
//
// (`isize` — , platform word size)
fn min_and_max(xs: &[isize]) -> (isize, isize) {
let init = (xs[0], xs[0]);
xs.iter()
.fold(init, |(min, max), &x| (cmp::min(min, x), cmp::max(max, x)))
}
#[test]
fn consume_tuple() {
let xs = vec![1, 2, 3, 4, 5];
let (min, max) = min_and_max(&xs); //
assert_eq!(1, min);
assert_eq!(5, max);
}
- Go . , , :
//
results := make(chan (*models.Document, error))
, struct
, interface{}
. .
Go — , . Go and_then
Rust.
Go. « » , Go .
kachayev , . .
Rust, , -. futures - Tokio. - . « Haskell»:
, ( ) . — , .
— , (multiple threads of control). . , . , . .
Go . Rust , .
, Map-Reduce. (list abstractions). Rust Map-Reduce Rayon:
// `par_iter` .
// `par_iter` — Rayon, Rayon
// (standard slice types).
use rayon::prelude::*;
pub fn average_response_time(logs: &[LogEntry]) -> usize {
let total = logs.par_iter()
.map(|ref entry| entry.end_time - entry.start_time)
.sum();
total / logs.len()
}
map
, (job queues), (worker pool). map
. , , . , . , Rayon (batches), map
. ( — 5000 , .) sum
( — Reduce) Rayon. , (worker threads).
. . .
Go 2.0 ? . . , Go , nil, . Rust . (receiver-less methods). , , Go.
Go . , . , . Go, , .
, , Rust: , Go, , «».
Javascript , Go, ( ). Flow Typescript Go. , , Javascript Flow.
Erlang Scala , Go. .
Clojure , ! . Clojure .
Haskell , . . Haskell «».
, . Go — — . , . , . , .