Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a closure capturing constructor argument to base class constructor

Tags:

f#

I want to derive a class from System.IO.BinaryWriter, and call it's constructor with a custom Stream, an implementation of which captures a constructor argument of the derived type. For the life of me, I cannot figure out whether even this is possible at all. What I am essentially trying to do, slightly trimmed to be an MCV, is

type HashingBinaryWriter private
     (hasher   : System.Security.Cryptography.HashAlgorithm,
      stream   : System.IO.Stream) =
   inherit System.IO.BinaryWriter(stream)

   let unsupp() = raise(System.NotSupportedException())
   let hash_stream =
      { new System.IO.Stream() with
         member __.CanRead = false
         member __.CanSeek = false
         member __.CanWrite = true
         member __.Length = unsupp()
         member __.Position with get() = unsupp() and set(_) = unsupp()
         member __.Seek(_,_) = unsupp()
         member __.SetLength _ = unsupp()
         member __.Read(_,_,_) = unsupp()
         member __.Flush() = ()
         member __.Write(buffer, offset, count) =
            hasher.TransformBlock(buffer, offset, count, null, 0) |> ignore
      }

   new(hasher) = new HashingBinaryWriter(hasher, hash_stream)
   // Or, alternatively
   new(hasher) as me = new HashingBinaryWriter(hasher, me.hash_stream)

The last line fails to compile because hash_stream is undefined, in either form. Apparently, as this answer suggests, the scope of constructor arguments is different from that of class declaration body, but I need to understand what is going on here (and if possible the why behind the F# design decision).

Indeed, I can see some workarounds (convert hash_stream to a private property, for example), but my vocabulary of F# idioms is lacking this one. My second question is, then, what would be the idiomatic way of doing this.

like image 935
kkm Avatar asked Mar 02 '26 20:03

kkm


1 Answers

There are multiple ways to do this - and I guess the right choice depends on how your complete implementation will look.

If you wanted something that is as close to the version in your question as possible, then you can just move unsupp and hash_stream inside the constructor:

type HashingBinaryWriter private
     (hasher   : System.Security.Cryptography.HashAlgorithm,
      stream   : System.IO.Stream) =
    inherit System.IO.BinaryWriter(stream)

    new(hasher : System.Security.Cryptography.HashAlgorithm) = 
        let unsupp() = raise(System.NotSupportedException())    
        let hash_stream =
            { new System.IO.Stream() with
                member __.CanRead = false
                member __.CanSeek = false
                member __.CanWrite = true
                member __.Length = unsupp()
                member __.Position with get() = unsupp() and set(_) = unsupp()
                member __.Seek(_,_) = unsupp()
                member __.SetLength _ = unsupp()
                member __.Read(_,_,_) = unsupp()
                member __.Flush() = ()
                member __.Write(buffer, offset, count) =
                  hasher.TransformBlock(buffer, offset, count, null, 0) |> ignore
            }  
        new HashingBinaryWriter(hasher, hash_stream)

I guess that this can easily get ugly if your implementation of hash_stream gets longer. In that case, it would make more sense to do what John suggests in the comments and move the implementation of the stream outside of the class, perhaps into a helper module:

let unsupp() = raise(System.NotSupportedException())

let createHashStream (hasher : System.Security.Cryptography.HashAlgorithm) =
    { new System.IO.Stream() with
        member __.CanRead = false
        member __.CanSeek = false
        member __.CanWrite = true
        member __.Length = unsupp()
        member __.Position with get() = unsupp() and set(_) = unsupp()
        member __.Seek(_,_) = unsupp()
        member __.SetLength _ = unsupp()
        member __.Read(_,_,_) = unsupp()
        member __.Flush() = ()
        member __.Write(buffer, offset, count) =
          hasher.TransformBlock(buffer, offset, count, null, 0) |> ignore
    }  

type HashingBinaryWriter private
     (hasher   : System.Security.Cryptography.HashAlgorithm,
      stream   : System.IO.Stream) =
    inherit System.IO.BinaryWriter(stream)
    new(hasher : System.Security.Cryptography.HashAlgorithm) = 
        new HashingBinaryWriter(hasher, createHashStream hasher)
like image 127
Tomas Petricek Avatar answered Mar 05 '26 19:03

Tomas Petricek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!