Bear with me folks. But I’m trying to implement an application used by industries where I’ll have a base implementation and then many, really many, protocols could be used to send information to this application that I’m building. The issue is, some of the fields are there, but most are not.
At the edge of my application I’ll have those protocols, but by the moment I get the message from a device and transcribe it to my internal representation I’ll have a single flow. The issue is, how can or should I handle this kind of situation internally? Just let it be null and check it for null? Mark it as a pointer? Use the Maybe?
I’m a big fan of unions in Odin. Maybe is also a key-word for union :: {$T}, so works the same. Unions tend to be more explicit than using rawptr’s, but more constricting. Though, that’s what I like about them. Forces me to handle every situation, and define all the possibilities.
If the structures are known upfront, then I might suggest using unions and/or a combination of unions and Maybe($T) depending on the situation. Also type assertions are helpful for checking nil, especially with Maybe.
Protocol01 :: struct {
field: Maybe(int)
}
Protocol02 :: struct {
field: Maybe(int)
}
Protocols :: union {
Protocol01,
Protocol02,
}
Proccess_Protocols :: proc(protocol: Protocols) -> (ok: bool) {
field: int
switch p in protocol {
case Protocol01: field = p.field.? -1 // type assertion, set to -1 if nil
//field = p.field.? or_return // return false if nil
case Protocol02: field = p.field.? -1 // type assertion, set to -1 if nil
//field = p.field.? or_return // return false if nil
case /* protocal is nil */: return false
}
if field < 0 { return false }
//(field >= 0) or_return // return false if -1
fmt.println("fieled is usable", field)
return true
}
This would work, but it would be so so so so noisy. The idea is to map all the external protocols into a single internal representation. New protocol added? No worry, everything would work the same. But then I have the problem of files be missing here and there depending on the protocol and the message the device is sending. On an FP like language I would 100% go for an option type, but I’m not so sure in Odin.
I meant for my above example to be imagined as a middle interface that would map to an internal master structure, not requiring the union switch for everything, just for entry into the internals. This would be considerably less chatty using a union switch, than a bunch of if/else statements checking for nil, imho. I don’t think checking for nil is avoidable in any case; FP or not.
Something like this, then check for nil on MasterProtocol fields in all other procedures.
External_Protocol01 :: struct { field: Maybe(int) }
External_Protocol02 :: struct { field: Maybe(int) }
External_Protocol :: union { External_Protocol01, External_Protocol02 }
Master_Protocol :: struct { field: Maybe(int) }
Process_External_Protocol :: proc(protocol: External_Protocol) -> (mp: Master_Protocol, ok: bool) {
switch p in protocol {
case External_Protocol01: mp.field = p.field
case External_Protocol02: mp.field = p.field
case /* protocal is nil */: return
}
return mp, true
}
Now, if the situation is such that, it’s not possible to pre-define each External_Protocol, then there may be a need to map out what is known, and can be expected to be consistent for all existing or future-existing protocols. What information will the protocol provide? → Field order?, Field type? purpose of protocol? Field count? You might then consider a rawptr or multi-pointer entry point system to map input protocol to internal Master_Protocol. Either way, I’m expecting there would be a translation step for entry into the internal flow, so “everything would work the same”. Then internally, only have to make decisions on the Master_Protocol structure which is known, and unambiguous.