Pass Function to reduce duplicate code - csv

I'm trying to learn F# and I feel like I can write / rewrite this block of code to be more "idiomatic" F# but I just can't figure out how I can accomplish it.
My simple program will be loading values from 2 csv files: A list of Skyrim potion effects, and a list of Skyrim Ingredients. An ingredient has 4 Effects. Once I have the Ingredients, I can write something to process them - right now, I just want to write the CSV load in a way that makes sense.
Code
Here are my types:
type Effect(name:string, id, description, base_cost, base_mag, base_dur, gold_value) =
member this.Name = name
member this.Id = id
member this.Description = description
member this.Base_Cost = base_cost
member this.Base_Mag = base_mag
member this.Base_Dur = base_dur
member this.GoldValue = gold_value
type Ingredient(name:string, id, primary, secondary, tertiary, quaternary, weight, value) =
member this.Name = name
member this.Id = id
member this.Primary = primary
member this.Secondary = secondary
member this.Tertiary = tertiary
member this.Quaternary = quaternary
member this.Weight = weight
member this.Value = value
Here is where I parse an individual comma-separated string, per type:
let convertEffectDataRow (csvLine:string) =
let cells = List.ofSeq(csvLine.Split(','))
match cells with
| name::id::effect::cost::mag::dur::value::_ ->
let effect = new Effect(name, id, effect, Decimal.Parse(cost), Int32.Parse(mag), Int32.Parse(dur), Int32.Parse(value))
Success effect
| _ -> Failure "Incorrect data format!"
let convertIngredientDataRow (csvLine:string) =
let cells = List.ofSeq(csvLine.Split(','))
match cells with
| name::id::primary::secondary::tertiary::quaternary::weight::value::_ ->
Success (new Ingredient(name, id, primary, secondary, tertiary, quaternary, Decimal.Parse(weight), Int32.Parse(value)))
| _ -> Failure "Incorrect data format!"
So I feel like I should be able to build a function that accepts one of these functions or chains them or something, so that I can recursively go through the lines in the CSV file and pass those lines to the correct function above. Here is what I've tried so far:
type csvTypeEnum = effect=1 | ingredient=2
let rec ProcessStuff lines (csvType:csvTypeEnum) =
match csvType, lines with
| csvTypeEnum.effect, [] -> []
| csvTypeEnum.effect, currentLine::remaining ->
let parsedLine = convertEffectDataRow2 currentLine
let parsedRest = ProcessStuff remaining csvType
parsedLine :: parsedRest
| csvTypeEnum.ingredient, [] -> []
| csvTypeEnum.ingredient, currentLine::remaining ->
let parsedLine = convertIngredientDataRow2 currentLine
let parsedRest = ProcessStuff remaining csvType
parsedLine :: parsedRest
| _, _ -> Failure "Error in pattern matching"
But this (predictably) has a compile error on second instance of recursion and the last pattern. Specifically, the second time parsedLine :: parsedRest shows up does not compile. This is because the function is attempting to both return an Effect and an Ingredient, which obviously won't do.
Now, I could just write 2 entirely different functions to handle the different CSVs, but that feels like extra duplication. This might be a harder problem than I'm giving it credit for, but it feels like this should be rather straightforward.
Sources
The CSV parsing code I took from chapter 4 of this book: https://www.manning.com/books/real-world-functional-programming

