Can I add params to computation expressions constructors in F# - f#

Given the following code
type Init<'a> = Init of 'a
type Right<'a> = Right of 'a
type Left<'a> = Left of 'a
type MovesBuilder(init) =
member x.Yield(()) = Init init
[<CustomOperation("left")>]
member x.Left(Init v) = Left "Left"
[<CustomOperation("right")>]
member x.Right(Left v) = Right "Right"
let moves v = MovesBuilder(v)
I'd like to do this
let test1 =
moves 1 { left
right }
But I get this error message
moves 1 { left
----------^^^^
stdin(565,13): error FS3099: 'left' is used with an incorrect number of
arguments. This is a custom operation in this query or computation
expression. Expected 0 argument(s), but given 1.
I also tried this
let test2 =
moves(1) { left
right }
let test3 =
(moves 1) { left
right }
type MovesFactory(init) =
member self.create = MovesBuilder(init)
let test4 =
MovesFactory(3).create { left
right }
Sadly all give me an error.

Related

In F#, how to change a property of a property within a record (Syntax?)

Lets assume I have a record like:
type Page = { footersize: Size }
what is the correct syntax to change only the Height of the footersize:
{ page with footersize = ??? }
TIA
Edit#1: "Size" is the size of a FrameworkElement, i.e.,
Size is in the .Net world as Size(width,height) and is a structure, not record. As tried below,
this does NOT work:
{p with footersize = {p.footersize with Height = 96 * 0.5}}
Error: This expression was expected to have type 'Size' but here as type Page.
The most direct way is to copy-update the whole object:
let page' =
{ page with
FooterSize = Size(page.FooterSize.Width, 100.)
}
If you have some deeper nesting, it gets a bit harder.
type Page = { FooterSize: Size }
type Document = { Title: string; Page: Page }
let document = { Title = "Fun and Profit"; Page = { FooterSize= Size(10., 10.) } }
To update height, now you have to do:
let document' = { document with Page = { document.Page with FooterSize= Size(document.Page.FooterSize.Width, 100.) }}
And that goes off screen! There's a language suggestion for nested record assignment, which would allow you to do { document with Page.FooterSize.Height = 100. } , but that's still a little ways off.
Lenses
type Lens<'a,'b> = ('a -> 'b) * ('a -> 'b -> 'a)
Don't worry if this seems confusing! It will become much clearer in time.
Lenses are the functional equivalent to C#'s property set technologyâ„¢ (basically page.FooterSize.Height = 100. in C#).
They're quite simple, just a pair of getter, setter functions.
let getFooterSize (p: Page) = p.FooterSize
let setFooterSize (p: Page) size = { p with FooterSize = size }
let getPage (d: Document) = d.Page
let setPage (d: Document) page = { d with Page = page }
let getWH (s: Size) = s.Width, s.Height
let setWH (s: Size) (w, h) = Size(w, h)
Of course, we can get values by simply using the compose right operator:
let (w, h) = (getPage >> getFooterSize >> getWH) document
But setting doesn't compose.
But with just three simple operators, we can have something very readable:
let get (getter, _) = getter
let set (_, setter) = setter
let (>=>) (get_ab, set_ab) (get_bc, set_bc) =
get_ab >> get_bc,
fun a c -> set_ab a (set_bc (get_ab a) c)
Since our lenses are just pair of getters and setters:
let pageL = getPage, setPage
let footerL = getFooterSize, setFooterSize
let sizeL = getWH, setWH
That's it. Those are our lenses. We can now compose these lenses with the fish operator (>=>) we had defined.
let (footer_w, footer_h) = get (pageL >=> footerL >=> sizeL) document
let document' = set (pageL >=> footerL >=> sizeL) document (footer_w, 100.)
Of course, you can write a lens in a much shorter form:
let pageL : Lens<Document, Page> =
(fun d -> d.Page), (fun d p -> { d with Page = p })

F# Types and Function signatures

I'm new to F# and I'm trying a few thing to get my head around the language.
I have to two types almost identical (Coordinate and Vector). Because of this type inference cannot work properly and I have hard time to specify the correct type on each function.
It somehow undestand that's a Vector here:
type Coordinate = {X:int; Y:int}
type Vector = {X:int; Y:int}
let calculateVector (origin:Coordinate) (destination:Coordinate) = { X=destination.X-origin.X; Y= destination.Y-origin.Y;}
And here when I want a return type of Coordinate, I cannot find how to specify the return it for this function:
let calculateNextCoordinate (coordinate:Coordinate) direction =
match direction with
| "N" -> { X=coordinate.X; Y=coordinate.Y-1 }
| "NE" -> { X=coordinate.X+1; Y=coordinate.Y-1 }
| "E" -> { X=coordinate.X+1; Y=coordinate.Y }
| "SE" -> { X=coordinate.X+1; Y=coordinate.Y+1 }
| "S" -> { X=coordinate.X; Y=coordinate.Y+1 }
| "SW" -> { X=coordinate.X-1; Y=coordinate.Y+1 }
| "W" -> { X=coordinate.X-1; Y=coordinate.Y }
| "NW" -> { X=coordinate.X-1; Y=coordinate.Y-1 }
| _ -> coordinate
I have this error on the default case: This expression was expected to have 'Vector' but here has type 'Coordinate'
i tired to have a look on this website for function signatures but could not something for my problem: https://fsharpforfunandprofit.com/posts/function-signatures/
Questions:
How do you fix this error?
Is it because inference type take by default the last type declared that match the properties (in my example Vector)?
Bonus: Is there a better way to handle this kind of situation in F#?
Thanks in advance
Since records are constructed using their member names:
{ X = 2; Y = 3}
you created a naming conflict between Vector and Coordinate. In F# the compiler always resolves to the latest definition in such cases and therefore, in your example, the compiler will interpret the record { X = ..., Y = ...} as a Vector.
There is a good article on F# records on fsharpforfunandprofit.com, that explains how to handle this easily, which I suggest you read for a good explanation.
But in short, you can prefix either member of the record instance with the record type:
{ Coordinate.X = 2; Y = 3 } // Creates a Coordinate
{ X = 2; Coordinate.Y = 3 } // Creates a Coordinate
and
{ Vector.X = 2; Y = 3 } // creates a vector

Simulating an 'Any' type in F#

I'd like to create a type with a definition a bit like this:
type LeftRight<'left, 'right> = {
Left : 'left list
Right : 'right list
}
and a couple of functions:
let makeLeft xs = { Left = xs; Right = [] }
let makeRight ys = { Left = []; Right = ys }
and I'd like to provide a 'combiner' function:
let combine l r = { Left = l.Left # r.Left; Right = l.Right # r.Right }
When I try and make something, I (obviously!) get issues as my value is generic:
let aaa = makeLeft [1;2;3]
// Value restriction. The value 'aaa' has been inferred to have generic type
// val aaa : LeftRight<int,'_a>
If I combine a left and a right, type inference kicks in and everything's A-OK:
let bbb = makeRight [1.0;2.0;3.0]
let comb = combine aaa bbb // LeftRight<int, float>
but I want to use one with only lefts on its own. I tried creating an 'Any' type:
type Any = Any
and explicitly specified the types on makeLeft and makeRight:
let makeLeft xs : LeftRight<_, Any> = { Left = xs; Right = [] }
let makeRight ys : LeftRight<Any, _> = { Left = []; Right = ys }
which makes the value definitions happy, but makes the combine function sad:
let combined = combine aaa bbb
// Type mismatch. Expecting a
// LeftRight<int,Any>
// but given a
// LeftRight<Any,float>
// The type 'int' does not match the type 'Any'
I feel like there's probably a way around this with loads of voodoo with .Net's overloading of function calls, but I can't make it work. Has anyone tried this before/have any ideas?
The value restriction is not a problem in this case, you need the result of makeLeft or makeRight be generic if you ever hope to use them generically further down the line.
In F# (and OCaml), generic syntactic values must be explicitly marked as such, with full type annotations. Indeed, the compiler reports this:
error FS0030: Value restriction. The value 'aaa' has been inferred to
have generic type
val aaa : LeftRight Either define 'aaa' as a simple data term, make it a function with explicit arguments or, if you do
not intend for it to be generic, add a type annotation.
Without going into too much detail*, this is to avoid issues that can occur when combining polymorphism and side effects. The downside is that it does reject some perfectly safe code as a result.
So, the solution is simple, we make these values explicitly generic:
let aaa<'a> : LeftRight<int,'a> = makeLeft [1;2;3]
let bbb<'a> : LeftRight<'a, float> = makeRight [1.0;2.0;3.0]
Putting them together in FSI:
let comb = combine aaa bbb;;;
val comb : LeftRight<int,float> = {Left = [1; 2; 3];
Right = [1.0; 2.0; 3.0];}
Note that if you combine without intermediate let bindings, you no longer have a generic value and the proper type can be inferred by the compiler:
combine (makeLeft [1;2;3]) (makeRight [1.0;2.0;3.0]);;
val it : LeftRight<int,float> = {Left = [1; 2; 3];
Right = [1.0; 2.0; 3.0];}
*For more detail, check out this article.

Can I create nested Computation Expressions for Builder Like DSLs?

This is what I'd like to do:
type DirectionBuilder() =
member self.Yield(()) = []
[<CustomOperation("left")>]
member self.Left (acc, degree) = None
[<CustomOperation("right")>]
member self.Right (acc, degree) = None
[<CustomOperation("velocity")>]
member self.Velocity (acc, ()) = new VelocityBuilder()
and VelocityBuilder() =
member self.Yield(()) = []
[<CustomOperation("accelerate")>]
member self.Accesslarate (acc, v) = None
[<CustomOperation("decelerate")>]
member self.Decelerate (acc, v) = None
let direction () = new DirectionBuilder()
direction() {
right 90
left 30
velocity() {
accelerate 1
}
}
This breaks down at line
velocity() {
----^^^^^^^^
stdin(952,5): error FS3099: 'velocity' is used with an incorrect number of
arguments. This is a custom operation in this query or computation
expression. Expected 1 argument(s), but given 2.
>
Is there anyway to explain to F# that this custom operation should indeed accept a computation expression?
I suppose there is a way to almost get the syntax you want. You make velocity operation accept a type that the VelocityBuilder produces - which in your case appears to be an option. Then you create a separate computation and pass it in.
So you get something like this:
type DirectionBuilder() =
member self.Yield(()) = []
[<CustomOperation("left")>]
member self.Left (acc, degree) = None
[<CustomOperation("right")>]
member self.Right (acc, degree) = None
[<CustomOperation("velocity")>]
member self.Velocity (acc, odd: 'a option) = None
type VelocityBuilder() =
member self.Yield(()) = []
[<CustomOperation("accelerate")>]
member self.Accelerate (acc, v) = None
[<CustomOperation("decelerate")>]
member self.Decelerate (acc, v) = None
let dir = new DirectionBuilder()
let vel = new VelocityBuilder()
dir {
right 90
left 30
velocity (vel {
accelerate 1
})
}
That said, if you set out to write computation workflows, you probably should start by designing a type to represent the state of your computation. Right now you have syntax sugar, but no meat ;)
Once you have a type, a workflow can follow from that if it turns out to be useful.

how do i fix these errors generated by my computational expression that is using my custom workflow builder?

From the MSDN documentation I understand that if Run is implemented it will be called automatically at the end of the computational expression. It says that:
builder.Run(builder.Delay(fun () -> {| cexpr |}))
will be generated for the computational expression. Run and/or Delay will be omitted if they are not defined in the workflow builder. I was expecting my ReaderBuilder to return a list of MyItem objects when Run is called automatically. So I do not understand why I'm getting a type mismatch error. The errors are generated by the return statement inside the ProcedureBuilder foo at the end of my code listing here. Could someone please explain what I'm misunderstanding about workflow builders and what I have implemented incorrectly?
I'm getting the following errors:
The type ''a list' is not compatible with the type 'ReaderBuilder'
Type constraint mismatch. The type 'a list is not compatible with type ReaderBuilder The type ''a list' is not compatible with the type 'ReaderBuilder'
open System
open System.Data
open System.Data.Common
open System.Configuration
let config = ConfigurationManager.ConnectionStrings.Item("db")
let factory = DbProviderFactories.GetFactory(config.ProviderName)
type Direction =
| In
| Out
| Ref
| Return
type dbType =
| Int32
| String of int
type ReaderBuilder(cmd) =
let mutable items = []
member x.Foo = 2
member x.YieldFrom item =
items <- item::items
item
member x.Run item =
items
type ProcBuilder(procedureName:string) =
let name = procedureName
let mutable parameters = []
let mutable cmd:DbCommand = null
let mutable data = []
member x.Command with get() = cmd
member x.CreateCommand() =
factory.CreateCommand()
member x.AddParameter(p:string*dbType*Direction) =
parameters <- p::parameters
member x.Bind(v,f) =
f v
member x.Reader = ReaderBuilder(cmd)
member x.Return(rBuilder:ReaderBuilder) =
data
let (?<-) (builder:ProcBuilder) (prop:string) (value:'t) =
builder.Command.Parameters.[prop].Value <- value
type MyItem() =
let mutable _a = 0
let mutable _b = String.Empty
let mutable _c = DateTime.Now
member x.a
with get() = _a
and set n = _a <- n
member x.b
with get() = _b
and set n = _b <- n
member x.c
with get() = _c
and set n = _c <- n
let proc name = ProcBuilder(name)
let (%) (builder:ProcBuilder) (p:string*dbType*Direction) =
builder.AddParameter(p)
builder
let (?) (r:DbDataReader) (s:string) = r.GetOrdinal(s)
let foo x y =
let foo = proc "foo" % ("x", Int32, In) % ("y", String(15), In)
foo?x <- x
foo?y <- y
foo {
do! foo?x <- x
do! foo?y <- y
return foo.Reader {
let item = MyItem()
item.a <- r.GetInt32("a")
item.b <- r.GetString("b")
item.c <- r.GetDateTime("c")
yield! item
}
}
The problem in your example is that the foo.Reader { ... } block has a return type MyItem list (because this is what the Run member of the ReaderBuilder type returns). However, the Return member of ProcBuilder expects an argument of type ReaderBuilder.
The data field of ReaderBuilder will be always an empty list, so this is also suspicious. I think you probably want to change the Return of ProcBuilder to take an argument MyItem list instead.
However, I think that using custom computation builder for database access doesn't really give you much advantage. You're not creating a "non-standard computation" in some sense. Instead, you probably just want a nice syntax for calling commands & reading data. Using the dynamic operator can make this quite elegant even without computation builders - I wrote an article about this some time ago.

Resources