-1

For the past few days, I've been at a road block with regards to the best way to approach the issue of first class functions (assigning a callable to some variable), and the best practices in terms of efficiency.

Let's say I am programming a Yugioh card game, and I want each individual card of type card to have at least these attributes:

type card struct {
    name string
    text string
}

I have been struggling with the idea on where (and how) to program each card's individual functionality. I am currently convinced that the best place for the first-class function is in type card struct and create the new attribute as a "callable" like I would in Python (Go playground link).

package main

import "fmt"


type card struct {
    name string
    text string
    f interface{}
}

type monsterCard struct {
    card
    attack int
    defense int
}

type buff func(target *monsterCard) // Could be defined in a second file

type swap func(target *monsterCard, value int) // ditto

var increaseAttack buff = func(target *monsterCard)  { // ditto
    target.attack += 100
}

var swichStats swap = func(target *monsterCard, value int) { // ditto
    attack := target.attack
    target.attack = value
    target.defense = attack
}

func main()  {
    m1 := monsterCard{
        card:    card{
            name: "Celtic Guardian",
            f:    increaseAttack,
        },
        attack:  1400,
        defense: 1200,
    }
    m2 := monsterCard{
        card:    card{
            name:     "Dark Magician",
            f:         swichStats,
        },
        attack:  2500,
        defense: 2100,
    }
    var monsters = [2]monsterCard{m1, m2}
    for _, m := range monsters {
        fmt.Println(m)
        switch m.f.(type) {
        case buff:
            m.f.(buff)(&m)
        case swap:
            m.f.(swap)(&m, m.defense)
        default:
            fmt.Printf("%T", m.f)
        }
        fmt.Println(m)
    }
}

I'm not very good with regards to efficient code, and I completely understand I might be optimizing early here; however, I will need to program hundreds of these cards, and if having these callables exist in global scope with a heavy reliance on type assertion make the program slow, then I'll be in trouble reorganizing the code.

Are there any glaring issues that you can see with my methodology? Am I going about first-class functions correctly, or is there some kind of glaring performance issues I can't see? Any and all help will be greatly appreciated!

  • 1
    Have you considered using plain methods? – mkopriva Apr 28 '20 at 04:25
  • 5
    Having function pointers as a member variable for structs only makes sense if you intend to change the function during the lifetime of a struct instance. Otherwise, plain old methods would work. In your case, you seem to be using a type switch to figure out what the function is, so simply defining those as methods for the corresponding types would be the cleanest approach. – Burak Serdar Apr 28 '20 at 04:30
  • 2
    This approach doesn't account for multiple effects and you will want to apply different effects at different times too. Make each card its own type and define an interface for each effect that may or may not be implemented by each card. You can embedd a common type for the basic stats. – Peter Apr 28 '20 at 05:38
  • 2
    `interface{}` is never best practice. It seems you'll need something that is called "multiple dispatch" or "multimethods". Go doesn't support that ootb. – Volker Apr 28 '20 at 05:58
  • @mkopriva Using regular functions was my first thought, but I wouldn't know how to make each card's struct point to a specific function. Seems possible in Python, but not in Go. – adespotakis Apr 28 '20 at 13:20
  • @adespotakis I'm not completely sure I understand what you're trying to do, but what you have there can definitely be made to look more pleasant. https://play.golang.com/p/Wl6rKVIfyrj – mkopriva Apr 28 '20 at 13:47
  • @BurakSerdar Thank you for the insight. So long as its clean and efficient, it works for me! – adespotakis Apr 28 '20 at 13:53
  • @Peter I've never considered making each card its own type, thanks for the insight. – adespotakis Apr 28 '20 at 13:56
  • @Volker I'm gonna make a deep dive into some of the languages that support multiple dispatch and seriously consider whether Go is still the right choice for it. Either I use interface and slow down my program or write duplicate code for each type of card. – adespotakis Apr 28 '20 at 14:02
  • @mkopriva I REALLY like how clean your code is, but it would not work for this one particular case; however, if I were to combine your input with BurakSerdar and Peter's comments, I think I can come up with a solution where "card.fn" can accept multiple variations of parameter type and numbers. – adespotakis Apr 28 '20 at 14:15
  • @adespotakis note that you can use closures to include additional parameters in `fn`: https://play.golang.com/p/v_RbObnu7sN – mkopriva Apr 28 '20 at 14:27
  • @mkopriva please post your solution as an answer so I can give you some internet points! – adespotakis Apr 28 '20 at 14:51

2 Answers2

1

You can use plain functions, and closures where needed:

type card struct {
    name string
    text string
    fn   func(*monsterCard)
}

type monsterCard struct {
    card
    attack  int
    defense int
}

func (mc *monsterCard) call() {
    mc.fn(mc)
}

func increaseAttack(mc *monsterCard) {
    mc.attack += 100
}

func switchStats(mc *monsterCard) {
    mc.attack, mc.defense = mc.defense, mc.attack
}


func updateTextAndAttack(text string, attack int) func(mc *monsterCard) {
    return func(mc *monsterCard) {
        mc.text, mc.attack = text, attack
    }
}

https://play.golang.com/p/v_RbObnu7sN

You can also use plain methods, and closures where needed:

type card struct {
    name string
    text string
}

type monsterCard struct {
    card
    attack  int
    defense int
}

func (mc *monsterCard) increaseAttack() {
    mc.attack += 100
}

func (mc *monsterCard) switchStats() {
    mc.attack, mc.defense = mc.defense, mc.attack
}

func (mc *monsterCard) updateTextAndAttack(text string, attack int) func() {
    return func() {
        mc.text, mc.attack = text, attack
    }
}

https://play.golang.com/p/Eo1mm-seldA

mkopriva
  • 35,176
  • 4
  • 57
  • 71
0

Thank you to everyone who helped me through to this answer. It would appear that I'm still using my scripting language mindset to try and solve problems in a compiled language. Instead of trying to create interfaces that ultimately lead to harder to read code, I will need to be more strict in my custom type definitions and implement a different function type for each type of card effect. Such strict enforcement will also allow for cards with multiple effects to exist in the game.

package main

type card struct {
    name string
    text string
}

type monsterCard struct {
    card
    attack int
    defense int
}

type buffEffect struct {
    monsterCard
    fn buff
}

type setValueEffect struct {
    monsterCard
    fn swap
}

type buff func(target *monsterCard)

type swap func(target *monsterCard, value int)

var increaseAttack buff = func(target *monsterCard)  {
    target.attack += 100
}

var setAttackValue swap = func(target *monsterCard, value int) {
    target.attack = value
}

func main()  {
    m1 := buffEffect{
        monsterCard: monsterCard{
            card:    card{
                name: "Celtic Guardian",
            },
            attack:  1400,
            defense: 1200,
        },
        fn:          increaseAttack,
    }
    m2 := setValueEffect{
        monsterCard: monsterCard{
            card:    card{
                name: "Dark Magician",
            },
            attack:  2500,
            defense: 2100,
        },
        fn:          setAttackValue,
    }
    m1.fn(&m1.monsterCard)
    m2.fn(&m2.monsterCard, 100)
}