Если вы уже наигрались с Go, устали от копипасты, ручного жонглирования типами и подумываете вернуться на какой-нибудь Python или, прости господи, PHP, то позвольте предложить вам попробовать язык D, где типизация хоть и тоже статическая, но она не путается под ногами и позволяет писать не менее выразительный код, чем на языках с динамической типизацией. А чтобы переход был не такой болезненный, вашему вниманию предлагается перевод Tour of the Go c эквивалентным кодом на D и краткими пояснениями.
Часть первая. Основы.
Hello World
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
module main;
import std.stdio;
void main()
{
// stdout.writeln( "Hello, 世界" );
writeln( "Hello, 世界" );
}
Разница не значительная, разве что в D пространство имён можно не указывать, если нет конфликта имён импортированных из разных модулей. Также стоит обратить внимание на обязательные точки с запятыми в конце строк — в D они, к сожалению, обязательны.
Packages
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
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
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))
}
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
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
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))
}
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
}
Оператор [] возвращает так называемый "срез", то есть массив элементов.
Variables
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
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
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)
}
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.
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)
}
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
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %qn", i, f, b, s) // 0 0 false ""
}
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
}
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
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
}
module main;
import std.stdio;
enum Big = 1L << 100; // Error: shift by 100 is outside the range 0..63
enum Small = Big >> 99;
Автор: vintage