Reputation: 4231
Assume the following code:
type Large = { v1: int; v2: int }
type Small = { v1: int }
let fn (value: Small) = printfn "%d" value.v1
// or
let fn (value: {v1: int}) = printfn "%d" value.v1
let large = { v1 = 5; v2 = 6 }
fn large
Small
is basically a sub-record of Large
. fn large
throws an error. Is there any "nice" way how to make it work?
Background:
Often there is a situation where you have a lot of data. You want to pass that data to an unknown function provided from the user as a parameter. The only thing that you know is that the function will need a part of the data. But you do not know which part of the data and you do not know about the Small
type. You only know about the Large
type. One option is to send all the data to the unknown function in the form of a large
record. But I believe that the fn
function should not accept Large
data if it uses only small part of it. The Large
data will just make the fn
function more difficult to read, understand and unit test. To my opinion the fn
function should accept only what it needs and nothing else, but up front I do not know which part of the Large
type the fn
function will need.
Upvotes: 1
Views: 311
Reputation: 962
The Large data will just make the fn function more difficult to read, understand and unit test.
I am not sure if I get the problem correctly. But if the main problem is about readability and test-ability, then maybe just use a conversion function
let forgetful (large : Large) = ({v1=large.v1} : Small)
then write your function
let fn (value: Small) = printfn "%d" value.v1
which is readable/testable and get the desire function f by stipulating
let f x = fn (forgetful x)
which doesnt require testing anymore..
Upvotes: 1
Reputation: 4231
Based on the following:
Strange behavior of F# records :
It is important to realize that F# does not use structural typing (meaning that you cannot use record with more fields as an argument to a function that takes record with fewer fields). This might be a useful feature, but it does not fit well with .NET type system. This basically means you cannot expect too fancy things - the argument has to be a record of a well known named record type.
nicolas:
If you truly dont know beforehand which part of Large fn will use, then you have to give it all. this is the only semantically correct choice.
I have come up with two possible solutions:
Solution 1
I basically split the user function into two. The nice thing is that reducedPostprocessFn
accepts only what it needs. This function is therefore easy to reason about and unit test. postprocessFn
is so short that it is also simple to see what it does. I find this solution similar to active patterns presented by Phillip Trelford
. I wonder what the advantage of active patterns is?
(* simulation *)
type Large = {v1: int; v2: int}
let simulation postprocessFn =
let large = {v1 = 1; v2 = 2}
postprocessFn large
(* user *)
let reducedPostprocessFn (v1: int) =
printfn "%d" v1
let postprocessFn (large: Large) =
reducedPostprocessFn large.v1
simulation postprocessFn
Solution 2
This solution uses duck typing but:
http://msdn.microsoft.com/en-us/library/dd233203.aspx
Explicit Member Constraint: ... not intended for common use.
I just remembered this won't work for record types. Technically their members are fields, although you can amend them with members using with member ....
So I used an ordinary class instead of records. Now I use only one function instead of two but, to be honest, duck typing in F# is just ugly.
(* simulation *)
type Large(v1, v2) =
member o.v1 = v1
member o.v2 = v2
let simulation postprocessFn =
let large = Large(1, 2)
postprocessFn large
(* user 2 *)
let inline postprocessFn small =
let v = (^a: (member v1: int) small)
printfn "%d" v
simulation postprocessFn
Upvotes: 1
Reputation: 6543
Interfaces might be a closer fit to what you have described in the updated question:
type Small =
abstract v1 : int
type Large =
inherit Small
abstract v2 : int
let fn (value: Small) = printfn "%d" value.v1
let large =
{ new Large with
member small.v1 = 5
member large.v2 = 6 }
fn large
With interfaces the Large
type can now be composed from 1 or more Small
types. Functions can expect only the Small
type they need, and the caller can simply pass a Large
type to each function.
Depending on the scenario you might also consider having a Large
record type that implements one or more Small
interfaces.
type Small = abstract v1 : int
type Large = { v1: int; v2: int } with
interface Small with member this.v1 = this.v1
let fn (value: Small) = printfn "%d" value.v1
let large = { v1 = 5; v2 = 6 }
fn large
Yet another option is to define a union for all the possible Small
items, and define Large
as a list:
type Item =
| V1 of int
| V2 of int
type Large = Item list
let (|Small|) (large:Large) =
large |> List.pick (function V1(v1) -> Some(v1) | _ -> None)
let fn (Small value) = printfn "%d" value
let large = [V1(5);V2(6)]
fn large
Active patterns can be used to extract Small
types from the data. An example of this kind of scheme is the FIX protocol which composes large financial data blocks from small tagged data blocks.
Upvotes: 2
Reputation: 9805
If you truly dont know beforehand which part of Large fn will use, then you have to give it all. this is the only semantically correct choice.
Now you might be puzzled because you feel that fn does not need all of it, because of the kind of function that fn is, and you are just missing some name to give to that class of functions.
One you give a name to this class, you can reduce Large to only the parts that it needs to access, or break fn in a part that is generic, and another tied to your type for that class of functions, like
type Large = { v1: int; v2: int }
with x.AdaptToPrintable() = x.v1.ToString()
and
fn x = printfn "%A" x
PS : I guess I am confused if your problem lies with the semantic or with the operational aspect of specifying values. If the later, then Phil's answer seem to be the best..
Upvotes: 1