ちょっと真面目に考えてみますか。Golangでprivateなメンバーを持ち必要に応じてgetterを提供すれば可能ですね。setterがなければ見かけ上はイミュータブルにできます。技術的には可能…。

package person

import "fmt"

type Person struct {

    name string

    age  int

}

// 値として Person を作成

func NewPerson(name string, age int) Person {

    return Person{name: name, age: age}

}

// メソッドはポインタレシーバーを使用

func (p *Person) Name() string {

    return p.name

}

func (p *Person) Age() int {

    return p.age

}

// 新しい名前で Person を作成

func (p *Person) WithName(newName string) Person {

    return Person{name: newName, age: p.age}

}

// 新しい年齢で Person を作成

func (p *Person) WithAge(newAge int) Person {

    return Person{name: p.name, age: newAge}

}

// 使用例

func ExampleUsage() {

    // 値として作成(スタック割り当ての可能性あり)

    person := NewPerson("Alice", 30)   

    // メソッドはポインタレシーバーを使用

    fmt.Printf("Name: %s, Age: %d\n", person.Name(), person.Age()) 

    // 新しい Person インスタンスの作成

    olderPerson := person.WithAge(31)

    renamedPerson := person.WithName("Alicia")

    fmt.Printf("Original: %s, %d\n", person.Name(), person.Age())

    fmt.Printf("Older: %s, %d\n", olderPerson.Name(), olderPerson.Age())

    fmt.Printf("Renamed: %s, %d\n", renamedPerson.Name(), renamedPerson.Age())

}

厄介なのはコレクションですね。可変なので間違った操作をしやすいです。サードパーティーの不変コレクションなど使うのが妥当です。

package shoppingcart

import (
    "fmt"
    "errors"

    "github.com/benbjohnson/immutable"
)

type Item struct {
    id       string
    name     string
    price    float64
    quantity int
}

func NewItem(id, name string, price float64, quantity int) *Item {
    return &Item{id: id, name: name, price: price, quantity: quantity}
}

func (i *Item) ID() string       { return i.id }
func (i *Item) Name() string     { return i.name }
func (i *Item) Price() float64   { return i.price }
func (i *Item) Quantity() int    { return i.quantity }

type Cart struct {
    id    string
    items *immutable.List[Item]
}

func NewCart(id string) *Cart {
    return &Cart{
        id:    id,
        items: immutable.NewList(),
    }
}

func (c *Cart) ID() string { return c.id }

func (c *Cart) Items() []*Item {
    result := make([]*Item, c.items.Len())
    for i := 0; i < c.items.Len(); i++ {
        result[i] = c.items.Get(i).(*Item)
    }
    return result
}

func (c *Cart) AddItem(item *Item) *Cart {
    newItems := c.items
    newItems = newItems.Append(item)
    return &Cart{id: c.id, items: newItems}
}

func (c *Cart) RemoveItem(itemID string, quantity int) (*Cart, error) {
// TODO
}

func (c *Cart) TotalPrice() float64 {
    total := 0.0
    for i := 0; i < c.items.Len(); i++ {
        item := c.items.Get(i).(*Item)
        total += item.price * float64(item.quantity)
    }
    return total
}

func ExampleUsage() {
    cart := NewCart("cart1")

    cart = cart.AddItem(NewItem("item1", "Book", 10.99, 2))
    cart = cart.AddItem(NewItem("item2", "Pen", 1.99, 5))

    fmt.Printf("Cart %s total: $%.2f\n", cart.ID(), cart.TotalPrice())

    // Add more of an existing item
    cart = cart.AddItem(NewItem("item1", "Book", 10.99, 1))

    // Remove an item
    var err error
    cart, err = cart.RemoveItem("item2", 2)
    if err != nil {
        fmt.Println("Error:", err)
    }

    fmt.Printf("Updated cart %s total: $%.2f\n", cart.ID(), cart.TotalPrice())

    // Print all items in the cart
    for _, item := range cart.Items() {
        fmt.Printf("%s: %d x $%.2f\n", item.Name(), item.Quantity(), item.Price())
    }
}

こういうことを考えていくと、イミュータブルや不変コレクションへの理解や学習などが必要になりますね。

ほかにもOptionやEither(Result)をどうするの?という話があります。Goの道から外れるのでは?という声も聞こえてきますね。それもわかる。

何を求められているのか?という文脈なし一般論でいうなら、ほかの関数型の機能を備えた言語(Scala, Rust)の方がよいのでは?という話になりそうです。

でも、目の前にある問題や課題は一般論で解決できるものなんだろうか?解像度が足りないだけかも??Goしかできるメンバーしかおらず、Goの道をある程度外れても、より高い品質が求められているなら、上記のような選択をするかもしれません。

1か月

利用規約プライバシーポリシーに同意の上ご利用ください

加藤潤一(かとじゅん)さんの過去の回答
    Loading...