How to create a list of trait

I have a list of operations, e.g.: Add, Sub, etc… I need to apply them to some numbers.

But I can’t find a way to create a List of trait

from collections import List

trait Operation(Copyable & Movable):
	pass

fn main():
	var list = List[Operation]() # error

It fails with error:

error: cannot implicitly convert 'AnyTrait[Operation]' value to 'Copyable & Movable' in type parameter
       var list = List[Operation]() # error
                       ^~~~~~~~~

Any workaround? Thanks.

Hi! We don’t support existentials yet, so you can’t put trait objects in a list.

That said, this sounds like a case where enums really shine:

from utils import Variant

@fieldwise_init  # if you are on nightly
struct Add(Copyable, Movable):
  ...

@value  # if you are on 25.3
struct Mul:
  ...

alias Op = Variant[Add, Mul]

fn main():
  add = Op(Add())
  mul = Op(Mul())
  l = List[Op](add, mul)
1 Like

Might wanna remove @value from example as it will soon be removed. So other can see in future.

1 Like

Thank you very much.

The working code:

from utils import Variant

@fieldwise_init 
struct Add(Copyable, Movable):
	var a: Int
	var b: Int

	fn __copyinit__(out self, other: Self):
		self.a = other.a
		self.b = other.b

	fn __moveinit__(out self, owned other: Self):
		self.a = other.a
		self.b = other.b

	fn calc(self) -> Int:
		return self.a + self.b

@fieldwise_init  
struct Mul(Copyable, Movable):
	var a: Int
	var b: Int

	fn __copyinit__(out self, other: Self):
		self.a = other.a
		self.b = other.b

	fn __moveinit__(out self, owned other: Self):
		self.a = other.a
		self.b = other.b

	fn calc(self) -> Int:
		return self.a * self.b

alias Op = Variant[Add, Mul]

fn main():
	add = Op(Add(1,2))
	mul = Op(Mul(3,4))
	l = List[Op](add, mul)

	for op in l: 
		if op[].isa[Add](): # <---- is it supposed to be used like this
			print(op[][Add].calc())
		if op[].isa[Mul]():
			print(op[][Mul].calc())

Looking forward to the List[trait] support :grinning_face:

1 Like

The code becomes very complex when there’re lots of .isa[](), so I ended up using high order functions:

alias F = fn(Int) escaping -> Int

fn add(x: Int) -> F:
	fn inner(y: Int) -> Int:
		return x + y
	return inner

fn mul(x: Int) -> F:
	fn inner(y: Int) -> Int:
		return x * y
	return inner

fn main(): 
	var result = 1

	var list = List[F](add(7), mul(11))
	for f in list:
		result = f[](result)
	
	print(result) 

I see you already have a solution, but your initial question triggered my curiosity to find an elegant algorithm. Initially I thought you needed a parallel approach, then later I saw your ‘higher order’ solution which uses sequential calculations, so I worked that out too. In short, the sequential solution below performs the same calculations as in your example (I think). The parallel algorithm does not, but I thought it worth sharing FWIW. Cheers!

from algorithm.functional import parallelize

alias fn_sig = fn (a: Int, b: Int) -> Int

struct Op(Copyable & Movable):
    """Contains mathematical operations."""

    alias Add = Self.add
    alias Mul = Self.mul

    var operation: fn_sig
    var a: Int
    var b: Int

    fn __init__(out self, operation: fn_sig, a: Int, b: Int):
        self.operation = operation
        self.a = a
        self.b = b

    @parameter
    fn __call__(self, out value: Int):
        """Parallel - returns values from initial arguments."""
        value = self.operation(self.a, self.b)

    @parameter
    fn __call__(self, mut value: Int):
        """Sequential - mutates the 'value' argument."""
        value = self.operation(value, self.b)

    @staticmethod
    fn bind(func: fn_sig, a: Int, b: Int, out result: Self):
        result = Self(func, a, b)

    @staticmethod
    fn add(a: Int, b: Int, out value: Int):
        value = a + b

    @staticmethod
    fn mul(a: Int, b: Int, out value: Int):
        value = a * b

fn main():
    alias RESIZE_DEFAULT = -1  # Value not important.
    operations = List[Op]()

    # 1) Sequential calculations.
    value = 1
    operations.append(Op.bind(Op.Add, value, 7))
    operations.append(Op.bind(Op.Mul, value, 11))

    # Perform Sequential calculations.
    for op in operations:
        op[](value)
    print("Sequential value =", String(value))
    print()

    # 2) Parallel calculations.
    print("Parallel results:")
    operations.clear()
    operations.append(Op.bind(Op.Add, 1, 2))
    operations.append(Op.bind(Op.Mul, 3, 4))
    operations.append(Op.bind(Op.Add, 5, 6))
    operations.append(Op.bind(Op.Mul, 7, 8))
    operations.append(Op.bind(Op.Add, 9, 10))

    results = List[Int]()
    results.resize(len(operations), RESIZE_DEFAULT)

    # Nested function for parallel calculations.
    @parameter
    fn calc(index: Int):
        results.insert(index, operations[index]())
        print("results[" + String(index) + "] =", results[index])

    # Perform parallel calculations.
    num_work_items = len(operations)
    num_workers = num_work_items
    parallelize[calc](num_work_items, num_workers)

Output:

Sequential value = 88

Parallel results:
results[0] = 3
results[4] = 19
results[1] = 12
results[3] = 56
results[2] = 11

@EzRyder Thank you/ sir. I see the point is to wrap the function in a struct, this makes it possible to add some debug information of the function. But I prefer implementng them separately, it’s easier to maintain.
The updated code:

alias F = fn(y: Int) escaping -> Int
alias Debug = fn() escaping -> String


@fieldwise_init
struct Wrapper(Movable & Copyable):
	var f: F
	var debug: Debug

	fn __call__(self, b: Int) -> Int:
		return self.f(b)

fn add(x: Int) -> Wrapper:
	fn f(y: Int) -> Int:
		return x + y

	fn debug() -> String:
		return String("+") + x.__str__()

	return Wrapper(f, debug)

fn mul(x: Int) -> Wrapper:
	fn f(y: Int) -> Int:
		return x * y

	fn debug() -> String:
		return String("*") + x.__str__()

	return Wrapper(f, debug)

fn main(): 
	var result = 1

	var list = List(add(7), mul(11))
	for f in list:
		print("operation: " + f[].debug())
		result = f[](result)
	
	print(result)

Output:

operation: +7
operation: *11
88

1 Like

This isn’t on my radar. What’s the background/timeline for this change?

1 Like