गो जनरिक्स: उपयोग के मामले और पैटर्न

गो जनरिक्स के साथ टाइप-सेफ रीयूज़ेबल कोड

Page content

Go में Generics Go 1.0 के बाद जोड़ें गए सबसे महत्वपूर्ण भाषा विशेषताओं में से एक का प्रतिनिधित्व करते हैं। Go 1.18 में पेश किए गए, Generics आपको कई प्रकारों के साथ काम करने की अनुमति देते हैं बिना प्रदर्शन या कोड स्पष्टता को बलिदान किए, जबकि कंपाइल-टाइम टाइप सेफ्टी बनाए रखते हैं।

इस लेख में Go प्रोग्राम में Generics का उपयोग करने के लिए व्यावहारिक उपयोग मामलों, सामान्य पैटर्न और सर्वोत्तम प्रथाओं का पता लगाया जाता है। अगर आप Go के नए हैं या मूलभूत अवधारणाओं पर एक रिफ्रेशर की आवश्यकता है, तो हमारे Go Cheatsheet के लिए देखें जो आवश्यक भाषा संरचनाओं और सिंटैक्स के लिए है।

a-lone-programmer

Go Generics को समझना

Go में Generics आपको टाइप पैरामीटर्स द्वारा पैरामीटराइज्ड फंक्शंस और टाइप्स लिखने की अनुमति देते हैं। यह उसी लॉजिक को विभिन्न प्रकारों के साथ काम करने की आवश्यकता होने पर कोड डुप्लिकेशन की आवश्यकता को समाप्त करता है, जबकि कंपाइल-टाइम टाइप सेफ्टी बनाए रखता है।

बुनियादी सिंटैक्स

Generics के लिए सिंटैक्स टाइप पैरामीटर्स को घोषित करने के लिए स्क्वायर ब्रैकेट्स का उपयोग करता है:

// Generic function
func Max[T comparable](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// Usage
maxInt := Max(10, 20)
maxString := Max("apple", "banana")

टाइप कंस्ट्रेंट्स

टाइप कंस्ट्रेंट्स निर्दिष्ट करते हैं कि आपके Generic कोड के साथ किस प्रकार के प्रकार का उपयोग किया जा सकता है:

  • any: कोई भी प्रकार (समान है interface{} के साथ)
  • comparable: प्रकार जो == और != ऑपरेटर्स का समर्थन करते हैं
  • कस्टम इंटरफेस कंस्ट्रेंट्स: अपने स्वयं के आवश्यकताओं को परिभाषित करें
// एक कस्टम कंस्ट्रेंट का उपयोग करते हुए
type Numeric interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64
}

func Sum[T Numeric](numbers []T) T {
    var sum T
    for _, n := range numbers {
        sum += n
    }
    return sum
}

सामान्य उपयोग मामले

1. Generic डेटा संरचनाएं

Generics का सबसे आकर्षक उपयोग मामलों में से एक पुन: उपयोग योग्य डेटा संरचनाएं बनाना है:

// Generic Stack
type Stack[T any] struct {
    items []T
}

func NewStack[T any]() *Stack[T] {
    return &Stack[T]{items: make([]T, 0)}
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

// Usage
intStack := NewStack[int]()
intStack.Push(42)
intStack.Push(100)

stringStack := NewStack[string]()
stringStack.Push("hello")

2. स्लाइस यूटिलिटीज

Generics स्लाइस मैनिपुलेशन फंक्शंस लिखने को आसान बनाते हैं:

// Map function
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Filter function
func Filter[T any](slice []T, fn func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce function
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

// Usage
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })
evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
sum := Reduce(numbers, 0, func(acc, n int) int { return acc + n })

3. Generic Map यूटिलिटीज

Generics के साथ मैप्स काम करना अधिक टाइप-सेफ़ हो जाता है:

// Get map keys as a slice
func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

// Get map values as a slice
func Values[K comparable, V any](m map[K]V) []V {
    values := make([]V, 0, len(m))
    for _, v := range m {
        values = append(values, v)
    }
    return values
}

// Safe map get with default value
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
    if v, ok := m[key]; ok {
        return v
    }
    return defaultValue
}

4. Generic Option पैटर्न

Option पैटर्न Generics के साथ अधिक सुंदर हो जाता है:

type Option[T any] struct {
    value *T
}

func Some[T any](value T) Option[T] {
    return Option[T]{value: &value}
}

func None[T any]() Option[T] {
    return Option[T]{value: nil}
}

func (o Option[T]) IsSome() bool {
    return o.value != nil
}

func (o Option[T]) IsNone() bool {
    return o.value == nil
}

func (o Option[T]) Unwrap() T {
    if o.value == nil {
        panic("attempted to unwrap None value")
    }
    return *o.value
}

