12
\$\begingroup\$

I've got a couple of utility functions to support memoization for functions with anywhere between 0 to 8 arguments:

Public Shared Function Mize(Of TResult)(ByVal input_f As System.Func(Of TResult)) As System.Func(Of TResult)
    Dim is_new = True
    Dim result As TResult
    Return Function()
               If is_new Then
                   result = input_f()
                   is_new = False
               End If
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TResult)) As System.Func(Of TArg1, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of TArg1, TResult)
    Return Function(arg1 As TArg1)
               Dim result As TResult
               If map.TryGetValue(arg1, result) Then Return result
               result = input_f(arg1)
               map.Add(arg1, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TResult)) As System.Func(Of TArg1, TArg2, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2)
               Dim args = System.Tuple.Create(arg1, arg2)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2)
               map.Add(args, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TArg3 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TArg3, TResult)) As System.Func(Of TArg1, TArg2, TArg3, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2, TArg3), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2, arg3 As TArg3)
               Dim args = System.Tuple.Create(arg1, arg2, arg3)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2, arg3)
               map.Add(args, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TArg3 As Structure, TArg4 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TArg3, TArg4, TResult)) As System.Func(Of TArg1, TArg2, TArg3, TArg4, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2, TArg3, TArg4), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2, arg3 As TArg3, arg4 As TArg4)
               Dim args = System.Tuple.Create(arg1, arg2, arg3, arg4)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2, arg3, arg4)
               map.Add(args, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TArg3 As Structure, TArg4 As Structure, TArg5 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TResult)) As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2, TArg3, TArg4, TArg5), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2, arg3 As TArg3, arg4 As TArg4, arg5 As TArg5)
               Dim args = System.Tuple.Create(arg1, arg2, arg3, arg4, arg5)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2, arg3, arg4, arg5)
               map.Add(args, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TArg3 As Structure, TArg4 As Structure, TArg5 As Structure, TArg6 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult)) As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2, arg3 As TArg3, arg4 As TArg4, arg5 As TArg5, arg6 As TArg6)
               Dim args = System.Tuple.Create(arg1, arg2, arg3, arg4, arg5, arg6)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2, arg3, arg4, arg5, arg6)
               map.Add(args, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TArg3 As Structure, TArg4 As Structure, TArg5 As Structure, TArg6 As Structure, targ7 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7, TResult)) As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2, arg3 As TArg3, arg4 As TArg4, arg5 As TArg5, arg6 As TArg6, arg7 As targ7)
               Dim args = System.Tuple.Create(arg1, arg2, arg3, arg4, arg5, arg6, arg7)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2, arg3, arg4, arg5, arg6, arg7)
               map.Add(args, result)
               Return result
           End Function
End Function

Public Shared Function Mize(Of TArg1 As Structure, TArg2 As Structure, TArg3 As Structure, TArg4 As Structure, TArg5 As Structure, TArg6 As Structure, targ7 As Structure, targ8 As Structure, TResult)(ByVal input_f As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7, targ8, TResult)) As System.Func(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7, targ8, TResult)
    Dim map = New System.Collections.Generic.Dictionary(Of System.Tuple(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7, targ8), TResult)
    Return Function(arg1 As TArg1, arg2 As TArg2, arg3 As TArg3, arg4 As TArg4, arg5 As TArg5, arg6 As TArg6, arg7 As targ7, arg8 As targ8)
               Dim args = New System.Tuple(Of TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, targ7, targ8)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
               Dim result As TResult
               If map.TryGetValue(args, result) Then Return result
               result = input_f(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
               map.Add(args, result)
               Return result
           End Function
End Function

The problem is that the memoization algorithm is repeated 6 times (the functions that accept functions with 2 arguments to 8 arguments basically have the same algorithm).

I'm clearly violating DRY here, and I want to modify the code above to not violate DRY.

I've tried shifting those repeating algorithm into a separate function, but since VB.NET/C# does not allow System.Delegate as a generic constraint, I'm out of ideas as to how I would do it.

What should I do and how can I improve my code above?

\$\endgroup\$
  • \$\begingroup\$ Check this out. Alternatively, you can resort to AOP: Memoizer Attribute Using PostSharp \$\endgroup\$ – Anton Gogolev May 19 '11 at 8:58
  • \$\begingroup\$ @Pacerier: Next time please say it only one time and use a list of names. ;) Also, would you now be so kindly and press the Delete-Button? \$\endgroup\$ – Bobby May 19 '11 at 15:12
  • \$\begingroup\$ @Bobby i mean i would like all of them to have the notification \$\endgroup\$ – Pacerier May 19 '11 at 15:27
14
\$\begingroup\$

Unless you want to go with another approach, like code injection (see Anton's comment), you're pretty much stuck with this. I agree it is ugly and repetitive. Basically you've run up against the edges of the type system. Type parameters are a little like method parameters, and type arguments are a little like passing arguments to a method, but methods are much more flexible. Methods permit all sorts of higher-order programming, methods can be variadic, and so on. There are no variadic generic types in the .NET type system, which is really what you want to make this sort of thing work smoothly.

As for violating the DRY principle, don't worry about it. The reason to not repeat yourself is because if you have the same piece of complex business logic in two different places, and one of them needs fixing, they can get out of sync easily when you forget to fix the other. (The C# compiler codebase is littered with comments that say "If you change the reference type convertibility algorithm here, don't forget to also change it here, here and here...") What you're building here is a mechanism: the boring plumbing code that makes the real logic of the program more efficient. DRY is more applicable to business logic than to boring mechanisms.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.