In C #, adding SelectMany extends linq to a new monad type, how do I do the same in VB.net?
Old Another Geek blog post explaining monads describes adding the SelectMany extension method in C # to extend linq syntax to new types.
I tried it in C # and it works. I did a direct conversion to VB.net and it doesn't work. Does anyone know if VB.net supports this feature or how to use it?
Here is C # code that works:
class Identity<T> {
public readonly T Value;
public Identity(T value) { this.Value = value; }
}
static class MonadExtension {
public static Identity<T> ToIdentity<T>(this T value) {
return new Identity<T>(value);
}
public static Identity<V> SelectMany<T, U, V>(this Identity<T> id, Func<T, Identity<U>> k, Func<T, U, V> s) {
return s(id.Value, k(id.Value).Value).ToIdentity();
}
}
class Program {
static void Main(string[] args) {
var r = from x in 5.ToIdentity()
from y in 6.ToIdentity()
select x + y;
}
}
Here is the VB.net code that doesn't work (note: written in vs2010, so some line continuations may be missing):
Imports System.Runtime.CompilerServices
Public Class Identity(Of T)
Public ReadOnly value As T
Public Sub New(ByVal value As T)
Me.value = value
End Sub
End Class
Module MonadExtensions
<Extension()> _
Public Function ToIdentity(Of T)(ByVal value As T) As Identity(Of T)
Return New Identity(Of T)(value)
End Function
<Extension()> _
Public Function SelectMany(Of T, U, V)(ByVal id As Identity(Of T), ByVal k As Func(Of T, Identity(Of U)), ByVal s As Func(Of T, U, V)) As Identity(Of V)
Return s(id.value, k(id.value).value).ToIdentity()
End Function
End Module
Public Module MonadTest
Public Sub Main()
''Error: Expression of type 'Identity(Of Integer)' is not queryable.
Dim r = From x In 5.ToIdentity() _
From y In 6.ToIdentity() _
Select x + y
End Sub
End Module
source to share
Obviously, VB.net requires that, in addition to defining SelectMany, the target type must implement the methods you want (e.g. Select, Where, etc.).
Add this method to Identity and the program compiles and runs:
Public Function [Select](Of R)(ByVal projection As Func(Of T, R)) As Identity(Of R)
Return projection(value).ToIdentity
End Function
You can also implement it as an extension method for existing linq-ify types:
<Extension()> _
Public Function [Select](Of T, R)(ByVal this As Identity(Of T), ByVal projection As Func(Of T, R)) As Identity(Of R)
Return projection(this.value).ToIdentity
End Function
Also, VB.net only needs SelectMany if there are multiple "from" lines. If the expression is of the form "from x select x + 1", then only the Identity.Select method should be implemented.
source to share
Equivalent code must also be supported by VB. Make sure you translate extension methods correctly:
This C #:
public static class MonadExtensions
{
public static Identity<T> ToIdentity<T>(this T value)
{
return new Identity<T>(value);
}
}
Would this VB be:
Imports System.Runtime.CompilerServices
Module MonadExtensions
<Extension()> _
Public Function ToIdentity(Of T)(ByVal value As T) As Identity(Of T)
Return New Identity(Of T)(value)
End Function
End Module
Update: Your code is listed correctly, so it seems like you are just using a VB compiler limitation. As far as I can tell, what you are trying to do is legal according to the language specification.
However, I managed to trick the compiler into accepting the request by Identity(Of T)
pretending that it implements IEnumerable(Of T)
:
Public Class Identity(Of T)
Implements IEnumerable(Of T)
Public ReadOnly value As T
Public Sub New(ByVal value As T)
Me.value = value
End Sub
Public Function GetEnumerator() As IEnumerator(Of T) _
Implements IEnumerable(Of T).GetEnumerator
Throw New InvalidOperationException("This should never be called.")
End Function
Public Function GetEnumerator1() As IEnumerator _
Implements IEnumerable(Of T).GetEnumerator
Throw New InvalidOperationException("This should never be called.")
End Function
End Class
Once we have convinced the compiler that it is a valid request, it correctly resolves the call to your custom SelectMany
.
Update 2: Or, what you said,
Strilanc. I tried this first but apparently forgot the attributeExtension
. From the language specification, something is considered requested if in order of preference ...
- It defines the corresponding Select method.
- It has an AsEnumerable () or AsQueryable () method.
- It has a Cast (Of T) method.
source to share