func (o Option[T]) UnwrapOr(defaultValue T) T {
    if o.value == nil {
        return defaultValue
    }
    return *o.value
}

उन्नत पैटर्न

कंस्ट्रेंट कंपोजिशन

आप कंस्ट्रेंट्स को संयोजित कर सकते हैं ताकि अधिक विशिष्ट आवश्यकताएं बनाई जा सकें:

type Addable interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64 | string
}

type Multiplicable interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64
}

type Numeric interface {
    Addable
    Multiplicable
}

func Multiply[T Multiplicable](a, b T) T {
    return a * b
}

Generic इंटरफेस

इंटरफेस भी Generic हो सकते हैं, जो शक्तिशाली अभिविन्यासों की अनुमति देते हैं:

type Repository[T any, ID comparable] interface {
    FindByID(id ID) (T, error)
    Save(entity T) error
    Delete(id ID) error
    FindAll() ([]T, error)
}

// Implementation
type InMemoryRepository[T any, ID comparable] struct {
    data map[ID]T
}

func NewInMemoryRepository[T any, ID comparable]() *InMemoryRepository[T, ID] {
    return &InMemoryRepository[T, ID]{
        data: make(map[ID]T),
    }
}

func (r *InMemoryRepository[T, ID]) FindByID(id ID) (T, error) {
    if entity, ok := r.data[id]; ok {
        return entity, nil
    }
    var zero T
    return zero, fmt.Errorf("entity not found")
}

टाइप इन्फरेंस

Go का टाइप इन्फरेंस अक्सर आपको स्पष्ट टाइप पैरामीटर्स को छोड़ने की अनुमति देता है:

// Type inference in action
numbers := []int{1, 2, 3, 4, 5}

// No need to specify [int] - Go infers it
doubled := Map(numbers, func(n int) int { return n * 2 })

// Explicit type parameters when needed
result := Map[int, string](numbers, strconv.Itoa)

सर्वोत्तम प्रथाएं

1. सरल से शुरू करें

Generics का अधिक उपयोग न करें। अगर एक सरल इंटरफेस या कंक्रीट टाइप काम करेगा, तो बेहतर पठन क्षमता के लिए उसे पसंद करें:

// Simple cases के लिए यह पसंद करें
func Process(items []Processor) {
    for _, item := range items {
        item.Process()
    }
}

// Over-generic - avoid
func Process[T Processor](items []T) {
    for _, item := range items {
        item.Process()
    }
}

2. अर्थपूर्ण कंस्ट्रेंट नामों का उपयोग करें

अपने कंस्ट्रेंट्स को स्पष्ट रूप से नाम दें ताकि इरादा संप्रेषित हो:

// अच्छा
type Sortable interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64 | string
}

// कम स्पष्ट
type T interface {
    int | string
}

3. जटिल कंस्ट्रेंट्स का दस्तावेज़ीकरण करें

जब कंस्ट्रेंट्स जटिल हो जाते हैं, तो दस्तावेज़ीकरण जोड़ें:

// Numeric उन प्रकारों का प्रतिनिधित्व करता है जो गणितीय ऑपरेशंस का समर्थन करते हैं.
// इसमें सभी इंटीजर और फ्लोटिंग-पॉइंट प्रकार शामिल हैं।
type Numeric interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64
}

4. प्रदर्शन पर विचार करें

Generics को कंक्रीट प्रकारों में कंपाइल किया जाता है, इसलिए कोई रनटाइम ओवरहेड नहीं है। हालांकि, ध्यान रखें कि अगर आप बहुत सारे प्रकार संयोजनों को इंस्टैंसियेट करते हैं तो कोड साइज:

// हर संयोजन अलग-अलग कोड बनाता है
var intStack = NewStack[int]()
var stringStack = NewStack[string]()
var floatStack = NewStack[float64]()

वास्तविक दुनिया के अनुप्रयोग

डेटाबेस क्वेरी बिल्डर्स

Generics डेटाबेस क्वेरी बिल्डर्स या ORMs बनाने में विशेष रूप से उपयोगी होते हैं। जब Go में डेटाबेस के साथ काम करते हैं, तो आप Go ORMs for PostgreSQL की तुलना के लिए हमारा लेख उपयोगी पा सकते हैं जो समझने के लिए कि Generics डेटाबेस ऑपरेशंस में टाइप सेफ्टी को कैसे सुधार सकते हैं।

type QueryBuilder[T any] struct {
    table string
    where []string
}

func (qb *QueryBuilder[T]) Where(condition string) *QueryBuilder[T] {
    qb.where = append(qb.where, condition)
    return qb
}

func (qb *QueryBuilder[T]) Find() ([]T, error) {
    // Implementation
    return nil, nil
}