Since the line types aren't interleaved into the same file and they refer to different csv file formats, I would probably not go for a Discriminated Union and instead pass the processing function to the function that processes the file line by line.
In terms of doing things idiomatically, I would use a Record rather than a standard .NET class for this kind of simple data container. Records provide automatic equality and comparison implementations which are useful in F#.
You can define them like this:
type Effect = {
Name : string; Id: string; Description : string; BaseCost : decimal;
BaseMag : int; BaseDuration : int; GoldValue : int
}
type Ingredient= {
Name : string; Id: string; Primary: string; Secondary : string; Tertiary : string;
Quaternary : string; Weight : decimal; GoldValue : int
}
That requires a change to the conversion function, e.g.
let convertEffectDataRow (csvLine:string) =
let cells = List.ofSeq(csvLine.Split(','))
match cells with
| name::id::effect::cost::mag::dur::value::_ ->
Success {Name = name; Id = id; Description = effect; BaseCost = Decimal.Parse(cost);
BaseMag = Int32.Parse(mag); BaseDuration = Int32.Parse(dur); GoldValue = Int32.Parse(value)}
| _ -> Failure "Incorrect data format!"
Hopefully it's obvious how to do the other one.
Finally, cast aside the enum and simply replace it with the appropriate line function (I've also swapped the order of the arguments).
let rec processStuff f lines =
match lines with
|[] -> []
|current::remaining -> f current :: processStuff f remaining
The argument f is just a function that is applied to each string line. Suitable f values are the functions we created above, e.g.convertEffectDataRow. So you can simply call processStuff convertEffectDataRow to process an effect file and processStuff convertIngredientDataRow to process and ingredients file.
However, now we've simplified the processStuff function, we can see it has type: f:('a -> 'b) -> lines:'a list -> 'b list. This is the same as the built-in List.map function so we can actually remove this custom function entirely and just use List.map.
let processEffectLines lines = List.map convertEffectDataRow lines
let processIngredientLines lines = List.map convertIngredientDataRow lines

(optional) Convert Effect and Ingredient to records, as s952163 suggested.
Think carefully about the return types of your functions. ProcessStuff returns a list from one case, but a single item (Failure) from the other case. Thus compilation error.
You haven't shown what Success and Failure definitions are. Instead of generic success, you could define the result as
type Result =
| Effect of Effect
| Ingredient of Ingredient
| Failure of string
And then the following code compiles correctly:
let convertEffectDataRow (csvLine:string) =
let cells = List.ofSeq(csvLine.Split(','))
match cells with
| name::id::effect::cost::mag::dur::value::_ ->
let effect = new Effect(name, id, effect, Decimal.Parse(cost), Int32.Parse(mag), Int32.Parse(dur), Int32.Parse(value))
Effect effect
| _ -> Failure "Incorrect data format!"
let convertIngredientDataRow (csvLine:string) =
let cells = List.ofSeq(csvLine.Split(','))
match cells with
| name::id::primary::secondary::tertiary::quaternary::weight::value::_ ->
Ingredient (new Ingredient(name, id, primary, secondary, tertiary, quaternary, Decimal.Parse(weight), Int32.Parse(value)))
| _ -> Failure "Incorrect data format!"
type csvTypeEnum = effect=1 | ingredient=2
let rec ProcessStuff lines (csvType:csvTypeEnum) =
match csvType, lines with
| csvTypeEnum.effect, [] -> []
| csvTypeEnum.effect, currentLine::remaining ->
let parsedLine = convertEffectDataRow currentLine
let parsedRest = ProcessStuff remaining csvType
parsedLine :: parsedRest
| csvTypeEnum.ingredient, [] -> []
| csvTypeEnum.ingredient, currentLine::remaining ->
let parsedLine = convertIngredientDataRow currentLine
let parsedRest = ProcessStuff remaining csvType
parsedLine :: parsedRest
| _, _ -> [Failure "Error in pattern matching"]
csvTypeEnum type looks fishy, but I'm not sure what you were trying to achieve, so just fixed the compilation errors.
Now you can refactor your code to reduce duplication by passing functions as parameters when needed. But always start with types!

You can certainly pass a function to another function and use a DU as a return type, for example:
type CsvWrapper =
| CsvA of string
| CsvB of int
let csvAfunc x =
CsvA x
let csvBfunc x =
CsvB x
let csvTopFun x =
x
csvTopFun csvBfunc 5
csvTopFun csvAfunc "x"
As for the type definitions, you can just use records, will save you some typing:
type Effect = {
name:string
id: int
description: string
}
let eff = {name="X";id=9;description="blah"}

Related

Custom json generator for variant of a sum type with ppx_deriving_yojson

I'd like to generate JSON objects representing an AST, and I'm having a look at ppx_deriving_yojson to do that (more specifically the to_yojson part). Now, there are of course corner cases when I'd like to resort to a customized encoding. As mentioned in the documentation, customizing the representation of a record field is easy:
type bar = { test: int;
test1: int [#to_yojson fun i -> `String (string_of_int i)] }
[##deriving to_yojson]
let j1 = bar_to_yojson { test = 0; test1 = 1 }
will give me
val j1 : Yojson.Safe.t = `Assoc [("test", `Int 0); ("test1", `String "1")]
But there are places where I'd like to have a custom representation for the variant of a sum type, and the to_yojson attribute does not seem to be recognized here:
type foo = Bar | Bla of string [#to_yojson fun s -> `String (s ^ "_suffix")]
[##deriving to_yojson]
let j2 = foo_to_yojson (Bla "bla")
gives me
val j2 : Yojson.Safe.t = `List [`String "Bla"; `String "bla"]
i.e. the default encoding of ppx_deriving_yojson. Is there something I am missing or is it simply not possible to achieve what I want?
NB: result are obtained with utop and #require "ppx_deriving_yojson";; as the first command
I forgot about inline records. This would do roughly what I want:
type foo = Bar | Bla of { bla: string [#to_yojson fun s -> `String (s ^ "_suffix")]}
[##deriving to_yojson]
let j2 = foo_to_yojson (Bla { bla = "bla"})
gives
val j2 : Yojson.Safe.t =
`List [`String "Bla"; `Assoc [("bla", `String "bla_suffix")]]
However, while this solution works for a brand new development, it would imply a significant refactoring effort on existing types. Thus, I'm still looking for a less intrusive solution.

