Eric
Eric

Reputation: 87

F# return a specified union type from byte array

I'm trying to write a function that takes in a byte array and converts it to an ADT as specified by a parameter. Is this possible in F#? This is my ADT:

type DataFormat =
    | Alphanumeric of string
    | Angle16 of float
    | Angle32 of float
    | Int16 of int
    | Int32 of int

I've tried about ten different ways of formatting the function specification, but I can't figure it out... I read some of the other posts on SO like this one which makes me think this is more complex than I think. This was my last two attempts which don't seem to lead anywhere.

// Attempt #1
// This function would require that I pass in a shell
// object as the "format" parameter to make it work, like:
//      let converted = fromBytes1 myArray Angle16(0.0)
let fromBytes1 (b : byte[]) (format : DataFormat) =
    match format with
    | Alphanumeric -> Alphanumeric(BitConverter.ToString(b))
    | Angle16 -> // convert 2-bytes into float...omitted
    | Angle32 -> Angle32(float (BitConverter.ToSingle(b,0)))

// Attempt #2
// the 'T seems to only specify the underlying type like (float, int)
let fromBytes2<'T> (b : byte[]) =
    match 'T with
    | Alphanumeric -> Alphanumeric(BitConverter.ToString(b))
    | Angle16 -> // convert 2-bytes into float...omitted
    | Angle32 -> Angle32(float (BitConverter.ToSingle(b,0)))

I've also tried using typeof<> but that only seems to return the underlying base type.

Upvotes: 1

Views: 432

Answers (1)

BitTickler
BitTickler

Reputation: 11937

I think you try to double-use your discriminated union here.

Use 1: You use it as a tag (or atom in erlang terms) to indicate to the function what you expect. Then, it would look like this:

type DataType = | Int | Bool  // without any data items associated.
let readExpected (valueType : DataType) (data : byte[] ) =
    match valueType with 
    | DataType.Int -> // parse the int from data and do something with it
    | DataType.Bool -> // parse the boolean representation from data ...

Use 2: You use the union as the real ADT. Then it would be the return value of your function:

type ADT = | Int of int | Bool of bool // now they have data
let read (data : byte[]) : ADT =
    let wireType = data.[0]
    match wireType with
    | 2uy -> Int(readInt data) // return the parsed int as ADT.Int
    | 3uy -> Bool(readBool data) // return the parsed bool as ADT.Bool
    | _ -> failwith "Unknown wire type in data."

You could try to mix those two approaches, of course.

type ADT = | Int of int | Bool of bool // now they have data
let readExpected (data : byte[]) (expectedType : ADT) : ADT =
    match expectedType with
    | Int(_) -> Int(readInt data)
    | Bool(_) -> Bool(readBool data)

If you dislike the way to write the expectedType explicitely with some phony data content, you could opt for a easy solution like this:

type ADT = | Int of int | Bool of bool // now they have data
let IntType = Int(0)
let BoolType = Bool(false)

let readExpected (data : byte[]) (expectedType : ADT) : ADT =
    match expectedType with
    | Int(_) -> Int(readInt data)
    | Bool(_) -> Bool(readBool data)

// caller code:
let intValue = readExpected data IntType
let boolValue = readExpected data BoolType

Maybe it would make sense here, to switch the order of the two parameters.

Maybe it would make sense not to pretend it is dynamic if in fact it is not. If the caller is able to specify IntType in the code above, you could simply also create one function per type.

type ADT = | Int of int | Bool of bool // now they have data
let readInt data =
   Int( foo data) // foo data is my lazy way of saying: However you parse it ;)
let readBool data =
   Bool( if data.[0] = 0uy then false else true )

// caller code:
let intValue = readInt data
let boolValue = readBool data

Note, that all those readXXX functions have the same type: byte[] -> ADT. So if you plan on specifying message types by means of composition, you can use that fact to your advantage.

Upvotes: 1

Related Questions