Если вы уже наигрались с 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 %q\n", 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) + uint(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;
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
ссылка на оригинал статьи https://habrahabr.ru/post/279657/
Добавить комментарий