[Перевод] man!( Go => D ).basics
Если вы уже наигрались с Go, устали от копипасты, ручного жонглирования типами и подумываете вернуться на какой-нибудь Python или, прости господи, PHP, то позвольте предложить вам попробовать язык D, где типизация хоть и тоже статическая, но она не путается под ногами и позволяет писать не менее выразительный код, чем на языках с динамической типизацией. А чтобы переход был не такой болезненный, вашему вниманию предлагается перевод Tour of the Go c эквивалентным кодом на D и краткими пояснениями.
Hello World
Go
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
D
module main;
import std.stdio;
void main()
{
// stdout.writeln( "Hello, 世界" );
writeln( "Hello, 世界" );
}
Разница не значительная, разве что в D пространство имён можно не указывать, если нет конфликта имён импортированных из разных модулей. Также стоит обратить внимание на обязательные точки с запятыми в конце строк — в D они, к сожалению, обязательны.
Packages
Go
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
D
module main;
import std.stdio;
import std.random;
void main()
{
writeln( "My favorite number is ", uniform( 0 , 10 ) );
}
Тут тоже всё одинаково, разве что в Go при импорте указывается путь к модулю, а в D используется имя модуля, задаваемое директивой «module», или автоматически выводимое из пути к файлу, если эта директива не указана.
Imports
В Go рекомендуется группировать импорты в одну директиву.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.", math.Sqrt(7))
}
В D тоже так можно, но особенности синтаксиса не располагают к этому:
module main;
import
std.stdio,
std.math;
void main()
{
writefln( "Now you have %f problems.", 7f.sqrt );
}
Кроме того, в D импорты можно указывать в любом блоке, а не только в начале файла:
module main;
void main()
{
import std.stdio;
{
import std.math;
writefln( "Now you have %f problems.", 7f.sqrt );
}
writefln( "Now you have %f problems.", 7f.sqrt ); // Error: no property 'sqrt' for type 'float'
}
Exported names
В Go модуль экспортирует лишь то, что начинается с большой буквы:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.pi) // cannot refer to unexported name math.pi
}
В D же экспортируется лишь то, что объявлено в public секции модуля (которая по умолчанию), либо помечено модификатором доступа public:
module math;
import std.math;
auto PI = std.math.PI;
private:
public auto pip = std.math.PI;
auto pi = std.math.PI;
module main;
import std.stdio;
import math;
void main()
{
writeln( PI );
writeln( pi ); // Error: module main variable math.pi is private
writeln( pip );
}
Подробнее о модульной системе D.
Functions
Go
package main
import "fmt"
// func add(x int, y int) int {
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
D
module main;
import std.stdio;
int add( int x , int y )
{
return x + y;
}
void main()
{
// writeln( add( 42 , 13 ) );
writeln( 42.add( 13 ) );
}
В Go тип обычно следует в конце, а в D — более традиционно — в начале. Кроме того, любую функцию в D можно вызвать как метод, что позволяет элегантно расширять сторонние типы. Go же позволяет не повторять одинаковые типы идущих друг за другом параметров. Тут же стоит упомянуть отсутствующее в Go обобщённое программирование, позволяющее реализовать функцию сразу для любых подходящих типов:
module main;
import std.stdio;
auto add( X , Y )( X x , Y y ) {
return x + y; // Error: incompatible types for ((x) + (y)): 'int' and 'string'
}
void main()
{
// writeln( 42.add!( int , float )( 13.3 ) );
writeln( 42.add( 13.3 ) ); // 55.3
writeln( 42.add( "WTF?" ) ); // Error: template instance main.add!(int, string) error instantiating
}
В D для любой функции можно указать в дополнительных круглых скобках параметры времени компиляции, куда можно либо явно передать типы, либо они могут быть выведены автоматически компилятором из типов аргументов.
Multiple results
Go
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
В D нет возможности возвратить из функции несколько отдельных значений, но можно вернуть кортеж:
module main;
import std.stdio;
import std.typecons;
auto swap( Item )( Item[2] arg... )
{
return tuple( arg[1] , arg[0] );
}
void main()
{
auto res = swap( "hello" , "world" );
writeln( res[0] , res[1] ); // worldhello
}
А при необходимости можно и распаковать возвращаемый кортеж в уже существующие переменные:
module main;
import std.stdio;
import std.meta;
import std.typecons;
auto swap( Item )( Item[2] arg... )
{
return tuple( arg[1] , arg[0] );
}
void main()
{
string a , b;
AliasSeq!( a , b ) = swap( "hello" , "world" );
writeln( a , b ); // worldhello
}
Named return values
Go
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
Достаточно сомнительный синтаксический сахар. D ничего такого не поддерживает, так что возвращаем либо структуру, либо опять же кортеж, но с именованными элементами:
module main;
import std.stdio;
import std.typecons;
auto split( int sum )
{
auto x = sum * 4 / 9;
auto y = sum - x;
return tuple!( "x" , "y" )( x , y );
}
void main()
{
// auto res = split( 17 ); writeln( res.x , res.y );
// writeln( split( 17 )[] );
writeln( 17.split[] ); // 710
}
Оператор [] возвращает так называемый «срез», то есть массив элементов.
Подробнее о кортежах в D.
Variables
Go
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
D
module main;
import std.stdio;
// bool c , python , java;
bool c;
bool python;
bool java;
void main()
{
int i;
writeln( i , c , python , java ); // 0falsefalsefalse
}
В целом, объявления переменных очень похожи, разве что синтаксис Go несколько более многословен.
Short variable declarations
Go
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
D
module main;
import std.stdio;
void main()
{
int i = 1 , j = 2;
auto k = 3;
auto c = true , python = false , java = "no!";
writeln( i , j , k , c , python , java ); // 123truefalseno!
}
Оба языка умеют выводить тип переменной из инициализирующего выражения. Однако подход Go с разделением объявления переменных на список имён и список значений довольно не нагляден и провоцирует ошибки.
Basic types
Таблица соответствия типов:
Go D
---------------------
void
bool bool
string string
int int
byte byte
int8 byte
int16 short
int32 int
int64 long
uint unint
uint8 ubyte
uint16 ushort
uint32 uint
uint64 ulong
uintptr size_t
ptrdiff_t
float32 float
float64 double
real
ifloat
idouble
ireal
complex64 cfloat
complex128 cdouble
creal
char
wchar
rune dchar
Существенное различие в том, что размер int и uint в Go зависит от платформы, а в D — не зависит. Также D контролирует, чтобы мнимые числа не перепутались с реальными. Кроме того, D позволяет работать с вещественными числами большего размера (80 бит), а с символами — меньшего (8 и 16 бит). Подробнее о типах в D.
Go
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
const f = "%T(%v)\n"
fmt.Printf(f, ToBe, ToBe)
fmt.Printf(f, MaxInt, MaxInt)
fmt.Printf(f, z, z)
}
D
module main;
import std.stdio;
import std.math;
bool ToBe = false;
ulong MaxInt = ulong.max;
cdouble z = sqrt( -5 + 12i );
void main()
{
enum f = "%s(%s)";
writefln( f , typeid( ToBe ) , ToBe ); // bool(false)
writefln ( f , typeid( MaxInt ) , MaxInt ); // ulong(18446744073709551615)
writefln( f , typeid( z ) , z ); // cdouble(2+3i)
}
В D у каждого типа есть свойства, позволяющие получить основные связанные с типом константы. Стоит обратить внимание, что в D константы времени компиляции создаются через ключевое слово «enum» — их значение инлайнится в место их использования. А вот ключевое слово «const» имеет несколько иное значение — это модификатор доступа, запрещающий нам изменять значение переменной (но в другом месте программы у нас может быть доступ на редактирование).
Zero values
Go
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s) // 0 0 false ""
}
D
module main;
import std.stdio;
void main()
{
writefln( "%s %s %s \"%s\"" , int.init , double.init , bool.init , string.init ); // 0 nan false ""
}
В D у каждого типа есть специальное поле «init», хранящее значение по умолчанию для этого типа.
Type conversions
Go требует ручного перевода значения из одного типа в другой:
package main
import (
"fmt"
"math"
)
func main() {
var x int = 3
var y uint = 4
var f float64 = math.Sqrt(float64(uint(x*x) + y*y))
var z uint = uint(f)
fmt.Println(x, y, z) // 345
}
D достаточно умён, чтобы требовать ручного перевода типов лишь когда это может привести к потере данных:
module main;
import std.stdio;
import std.conv;
void main()
{
int x = 3;
uint y = 4;
double f = ( x^^2 + y^^2 )^^0.5;
uint z = f.to!uint;
writeln( x , y , z ); // 345
}
Numeric Constants
Численные константы в Go позволяют задавать числа, которые невозможно использовать в рантайме без потерь:
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small)) // 21
fmt.Println(needInt(Big)) // constant 1267650600228229401496703205376 overflows int
fmt.Println(needFloat(Small)) // 0.2
fmt.Println(needFloat(Big)) // 1.2676506002282295e+29
}
В D при компиляции используются те же типы, что и при выполнении, так что и значения констант имеют те же ограничения:
module main;
import std.stdio;
enum Big = 1L << 100; // Error: shift by 100 is outside the range 0..63
enum Small = Big >> 99;
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.