कॉन्फ़िगरेशन मैनेजमेंट

CLI एप्लिकेशंस या कॉन्फ़िगरेशन सिस्टम बनाने के दौरान, Generics टाइप-सेफ़ कॉन्फ़िगरेशन लोडर्स बनाने में मदद कर सकते हैं। अगर आप कमांड-लाइन टूल्स बना रहे हैं, तो हमारा गाइड building CLI applications with Cobra & Viper दिखाता है कि Generics कॉन्फ़िगरेशन हैंडलिंग को कैसे सुधार सकते हैं।

type ConfigLoader[T any] struct {
    path string
}

func (cl *ConfigLoader[T]) Load() (T, error) {
    var config T
    // Load and unmarshal configuration
    return config, nil
}

यूटिलिटी लाइब्रेरीज

Generics विभिन्न प्रकारों के साथ काम करने वाले यूटिलिटी लाइब्रेरीज बनाने में चमकते हैं। उदाहरण के लिए, जब रिपोर्ट्स जनरेट करने या विभिन्न डेटा फॉर्मेट्स के साथ काम करने के दौरान, Generics टाइप सेफ्टी प्रदान कर सकते हैं। हमारा लेख generating PDF reports in Go दिखाता है कि Generics को रिपोर्ट जनरेशन यूटिलिटीज पर कैसे लागू किया जा सकता है।

प्रदर्शन-संवेदनशील कोड

सर्वरलेस फंक्शंस जैसे प्रदर्शन-संवेदनशील अनुप्रयोगों में, Generics टाइप सेफ्टी बनाए रखने की अनुमति दे सकते हैं बिना रनटाइम ओवरहेड के। जब सर्वरलेस अनुप्रयोगों के लिए भाषा विकल्पों पर विचार करते हैं, तो प्रदर्शन विशेषताओं को समझना महत्वपूर्ण है। हमारा विश्लेषण AWS Lambda performance across JavaScript, Python, and Golang दिखाता है कि Go का प्रदर्शन, Generics के साथ संयुक्त, कैसे लाभकारी हो सकता है।

सामान्य गलतियाँ

1. प्रकारों को अत्यधिक सीमित करना

जब आवश्यक नहीं हो तो सीमाएँ अत्यधिक सीमित न करें:

// अत्यधिक सीमित
func Process[T int | string](items []T) { }

// बेहतर - अधिक लचीला
func Process[T comparable](items []T) { }

2. प्रकार के अनुमान को नज़रअंदाज़ करना

जब संभव हो तो Go को प्रकार अनुमान करने दें:

// अनावश्यक स्पष्ट प्रकार
result := Max[int](10, 20)

// बेहतर - Go को अनुमान करने दें
result := Max(10, 20)

3. शून्य मानों को भूल जाना

याद रखें कि सामान्य प्रकारों के पास शून्य मान होते हैं:

func Get[T any](slice []T, index int) (T, bool) {
    if index < 0 || index >= len(slice) {
        var zero T  // महत्वपूर्ण: शून्य मान वापस करें
        return zero, false
    }
    return slice[index], true
}

निष्कर्ष

Go में जनरिक्स एक शक्तिशाली तरीका प्रदान करते हैं ताकि आप प्रदर्शन या पठनीयता को बलिदान किए बिना प्रकार सुरक्षित, पुन: उपयोग योग्य कोड लिख सकें। सिंटैक्स, सीमाओं, और सामान्य पैटर्न को समझकर, आप जनरिक्स का उपयोग करके अपने Go एप्लिकेशनों में कोड डुप्लिकेशन को कम कर सकते हैं और प्रकार सुरक्षा को बेहतर बना सकते हैं।

जनरिक्स का उपयोग सावधानीपूर्वक करें—हर स्थिति की आवश्यकता नहीं होती। संदेह में, इंटरफेस या कंक्रीट प्रकारों जैसे सरल समाधानों का पसंद करें। हालाँकि, जब आपको कई प्रकारों के साथ काम करना हो जबकि प्रकार सुरक्षा बनाए रखनी हो, तो जनरिक्स आपकी Go टूलकिट में एक उत्कृष्ट उपकरण हैं।

जैसे-जैसे Go विकसित होती है, जनरिक्स आधुनिक, प्रकार सुरक्षित एप्लिकेशंस बनाने के लिए एक आवश्यक विशेषता बन रहे हैं। चाहे आप डेटा संरचनाएँ, यूटिलिटी लाइब्रेरी बनाएँ या जटिल अवधारणाएँ, जनरिक्स आपको साफ़, अधिक रखरखाव योग्य कोड लिखने में मदद कर सकते हैं।

उपयोगी लिंक और संबंधित लेख