I want to be able to explicitly write type signatures in my code.
VS code will (eventually) generate sort of ghost signatures, but I actually want to explicitly take these generated signatures and type the code.
Any ideas? I could use FSI, but that can be quite a cumbersome technique.
Ideally I'd right click and "generate signature"..though that doesn't always fit peoples coding style...I tend to write code;
let f : int -> string =
fun i -> i.ToString()
You can get the type of an F# function using the Compiler Services SDK. This would require writing a custom analyzer for your projects, but it should be a reusable component that you can integrate into your development process once implemented. The basic steps to resolve every function's type signature would be:
Create an F# Type Checker (FSharpChecker) instance.
Load your project options (FSharpProjectOptions).
Parse and check each file (FSharpChecker.parseAndCheckFileInProject).
Retrieve the Declarations list from each type-checker result (FSharpCheckFileAnswer).
Print the type signature (FSharpType) for each declaration.
Here's a quick solution I put together as a starting point:
#r #"FSharp.Compiler.Service.25.0.1\lib\net45\FSharp.Compiler.Service.dll"
#r #"FSharp.Compiler.Service.ProjectCracker.25.0.1\lib\net45\FSharp.Compiler.Service.ProjectCracker.dll"
open Microsoft.FSharp.Compiler.SourceCodeServices
open System
open System.IO
type Namespace =
{
Name: string
XmlDoc: System.Collections.Generic.IList<string>
}
type Declaration =
| Namespace of Namespace * Declaration list
| Module of FSharpEntity * Declaration list
| Class of FSharpEntity * Declaration list
| Interface of FSharpEntity * Declaration list
| Enum of FSharpEntity * Declaration list
| Record of FSharpEntity * Declaration list
| Union of FSharpEntity * Declaration list
| Function of FSharpMemberOrFunctionOrValue
| Binding of FSharpMemberOrFunctionOrValue
let checker = FSharpChecker.Create(1, true)
let getProject projectFile =
ProjectCracker.GetProjectOptionsFromProjectFile(projectFile)
let private isNamespace (declaration: FSharpImplementationFileDeclaration) =
match declaration with
| FSharpImplementationFileDeclaration.Entity (entity, children) -> entity.IsNamespace
| _ -> false
let rec private getDeclaration nsSoFar (declaration: FSharpImplementationFileDeclaration) =
[
match declaration with
| FSharpImplementationFileDeclaration.Entity (entity, children) ->
if entity.IsNamespace then
if children.Length = 1 && children.Head |> isNamespace
then match nsSoFar with
| Some ns -> yield! getDeclaration (Some <| sprintf "%s.%s" ns entity.DisplayName) children.Head
| None -> yield! getDeclaration (Some entity.DisplayName) children.Head
else match nsSoFar with
| Some ns ->
let nsEntity = {Name = sprintf "%s.%s" ns entity.DisplayName; XmlDoc = entity.XmlDoc}
yield Namespace (nsEntity, children |> List.collect (getDeclaration nsSoFar))
| None ->
let nsEntity = {Name = entity.DisplayName; XmlDoc = entity.XmlDoc}
yield Namespace (nsEntity, children |> List.collect (getDeclaration nsSoFar))
elif entity.IsClass then
yield Class (entity, children |> List.collect (getDeclaration nsSoFar))
elif entity.IsInterface then
yield Interface (entity, children |> List.collect (getDeclaration nsSoFar))
elif entity.IsEnum then
yield Enum (entity, children |> List.collect (getDeclaration nsSoFar))
elif entity.IsFSharpModule then
yield Module (entity, children |> List.collect (getDeclaration nsSoFar))
elif entity.IsFSharpRecord then
yield Record (entity, children |> List.collect (getDeclaration nsSoFar))
elif entity.IsFSharpUnion then
yield Union (entity, children |> List.collect (getDeclaration nsSoFar))
else
()
| FSharpImplementationFileDeclaration.MemberOrFunctionOrValue (func, _, _) ->
if func.IsValCompiledAsMethod
then yield Function func
else yield Binding func
| _ -> ()
]
let getDeclarations (project: FSharpProjectOptions) file =
async {
let source = File.ReadAllText file
let! (parseResults, checkResults) = checker.ParseAndCheckFileInProject(file, 1, source, project)
return
match checkResults with
| FSharpCheckFileAnswer.Succeeded checkInfo ->
match checkInfo.ImplementationFile with
| Some implementation -> implementation.Declarations |> List.collect (getDeclaration None)
| None -> failwithf "No Implementation Available for File %s" file
| error -> failwithf "Error Checking File %s:\r\n%A" file error
}
let getDeclarationsForScript file =
async {
let source = File.ReadAllText file
let! (project, _) = checker.GetProjectOptionsFromScript(file, source)
return! getDeclarations project file
}
Then, if we have a sample script file called "Test.fsx" with a function like your example inside it (let f i = sprintf "%d" i), we can print the function's signature like so:
let getTypeName (t: FSharpType) =
t.Format(FSharpDisplayContext.Empty).Replace("Microsoft.FSharp.Core.", "")
let rec printFunctionSignatures declarations =
for declaration in declarations do
match declaration with
| Namespace (_, ds) -> printFunctionSignatures ds
| Module (_, ds) -> printFunctionSignatures ds
| Function f -> f.FullType |> getTypeName |> printfn "%s: %s" f.DisplayName
| _ -> () // Handle all the other cases
getDeclarationsForScript "Test.fsx"
|> Async.RunSynchronously
|> printFunctionSignatures
This will pint out:
f: int -> string
Related
Suppose:
The events are A perceived, B perceived or Ping perceived.
A possible sequence of events could be A,A,A,B,Ping.
The states are InA, InB, PingMissing.
The rules are
No Ping in all events -> PingMissing.
A -> InA
B -> InB
(Only Ping events -> InA)
I would like to have one recommended action/ state.
I see three possibilities for the transition function f(s,e)->s:
Create a pseudo event likePingMissing perceived. Hence everything is in one function.
Two separate transition functions and combining the result.
One transition function with two states as a tuple and combining the result.
Any thoughts? Best practices?
Implementation of 2. in F# (language doesn't really matter):
type Event =
| A
| B
| Ping
type State1 =
| InA
| InB
type State2 =
| PingReceived
| PingMissing
type StateCombined =
| InA'
| InB'
| PingMissing'
let f1 s e :State1 =
match s,e with
| _, A -> InA
| _, B -> InB
| _, _ -> s
let f2 s e :State2 =
match s,e with
| _, Ping -> PingReceived
| _, _ -> s
let fCombined events =
let finalState1 = events |> Seq.fold f1 InA
let finalState2 = events |> Seq.fold f2 PingMissing
match finalState1, finalState2 with
| _, PingMissing -> PingMissing'
| InA, _ -> InA'
| InB, _ -> InB'
fCombined [A;A;A;B]
// PingMissing'
fCombined [A;A;A;B;Ping]
// InB'
I would tend to model the unified state as a tuple of the two substates (broadly in this case: "has a ping been received" and "if a ping has been received, was the last perception an A or a B"). A convenience function can then distill that into a recommendation.
This has the advantage of not reusing the sequence of events, so is a bit more compatible with a view of the events as a stream: at the very least this results in not having to refetch the events from an event store or keep the entire sequence of events in memory.
For example, in Scala (and explicitly modeling the situation where no A nor B has been perceived yet):
sealed trait Event
case object A extends Event
case object B extends Event
case object Ping extends Event
sealed trait PingState
case object PingReceived extends Event // Don't strictly need...
case object PingMissing extends Event
sealed trait LastPerceivedState
case object InA extends Event
case object InB extends Event
// ... could just as well be (Option[PingMissing], Option[LastPerceivedState])...
type State = (PingState, Option[LastPerceivedState])
// ... in which case, this is (Some(PingMissing), None)
val InitialState = PingMissing -> None
def distilledState(state: State): Either[PingMissing, Option[LastPerceivedState]] =
state match {
case (PingMissing, _) => Left(PingMissing)
case (_, lpsOpt) => Right(lpsOpt)
}
The transition function could then be written directly (taking advantage of the fact that the events can be partitioned into events which affect PingState or LastPerceivedState but never both):
val transitionF = { (state: State, evt: Event) =>
val (ps, lpsOpt) = state
evt match {
case A => ps -> Some(InA)
case B => ps -> Some(InB)
case Ping => PingReceived -> lpsOpt
}
}
In the event that there are events which affect both, then decomposing into subhandlers might simplify the code (at the expense of some possibly redundant invocations):
val pingStateTransition = { (ps: PingState, evt: Event) =>
if (ps == PingReceived) PingReceived
else if (evt == Ping) PingReceived
else ps
}
val lastPerceivedStateTransition = { (lpsOpt: Option[LastPerceivedState], evt: Event) =>
evt match {
case A => Some(InA)
case B => Some(InB)
case _ => lpsOpt
}
}
val transitionF = { (state: State, evt: Evt) =>
pingStateTransition(state._1, evt) -> lastPerceivedStateTransition(state._2, evt)
}
I have a function that accepts a reference to an enum, which I need to parse by matching the enum and reading its content. One of the variants of the enum (not in the simplified minimal working example below), may contain as value the type of the enum itself, therefore I may need to recursively call the same function to parse its value.
I would like to write a function that acts as a filter and returns an Option::Some containing a reference to the content of the enum variant, or None if the value must be discarded.
What follows is a minimal working (not-really compiling) example:
enum Data<'a> {
Value(&'a String),
Null,
}
fn main() {
let s = String::new();
let d = Data::Value(&s);
let equal = |d: &Data| -> Option<&String> {
if let Data::Value(s) = d {
Some(s)
} else {
None
}
};
parse(&d, equal);
//parse(&d, equal_filter);
}
fn equal_filter<'a>(d: &'a Data) -> Option<&'a String> {
if let Data::Value(s) = d {
Some(s)
} else {
None
}
}
fn parse<'a, F>(data: &Data<'a>, filter: F)
where
F: Fn(&Data<'a>) -> Option<&'a String>,
{
filter(data);
}
Playground.
I tried to compile the code by using a closure first, but in that case I get the error:
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:11:33
|
11 | if let Data::Value(s) = d {
| ^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 10:17...
--> src/main.rs:10:17
|
10 | let equal = |d: &Data| -> Option<&String> {
| _________________^
11 | | if let Data::Value(s) = d {
12 | | Some(s)
13 | | } else {
14 | | None
15 | | }
16 | | };
| |_____^
= note: ...so that the types are compatible:
expected &Data<'_>
found &Data<'_>
note: but, the lifetime must be valid for the expression at 18:5...
--> src/main.rs:18:5
|
18 | parse(&d, equal);
| ^^^^^
note: ...so that a type/lifetime parameter is in scope here
--> src/main.rs:18:5
|
18 | parse(&d, equal);
| ^^^^^
So I tried with a function, but a got another error:
error[E0271]: type mismatch resolving `for<'r> <for<'a, 's> fn(&'a Data<'s>) -> std::option::Option<&'a std::string::String> {equal_filter} as std::ops::FnOnce<(&'r Data<'_>,)>>::Output == std::option::Option<&std::string::String>`
--> src/main.rs:19:5
|
19 | parse(&d, equal_filter);
| ^^^^^ expected bound lifetime parameter, found concrete lifetime
|
note: required by `parse`
--> src/main.rs:30:1
|
30 | / fn parse<'a, F>(data: &Data<'a>, filter: F)
31 | | where
32 | | F: Fn(&Data<'a>) -> Option<&'a String>,
33 | | {
34 | | filter(data);
35 | | }
| |_^
I would prefer to solve the issue using the closure, but I don't know how to proceed even by using the function.
Ultimately, this is caused due to limitations in Rust's type inference. Specifically, if a closure is passed immediately to a function that uses it, the compiler can infer what the argument and return types are. Unfortunately, when it is stored in a variable before being used, the compiler does not perform the same level of inference.
Inline your closure and it works:
enum Data<'a> {
Value(&'a String),
Null,
}
fn main() {
let s = String::new();
let d = Data::Value(&s);
parse(&d, |d| match d {
Data::Value(s) => Some(s),
_ => None,
});
}
fn parse<'a, F>(data: &Data<'a>, filter: F)
where
F: Fn(&Data<'a>) -> Option<&'a String>,
{
filter(data);
}
However, I'd encourage you to instead create methods on the enum and participate in the idiomatic set of conversion functions:
enum Data<'a> {
Value(&'a String),
Null,
}
impl<'a> Data<'a> {
fn as_value(&self) -> Option<&'a str> {
match self {
Data::Value(s) => Some(s),
_ => None,
}
}
}
fn main() {
let s = String::new();
let d = Data::Value(&s);
parse(&d, Data::as_value);
}
fn parse<'a, F>(data: &Data<'a>, filter: F)
where
F: Fn(&Data<'a>) -> Option<&'a str>,
{
filter(data);
}
Your function variant doesn't work because you've put the relevant lifetime in the wrong place:
// Wrong
fn equal_filter<'a>(d: &'a Data) -> Option<&'a String>
// Right
fn equal_filter<'a>(d: &Data<'a>) -> Option<&'a String>
Using either #[deny(elided_lifetimes_in_paths)] or #[deny(rust_2018_idioms)] will guide you to this:
error: hidden lifetime parameters in types are deprecated
--> src/main.rs:12:22
|
12 | let equal = |d: &Data| -> Option<&String> {
| ^^^^- help: indicate the anonymous lifetime: `<'_>`
|
error: hidden lifetime parameters in types are deprecated
--> src/main.rs:24:28
|
24 | fn equal_filter<'a>(d: &'a Data) -> Option<&'a String> {
| ^^^^- help: indicate the anonymous lifetime: `<'_>`
See also:
How to declare a lifetime for a closure argument?
Following version is calling all functions synchronously,
I'm looking to find out how to call asynchronous functions in parallel and return all results and errors to the caller.
Request
let requestAsync (url: string) : Async<Result<string, Error>> =
async {
Console.WriteLine ("Simulating request " + url)
try
do! Async.Sleep(1000)
return Ok (url + ": body...")
with :? WebException as e ->
return Error {code = 500; message = "Internal Server Error";}
}
Test
[<TestMethod>]
member this.TestrequestAsync() =
let urls = [|
"http://www.example.com/1";
"http://www.example.com/2";
"http://www.example.com/3";
"http://www.example.com/4";
"http://www.example.com/5";
"http://www.example.com/6";
"http://www.example.com/7";
"http://www.example.com/8";
"http://www.example.com/9";
"http://www.example.com/10";
|]
urls
|> Array.map (fun url -> requestAsync url |> Async.RunSynchronously) // Async.Parallel some mismatch
// Iterate results
Ideally to be able to match Ok and Error results while iterating through results
Edit based on the answer.
let result =
urls
|> Seq.map Entity.requestDetailAsync2
|> Async.Parallel
|> Async.RunSynchronously
result
|> Array.iter Console.WriteLine // match x with Ok and Error?
Attempt
result |> Array.iter (fun data -> match data with
| Ok result -> Console.WriteLine(result)
| Error error -> Console.WriteLine(error) )
Iteration using For in
for r in result do
match r with
| Ok re -> Console.WriteLine(re)
| Error error -> Console.WriteLine(error)
You can use Async.Parallel to run many async operations in parallel:
let results =
urls
|> Seq.map requestAsync // seq<Async<'T>>
|> Async.Parallel // async<T' []>
|> Async.RunSynchronously // T' []
Here's a very similar example on MSDN.
There may be an issue with your requestAsync function return type, or a missing type definition in your example. Here's what I used to verify the solution:
type RequestError = {
code : int
message : string
}
let requestAsync (url: string) =
async {
Console.WriteLine ("Simulating request " + url)
try
do! Async.Sleep(1000)
return Ok (url + ": body...")
with :? WebException as e ->
return Error {code = 500; message = "Internal Server Error";}
}
I really often use:
try
try
with
finally
so I'm interesting if is possible to make new syntax operator to not write "try" two times.
let mytry foo bar foobar =
try
try
foo
with
| _ -> bar // weird part here, I want to have a match
finally foobar
mytry
<| foo
<| | :? SocketException ->
| _ -> // ok it looks funny but how to realize it?
<| foobar
the problems I see here are
non-common syntax, in mytry there is no try with finally keywords, just <| <| <| for each, but it's lesser trouble I guess
with: I don't know how can I realize this part. even how it will look if I can realize it...
The question is whether you really need try/finally. Most of the time try/finally is used for disposing resources even when exceptions occur. But you can always replace it by the use keyword.
For example:
open System.IO
let openFile(url: string) =
let fileStream = File.OpenText(url)
try
try
let readline = fileStream.ReadLine()
printfn "Readline: %s" readline
with
| :? IOException as ex ->
printfn "IOException: %A" ex
| ex -> printfn "Another exception: %A" ex
finally
fileStream.Dispose()
can be rewritten as:
let openFile(url: string) =
use fileStream = File.OpenText(url)
try
let readline = fileStream.ReadLine()
printfn "Readline: %s" readline
with
| :? IOException as ex ->
printfn "IOException: %A" ex
| ex -> printfn "Another exception: %A" ex
For the learning purpose, you can define mytry using high-order functions as follows:
let mytry foo bar foobar =
try
try
foo ()
with
| exn -> bar exn
finally foobar ()
But it doesn't look really nice on above example:
let myOpenFile(url: string) =
let fileStream = File.OpenText(url)
mytry (fun () -> let readline = fileStream.ReadLine()
printfn "Readline: %s" readline)
(fun ex -> match ex with
| :? IOException ->
printfn "IOException: %A" ex
| _ -> printfn "Another exception: %A" ex)
(fun () -> fileStream.Dispose())
You can write a higher-order function that takes the three parts as separate function. The body of the try would be a function unit -> 'R where 'R is the result. The exception handler will need to handle only some exceptions, so you can return option to say whether you handled the result or if you want the exception to be rethrown. The type of handler will be exn -> 'R option. The finalizer is then simply a function unit -> unit.
The usage is not as elegant as using built-in language feature, but it does the trick:
tryWithFinally
(fun () ->
1/0 ) // The nested body
(function
| :? DivideByZeroException -> Some -1 // Handle division by zero
| _ -> None ) // Rethrow any other exceptions
(fun () ->
printfn "done" )
The implementation is quite easy once you know the structure, but for completeness, here it is:
let tryWithFinally f handler finalizer =
try
try f()
with e ->
match handler e with
| Some r -> r
| None -> reraise()
finally
finalizer()
Anyway, I agree with #pad that in most of the cases, you should be fine with just use and try .. with.
I'm getting the error Procedure or function 'getfoo2' expects parameter '#x', which was not supplied.
When I set a breakpoint on line 156 in File1.fs and examine the contents of foo2, which is my DbCommand object, the parameters collection contains both of my parameters #x and #y. They both have the correct DbType and value set. So I have no idea where to look to find my problem. Is this a bug, or am I missing something somewhere else in my code? I have posted the sql for the database, my f# File1.fs, Program.fs, and the app.config. File1.fs comes before Program.fs in the project.
My system is:
Microsoft SQL Server Developer Edition (64-bit) version 10.0.4000.0
Windows 7 Professional SP1
Visual Studio 2010 SP1
Below is the source code:
stored proc, table, and sample data:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[getfoo2]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[getfoo2]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[getfoo2]
#x int,
#y varchar(15)
AS
BEGIN
SET NOCOUNT ON;
select x,y,z from foo
where
x = #x
and
y = #y
END
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[foo]') AND type in (N'U'))
DROP TABLE [dbo].[foo]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[foo](
[x] [int] NOT NULL,
[y] [varchar](15) NOT NULL,
[z] [datetime] NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
insert into foo (x,y,z) values (1,'a',NULL)
insert into foo (x,y,z) values (1,'b','Jan 1 2001 12:00AM')
insert into foo (x,y,z) values (1,'c','Jan 2 2002 12:00AM')
insert into foo (x,y,z) values (2,'a','Feb 1 2001 12:00AM')
insert into foo (x,y,z) values (2,'b',NULL)
insert into foo (x,y,z) values (2,'c','Feb 2 2001 12:00AM')
insert into foo (x,y,z) values (3,'a','Mar 1 2001 12:00AM')
insert into foo (x,y,z) values (3,'b','Mar 2 2001 12:00AM')
insert into foo (x,y,z) values (3,'c',NULL)
GO
File1.fs
module File1
open System.Configuration
open System.Data.Common
open System.Data
type Direction =
| In
| Out
| Ref
| Return
type DbType =
| AnsiString of int
| AnsiStringFixedLength of int
| Binary of int
| Boolean
| Byte
| Currency
| Date
| DateTime
| DateTime2
| DateTimeOffset
| Decimal
| Double
| Guid
| Int16
| Int32
| Int64
| Object of int
| SByte
| Single
| String of int
| StringFixedLength of int
| Time
| UInt16
| UInt32
| UInt64
| VarNumeric of int
| Xml of int
type Db(cnnName:string) =
let config = ConfigurationManager.ConnectionStrings.[cnnName]
let factory = System.Data.Common.DbProviderFactories.GetFactory(config.ProviderName)
let (|HasSize|NoSize|) p =
match p with
// no size
| Boolean -> NoSize(System.Data.DbType.Boolean)
| Byte -> NoSize(System.Data.DbType.Byte)
| Currency -> NoSize(System.Data.DbType.Currency)
| Date -> NoSize(System.Data.DbType.Date)
| DateTime -> NoSize(System.Data.DbType.DateTime)
| DateTime2 -> NoSize(System.Data.DbType.DateTime2)
| DateTimeOffset -> NoSize(System.Data.DbType.DateTimeOffset)
| Decimal -> NoSize(System.Data.DbType.Decimal)
| Double -> NoSize(System.Data.DbType.Double)
| Guid -> NoSize(System.Data.DbType.Guid)
| Int16 -> NoSize(System.Data.DbType.Int16)
| Int32 -> NoSize(System.Data.DbType.Int32)
| Int64 -> NoSize(System.Data.DbType.Int64)
| SByte -> NoSize(System.Data.DbType.SByte)
| Single -> NoSize(System.Data.DbType.Single)
| Time -> NoSize(System.Data.DbType.Time)
| UInt16 -> NoSize(System.Data.DbType.UInt16)
| UInt32 -> NoSize(System.Data.DbType.UInt32)
| UInt64 -> NoSize(System.Data.DbType.UInt64)
// has size
| AnsiString(x) -> HasSize(System.Data.DbType.AnsiString,x)
| AnsiStringFixedLength(x) -> HasSize(System.Data.DbType.AnsiStringFixedLength,x)
| Binary(x) -> HasSize(System.Data.DbType.Binary,x)
| Object(x) -> HasSize(System.Data.DbType.Object,x)
| String(x) -> HasSize(System.Data.DbType.String,x)
| StringFixedLength(x) -> HasSize(System.Data.DbType.StringFixedLength,x)
| VarNumeric(x) -> HasSize(System.Data.DbType.VarNumeric,x)
| Xml(x) -> HasSize(System.Data.DbType.Xml,x)
let dbDir (p:Direction) =
match p with
| In -> System.Data.ParameterDirection.Input
| Out -> System.Data.ParameterDirection.Output
| Ref -> System.Data.ParameterDirection.InputOutput
| Return -> System.Data.ParameterDirection.ReturnValue
member x.CreateProcedure(name) =
let cmd = factory.CreateCommand()
let cn = factory.CreateConnection()
cn.ConnectionString <- config.ConnectionString
cmd.Connection <- cn
cmd.CommandText <- name
cmd
member x.CreateParameter(name:string,typ:DbType,dir:Direction) =
let p = factory.CreateParameter()
if name.StartsWith("#") then
p.ParameterName <- name
else
p.ParameterName <- "#" + name
p.Direction <- dbDir dir
match typ with
| HasSize(t,s) ->
p.DbType <- t
p.Size <- s
| NoSize(t) -> p.DbType <- t
p
type Foo() =
let mutable x:int = 0
let mutable y:string = ""
let mutable z:option<System.DateTime> = None
member a.X with get() = x and set n = x <- n
member a.Y with get() = y and set n = y <- n
member a.Z with get() = z and set n = z <- n
let db = Db("db")
let proc name (parameters:list<string*DbType*Direction>) =
let cmd = db.CreateProcedure(name)
let param p =
db.CreateParameter p
|> cmd.Parameters.Add
|> ignore
List.iter param parameters
cmd
let (?<-) (cmd:DbCommand) (s:string) (value:'a) =
cmd.Parameters.["#" + s].Value <- value
let (<|>) (value:option<'a>) (replacement:'a) =
match value with
| Some(x) -> x
| _ -> replacement
let (?) (r:DbDataReader) (s:string) : option<'a> =
let index = r.GetOrdinal s
match r.IsDBNull index with
| true -> None
| _ -> r.GetValue index
:?> 'a
|> Some
let foo x y =
let foo2 = proc "getfoo2"
<| [ ("x",Int32,In);
("y",String(15),In) ]
foo2?x <- x
foo2?y <- y
try
foo2.Connection.Open()
use r = foo2.ExecuteReader()
[
while r.Read() do
let item = Foo()
item.X <- (r?x) <|> 1
item.Y <- (r?y) <|> ""
item.Z <- r?z
yield item
]
finally
foo2.Connection.Close()
Program.fs
open System
open System.Data
open System.Data.Common
open System.Configuration
open File1
let config = ConfigurationManager.ConnectionStrings.Item("db")
let factory = DbProviderFactories.GetFactory(config.ProviderName)
[<EntryPoint>]
let main (args : string[]) =
let foo1a = foo 1 "a"
let foo1b = foo 1 "b"
let foo1c = foo 1 "c"
for f in foo1a do
let mutable z = DateTime.Now
match f.Z with
| Some(x) -> z <- x
| None -> z <- DateTime.MinValue
printfn "%d : %s : %O" f.X f.Y z
// program exit code
0
app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="db" providerName="System.Data.SqlClient" connectionString="server=(local);uid=;pwd=;Trusted_Connection=yes;database=scratchPad"/>
</connectionStrings>
</configuration>
(Reposting from comment)
Your code looks generally fine to me, although you may want to explicitly set cmd.CommandType to CommandType.StoredProcedure inside of CreateProcedure.