ちょっと真面目に考えてみますか。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か月