F#: Apply a value as though it is a function

Is it possible in F# to apply a value as though it is a function? For example:
let record =
{|
Func = fun x -> x * x
Message = "hello"
|}
let message = record.Message // unsweetened field access
let unsweet = record.Func 3 // unsweetened function application
let sweet = record 3 // sweetened function application
Right now, the last line doesn't compile, of course:
error FS0003: This value is not a function and cannot be applied.
Is there some sort of syntactical sweetener that would allow me to "route" function application as I see fit, while still retaining its normal unsweetened behavior as well? I'm thinking of something magic like this:
// magically apply a record as though it was a function
let () record arg =
record.Func arg
(Note: I used a record in this example, but I'd be happy with a class as well.)
The closest thing I can think of would be a custom operator using statically resolved type parameters to resolve a specific property of type FSharpFunc, then invoke the function with the supplied input parameter. Something like this:
let record =
{|
Func = fun x -> x * x
Message = "hello"
|}
let inline (>.>) (r: ^a) v =
let f = (^a : (member Func: FSharpFunc< ^b, ^c>) r)
f v
record >.> 3 // val it : int = 9

F# assign (custom) type to a function

For all the progress I've made in F#, I still get lost in various of the constructor and deconstructor syntax.
I'm running a recursive simulation. One of the parameters is a function for the stopping condition. I have various possible stopping conditions to choose from. I make them all have the same signature. So I decide it would be nice, and educational, to lock down these functions to a custom type--so that not just any function that happens to match the signature can be sent:
type StoppingCondition = | StoppingCondition of (ReturnType -> ReturnType -> int -> float -> bool)
I think I'm doing this right, from tutorials, having a type name and an identical constructor name (confusing...), for a single case discriminated union. But now I can't figure out how to apply this type to an actual function:
let Condition1 lastRet nextRet i fl =
true
How do I make Condition1 be of type StoppingCondition? I bet it's trivial. But I've tried putting StoppingCondition as the first, second or last term after let, with and without parens and colons. And everything is an error.
Thanks for the patience found here.
EDIT:
I'll try to synthesize what I lean from the four answers (as of this moment), all good:
Trying to mimic this pattern:
s : string = "abc"
I was trying to write:
type StoppingCondition = | StoppingCondition of (ReturnType -> ReturnType -> int -> float -> bool)
let condition1 lastRet nextRet i fl : StoppingCondition = // BAD
//wrong for a type alias, totally wrong for a union constructor
true
//or
let condition1 : StoppingCondition lastRet nextRet i fl = // BAD again
true
or other insertions of : Stopping Condition (trying to prefix it, in the way that constructors go, in that one line).
Now I see that to get what I was getting at I would have to do:
type StoppingCondition = | StoppingCondition of (ReturnType -> ReturnType -> int -> float -> bool)
let conditionFunc1 lastRet nextRet i fl = //...
true
let stoppingCondition1 = StoppingCondition conditionFunc1
//or
let stoppingCondition2 = StoppingCondition <| (func lastRet nextRet i fl -> false)
//and there's another variation or 2 below
And what I didn't appreciate as a big negative to this approach is how a union type is different from a type alias. A type alias of string admits of the string functions when declared--it really is a string and does "string things. A single case discriminated union of string--is not a string any more. To have it do "string things" you have to unwrap it. (Or write versions of those functions into your type (which might be wrappers of the string functions).) Likewise a type alias of my function accepts those parameters. A DU of my function is just a wrapper and doesn't take arguments. So this doesn't work with discriminated union:
let x = stoppingCondition1 ret1 ret2 2 3.0 // BAD
//"stoppingCondition1 is not a function and cannot be applied"
And there's not enough value in my case here to work around the wrapper. But a type alias works:
type StoppingAlias = ReturnType -> ReturnType -> int -> float -> bool
let stoppingCondition:StoppingAlias = fun prevRet nextRet i x -> true
let b = stoppingCondition ret1 ret2 10 1.0 // b = true
I may not have everything straight in what I just said, but I think I'm a lot closer.
Edit 2:
Side note. My question is about defining the type of a function. And it compares using a type alias and a union type. As I worked at trying to do these, I also learned this about using a type alias:
This works (from: https://fsharpforfunandprofit.com/posts/defining-functions/ ):
type Adder = decimal -> decimal -> decimal
let f1 : Adder = (fun x y -> x + y)
//or
let f2 : decimal -> decimal -> decimal = fun x y -> x + y
but these are wrong:
let (f2 : Adder) x y = x + y // bad
let (f3 x y) : (decimal -> decimal -> decimal) = x + y // bad
let (f3 : (decimal -> decimal -> decimal)) x y = x + y // bad
And some discussion on this whole issue: F# Type declaration possible ala Haskell?
(And also, yeah, "assigning a type" isn't the right thing to say either.)
You don't "make it be of type" StoppingCondition. You declare a value of type StoppingCondition and pass Condition1 as the parameter of the DU case constructor:
let stop = StoppingCondition Condition1
That means, however, that every time you want to access the function contained in your single DU case, you have to pattern match over it in some way; that can become annoying.
You say you don't want just any functions that fulfill the signature to be valid as stopping conditions; however, it seems to be specific enough to avoid "accidentally" passing in an "inappropriate" function - with that, you could do something simpler - define StoppingCondition as a type alias for your specific function type:
type StoppingCondition = ReturnType -> ReturnType -> int -> float -> bool
Now you can use StoppingCondition everywhere you need to specify the type, and the actual values you pass/return can be any functions that fulfill the signature ReturnType -> ReturnType -> int -> float -> bool.
As has been said, you have to construct an instance of a StoppingCondition from an appropriate function, for example:
let Condition1 = StoppingCondition (fun _ _ _ _ -> true)`
One nice way to do this without weird indentation or extra parentheses is a backward pipe:
let Condition1 = StoppingCondition <| fun lastRet nextRet i fl ->
// Add function code here
The signature might be long enough to justify a record type instead of four curried parameters. It's a question of style and how it'll be used; the result may look like this:
type MyInput =
{ LastReturn : ReturnType
NextReturn : ReturnType
MyInt : int
MyFloat : float }
type StopCondition = StopCondition of (MyInput -> bool)
let impossibleCondition = StopCondition (fun _ -> false)
let moreComplicatedCondition = StopCondition <| fun inp ->
inp.MyInt < int (round inp.MyFloat)
To call the function inside a StopCondition, unwrap it with a pattern:
let testStopCondition (StopCondition c) input = c input
Specify the return type of a function is done like this:
let Condition1 lastRet nextRet i fl :StoppingCondition=
true
of course, this won't compile as true is not of the correct type.
I suspect the actual definition you want is closer to
let Condition1 :StoppingCondition=
true
though, as the type looks like it contains the function arguments.
Expanding on this, you can define such a function like:
let Condition1=fun a b c d -> StoppingCondition(fun a b c d -> whatever)
but this whole thing is pretty ugly.
Realistically, I think it is better to put all the functions in an array, which will force the types to match
So, it seems to me that you things you might want to with StoppingConditions are to create some predefined type of stopping condition.
Here are some examples of some possible stopping conditions:
let stopWhenNextGreaterThanLast = StoppingCondition (fun last next _ _ -> next > last)
let stopWhenLastGreaterThanLast = StoppingCondition (fun last next _ _ -> last> next)
(I've underscored the parameters I'm not using in my stopping condition definition)
Hopefully you can see that both of these values of type StoppingCondition.
Then you might want a function to determine if the stopping condition had been met given some parameters:
let shouldStop stoppingCond last next i value =
match stoppingCond with
|StoppingCondition f -> f last next i value
This function takes a stopping condition and the various states of your recursion and returns true or false depending on whether or not it should now stop.
This should be all you need to make use of this approach in practice.
You could extend this approach by doing something like this to cover multiple potential stopping conditions:
type StoppingCondition =
| StoppingCondition of (ReturnType -> ReturnType -> int -> float -> bool)
| Or of StoppingCondition * StoppingCondition
And modifying the shouldStop function
let rec shouldStop stoppingCond last next i value =
match stoppingCond with
|StoppingCondition f -> f last next i value
|Or (stp1, stp2) -> (shouldStop stp1 last next i value) || (shouldStop stp2 last next i value)
Now if we have a single condition, we stop when it's met or if we multiple conditions, we can check whether either of them are met.
Then you could Or together new stopping conditions from a base condition:
let stopWhenIIsEven = StoppingCondition (fun _ _ i _ -> i % 2 = 0)
let stopWhenValueIsZero = StoppingCondition (fun _ _ _ value -> value = 0.0)
let stopWhenIEvenOrValueZero = Or (stopWhenIIsEven, stopWhenValueIsZero)

What does this '()' notation mean?

I just started to learn F#. The book uses the following notation:
let name() = 3
name()
what that differs from this:
let name = 3
name
?
Before answering what () is lets get some basics defined and some examples done.
In F# a let statement has a name, zero or more arguments, and an expression.
To keep this simple we will go with:
If there are no arguments then the let statement is a value.
If there are arguments then the let statement is a function.
For a value, the result of the expression is evaluated only once and bound to the identifier; it is immutable.
For a function, the expression is evaluated each time the function is called.
So this value
let a = System.DateTime.Now;;
will always have the time when it is first evaluated or later invoked, i.e.
a;;
val it : System.DateTime = 1/10/2017 8:16:16 AM ...
a;;
val it : System.DateTime = 1/10/2017 8:16:16 AM ...
a;;
val it : System.DateTime = 1/10/2017 8:16:16 AM ...
and this function
let b () = System.DateTime.Now;;
will always have a new time each time it is evaluated, i.e.
b ();;
val it : System.DateTime = 1/10/2017 8:18:41 AM ...
b ();;
val it : System.DateTime = 1/10/2017 8:18:49 AM ...
b ();;
val it : System.DateTime = 1/10/2017 8:20:32 AM ...
Now to explain what () means. Notice that System.DateTime.Now needs no arguments to work.
How do we create a function when the expression needs no arguments?
Every argument has to have a type, so F# has the unit type for functions that need no arguments and the only value for the unit type is ().
So this is a function with one argument x of type int
let c x = x + 1;;
and this is a function with one argument () of type unit
let b () = System.DateTime.Now;;
Definitely do NOT think of () as some syntax for a function call or anything like that. It's just a value, like 3, 5, 'q', false, or "blah". It happens to be a value of type Unit, and in fact it's the only value of type unit, but really that's beside the point. () here is just a value. I can't stress that enough.
First consider
let name x = 3
What's this? This just defines a function on x, where x can be any type. In C# that would be:
int Name<T>(T x)
{
return 3;
}
Now if we look at let name () = 3 (and I somewhat recommend putting that extra space there, so it makes () look more a value than some syntactic structure) then in C# you can think of it as something like (pseudocode)
int Name<T>(T x) where T == Unit //since "()" is the only possible value of Unit
{
return 3;
}
or, more simply
int Name(Unit x)
{
return 3;
}
So we see that all let name () = 3 is, the definition of a function that takes a Unit argument, and returns 3, just like the C# version above.
However if we look at let name = 3 then that's just a variable definition, just like var name = 3 in C#.
In
let name() = 3
name()
name is a function, of type unit -> int.
In
let name = 3
name
name is an integer, of type int.
In F#, every function has an input type and an output type. The input type of let name() = 3 is unit, which has only one value (). Its output type is int, which has values from –2,147,483,648 to 2,147,483,647. As another example type bool has only two values, true and false.
So back to you question what's the usage of (). If you don't specify the input value of a function, it cannot get executed. So you have to specify an input value to your function let name()=3 to get it executed and because of its input type is unit, the only value you can use is ().
Here is another way to define the name function:
let name : (unit -> int) = (fun _ -> 3);;
and compare this to:
let name : int = 3
Using () creates a function which takes a paramter of type unit, rather than the second case which is just a simple integer.
This is particularly important when you want to control execution of the function.
The main difference is when you have
let name() =
printfn "hello"
1
vs
let name =
printfn "hello"
1
then
let t = name + name
will print "hello" once. But
let t = (name()) + (name())
will print "hello" twice.
You have to be careful with this when considering the order in which functions are evaluated.
Consider the following program:
let intversion =
printfn "creating integer constant"
1
printfn "integer created"
let funcversion() =
printfn "executing function"
1
printfn "function created"
let a = intversion + intversion
printfn "integer calculation done"
let b = (funcversion()) + (funcveriosn())
printfn "function calculation done"
This will print the following in order
creating integer constant
integer created
function created
integer calculation done
executing function
executing function
function calculation done

F# Beginner: retrieving an array of data from a server

I'm trying to grab data from a MySQL database.
Approach 2 - apply/map style
I'm using the MySQL ADO Reference to try to build this system. In particular, the example found at 21.2.3.1.7.
(using a pseudo code)
let table = build_sequence(query.read)
Where query.read returns a row in the table(Or rather, a list of elements that happen to be a row in the table). And the table variable is a list of lists that will represent a table returned from the query.
I've stared at the code given below, and it's syntax is over my head, I'm afraid.
Approach 1 - looping.
Problem 1: It's inelegant, requiring a mutable.
Problem 2: This just feels wrong, based on my prior experience with Prolog & Lisp. There's gotta be a more...functional way to do this.
I'm not sure where to begin though. Comments & thoughts?
let reader : MySql.Data.MySqlClient.MySqlDataReader = command.ExecuteReader()
let arr = []
let mutable rowIter = 0
let readingLoop() =
while(reader.Read()) do
rowIter = rowIter + 1
for i = 0 to reader.FieldCount do
//set arr[someiterator, i] = reader.GetValue[i].ToString())
The Seq type has a neat function for handling database cursors called generate_using (see F# Manual and the Data Access chapter in Foundations of F#). This is a higher order function that takes one function to open the cursor and another (called repeatedly) to process records from the cursor. Here is some code that uses generate_using to execute a sql query:
let openConnection (connectionName : string) =
let connectionSetting = ConfigurationManager.ConnectionStrings.Item(connectionName)
let connectionString = connectionSetting.ConnectionString
let connection = new OracleConnection(connectionString)
connection.Open()
connection
let generator<'a> (reader : IDataReader) =
if reader.Read() then
let t = typeof<'a>
let props = t.GetProperties()
let types = props
|> Seq.map (fun x -> x.PropertyType)
|> Seq.to_array
let cstr = t.GetConstructor(types)
let values = Array.create reader.FieldCount (new obj())
reader.GetValues(values) |> ignore
let values = values
|> Array.map (fun x -> match x with | :? DBNull -> null | _ -> x)
Some (cstr.Invoke(values) :?> 'a)
else
None
let executeSqlReader<'a> (connectionName : string) (sql : string) : 'a list =
let connection = openConnection connectionName
let opener() =
let command = connection.CreateCommand(CommandText = sql, CommandType = CommandType.Text)
command.ExecuteReader()
let result = Seq.to_list(Seq.generate_using opener generator)
connection.Close()
connection.Dispose()
result
For example to list all the tables in an Oracle database we need to define a column definition type and invoke executeSqlReader as follows:
type ColumnDefinition = {
TableName : string;
ColumnName : string;
DataType : string;
DataLength : decimal;
}
let tableList = executeSqlReader<ColumnDefinition>
"MyDatabase"
"SELECT t.table_name, column_name, data_type, data_length FROM USER_TABLES t, USER_TAB_COLUMNS c where t.TABLE_NAME = c.table_name order by t.table_name, c.COLUMN_NAME"
It can be hard to work with imperative APIs in a non-imperative way. I don't have MySql handy, but I made an approxmiation, hopefully this will provide inspiration. Seq.unfold is a function people find pretty awesome once they grok it. List.init (or Array.init) are also handy for initializing known-size data structures without using mutables.
#light
type ThingLikeSqlReader() =
let mutable rowNum = 0
member this.Read() =
if rowNum > 3 then
false
else
rowNum <- rowNum + 1
true
member this.FieldCount = 5
member this.GetValue(i) = i + 1
let reader = new ThingLikeSqlReader()
let data = reader |> Seq.unfold (fun (reader : ThingLikeSqlReader) ->
if reader.Read() then
Some (List.init reader.FieldCount (fun i -> reader.GetValue(i)), reader)
else
None) |> Seq.to_list
printfn "%A" data