from the documentation at https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/keyword-reference:
it looks like 'and' can be used in the four categories above. With records and constraints, I see the use cases. But can anyone illustrate the uses in let bindings and members?
I see an example in this gist: https://gist.github.com/theburningmonk/3199252 but I'm not sure how it works.
For let bindings and members, the and keyword is used for definining mutually recursive types and functions. A silly example with functions:
let rec f x = g x + 1
and g x = f x - 1
A silly example with classes:
type A() =
member x.B = B()
and B() =
member x.A = A()
The case with classes really covers all possible type definitions, including records and discriminated unions:
type A =
| Aaa of int
| Aaaa of C
and C =
{ Bbb : B }
and B() =
member x.Bbb = Aaa 10
Related
using the lib 'FsToolkit.ErrorHandling'
and the following code:
let f x =
if x % 2 = 0 then Ok $"even {x}" else Error $"odd {x}"
let xx =
validation {
let! a = f 1
and! b = f 2
and! c = f 3
return $"{a} {b} {c}"
}
printfn $"{xx.GetType()}"
The output is a
Result<string, string list>
Or, more specifically:
Microsoft.FSharp.Core.FSharpResult2[System.String,Microsoft.FSharp.Collections.FSharpList1[System.String]]
But the IDE (Rider) sees it differently:
Is this an expected behavior for some reason? or could it be a bug?
Validation<'a, 'err> is a type alias for Result<'a, 'err list>:
https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/f5019f10c4418426a2e182377be06beecd09876b/src/FsToolkit.ErrorHandling/Validation.fs#L3
This doesn't create a new type but creates a new way to refer to an existing type, which means that they can be used interchangeably.
I read the following type definition. What does it do?
type StreamCell<'a> =
| Nill
| Cons of 'a * Stream<'a>
and Stream<'a> = Lazy<StreamCell<'a>>
I tried to define the value with the type.
let x = Lazy(1::2::Nill) // Type is Lazy<list<int>>
let y = Lazy(Nill::1) // Lazy<StreamCell<obj>>
I thought the type of x and y should be StreamCell?
The and in F# exists to define recursive types. In most other languages there exists no order. Once you define a class, function and so on. You can access it. But in F# order is important. You only can access thinks that are already defined.
Because of this, usually it would not be possible to define recursive types, or in generall circular types. What i think is a good idea. But sometimes, you want this, and in this case, you must define the types that should be recursive with an and.
A simple example would be
type A = A of B
type B = B of A
and this will fail. Because when you define A, there is no B. So B must be defined before A. But you cannot define B before A because it depends on A.
So instead of using type you use and instead.
type A = A of B
and B = B of A
You cannot create a value of this type because it would be infinite, but it's only for understanding the problem. Next, your example is not the best, because.
and Stream<'a> = Lazy<StreamCell<'a>>
is only a Type Alias. Here you define Stream<'a> as an alias to Lazy<StreamCell<'a>>. But the compiler will usually not use Stream<'a>. This only helps if you would write the type manually in your function definitions. Your definition could also be.
type StreamCell<'a> =
| Nill
| Cons of 'a * Lazy<StreamCell<'a>>
In your example
let x = Lazy(1::2::Nill)
You use :: and this IS NOT the Cons you define with your stream. You will use the cons operator that is defined with F#, and that is the built-in list. This is the reason why you see Lazy<List<int>> as a type.
If you want to define your stream with two values you need to write.
let x = Cons(1,lazy Cons(2, lazy Nill))
As a general note i would rename Cons to Next or something else. To avoid confusion and create helper function to create Nill and Next values.
Addition
and can also be used to change the Order of definition, and make it more obvious, which types belong together.
type Person = {
Name: string
Sex: Sex
}
and Sex =
| Male
| Female
let person = { Name="David"; Sex=Male }
Example
Here is a full-blown example how i would do a Stream type on what you provided.
type Stream<'a> =
| Nill
| Next of 'a * Lazy<Stream<'a>>
let nill = Nill
let next h t = Next(h,t)
let rec unfold gen state =
match gen state with
| None -> Nill
| Some(x,state) -> next x (lazy unfold gen state)
let rec fold f acc xs =
match xs with
| Nill -> acc
| Next(h,t) -> fold f (f acc h) (t.Force())
let rec rev stream =
fold (fun acc x -> next x (lazy acc)) nill stream
let toList stream =
fold (fun acc x -> x::acc ) [] (rev stream)
let rec take x stream =
if x > 0 then
match stream with
| Nill -> Nill
| Next(h,t) -> next h (lazy take (x-1) (t.Force()))
else
Nill
let fromTo start stop =
unfold (fun acc -> if acc<stop then Some(acc,acc+1) else None) start
let x = next 1 (lazy next 2 (lazy next 3 (lazy nill)))
let y = next 1.0 (lazy next 2.0 (lazy next 3.0 (lazy nill)))
printfn "%A" (toList (take 2 x))
printfn "%A" (toList (take 2 y))
printfn "%A" (toList (take 2 (fromTo 1 100)))
printfn "%A" (toList (take 5 (fromTo 1 1_000_000_000)))
I found the following piece of code in the fantomas library for F#. I am having a hard time understanding this as an F# noob. From what I understand, it's a custom operator that takes 3 arguments, but why would an operator need 3 arguments? And what exactly is happening here?
/// Function composition operator
let internal (+>) (ctx: Context -> Context) (f: _ -> Context) x =
let y = ctx x
match y.WriterModel.Mode with
| ShortExpression infos when
infos
|> Seq.exists (fun x -> x.ConfirmedMultiline)
->
y
| _ -> f y
Here's an example of how fantomas uses this operator in ther CodePrinter module.
let short =
genExpr astContext e1
+> sepSpace
+> genInfixOperator "=" operatorExpr
+> sepSpace
+> genExpr astContext e2
Operators behave a lot like function names:
let (++) a b c d =
a + b + c + d
(++) 1 2 3 4
One difference is that operators can be used infix. An operator with more than 2 arguments allows infix only for the first 2 arguments:
// the following is equal:
let f = (++) 1 2 // like a function name
let f = 1 ++ 2 // with infix
f 50 60
I did not find how fantomas uses the operator you mention, would be curious, in particular since fantomas is a high profile f# project.
It might be instructive to compare this to the regular function composition operator, >>. The definition for this is:
let (>>) (f : a' -> b') (g : b' -> c') (x : a') =
g ( f x )
Esentially, it applies f to x, and then applies g to the result.
If we have the following functions:
let plusOne i = i + 1
let timesTwo j = j * 2
And apply it the following way:
let plusOneTimesTwo = plusOne >> timesTwo
What we're really doing is something like this:
let plusOneTimesTwo = (>>) plusOne timesTwo
When you don't supply all of the necessary arguments to a function (in this case, x), what you get is a function that takes the remaining arguments and then returns what the original function would return (this is partial application.) In this case, plusOneTimesTwo's function signature is now x : int -> int.
The example you've listed is essentially the same thing, but it's performing additional logic to determine whether it wants to apply the second function to the result y or to return it as-is.
I want to extend some system types and later use them via inlining
type System.String with
member this.foo n = this + "!" + n
type System.Boolean with
member this.foo n = sprintf "%A!%A" this n
Now I call these extension methods
let x = "foo".foo "bar"
let y = true.foo "bar"
which gives me this
- val x : System.String = "foobar"
- val y : string = "true!"bar""
All fine and dandy - but now I want to wrap the call to .foo into an inline function
let inline foo n v = (^T : (member foo : ^N -> ^S) v, n)
let z = foo "bar" "baz"
Only now I get a compiler error telling me that
> The type 'string' does not support the operator 'foo':
well ... it does!
Can somebody explain whats going on?
Extension methods are not taken into account in static member constraints (possible duplicate of this) and this is a general problem when you want to implement generic code using member constraints and make it work also with already defined or primitive types.
See the user voice request, also the workarounds mentioned here and Don Syme's explanation of why it's complicated to implement it in the F# compiler.
If you follow the links there you will see currently the way to workaround it basically involves creating an intermediate type and overloads for all the known types and a generic one for the extensions.
This is a very basic example of how to workaround it:
type Foo = Foo with
static member ($) (Foo, this:int) = fun (n:int) -> this + n
static member ($) (Foo, this:string) = fun n -> this + "!" + n
static member ($) (Foo, this:bool) = fun n -> sprintf "%A!%A" this n
let inline foo this n = (Foo $ this) n
//Now you can create your own types with its implementation of ($) Foo.
type MyType() =
static member ($) (Foo, this) =
fun n -> printfn "You called foo on MyType with n = %A" n; MyType()
let x = foo "hello" "world"
let y = foo true "world"
let z = foo (MyType()) "world"
You can enhance it by adding an explicit generic overload for new types:
// define the extensions
type System.String with
member this.foo n = this + "!" + n
type System.Boolean with
member this.foo n = sprintf "%A!%A" this n
// Once finished with the extensions put them in a class
// where the first overload should be the generic version.
type Foo = Foo with
static member inline ($) (Foo, this) = fun n -> (^T : (member foo : ^N -> ^S) this, n)
static member ($) (Foo, this:string) = fun n -> this.foo n
static member ($) (Foo, this:bool) = fun n -> this.foo n
// Add other overloads
static member ($) (Foo, this:int) = fun n -> this + n
let inline foo this n = (Foo $ this) n
//later you can define any type with foo
type MyType() =
member this.foo n = printfn "You called foo on MyType with n = %A" n; MyType()
// and everything will work
let x = foo "hello" "world"
let y = foo true "world"
let z = foo (MyType()) "world"
You can further refine it by writing the static constraints by hand and using a member instead of an operator (see an example here),
At the end of the day you will end up with something like this generic append function from FsControl.
Statically resolved type constraints do not support extension methods. It's just not a feature of F#.
If you would like F# to gain support for higher-kinded polymorphism, you can vote for it on user voice.
F# assigns function arguments via pattern matching. This is why
// ok: pattern matching of tuples upon function call
let g (a,b) = a + b
g (7,4)
works: The tuple is matched with (a,b) and a and b are available directly inside f.
Doing the same with discriminated unions would be equally beneficial, but I cannot get it to done:
// error: same with discriminated unions
type A =
| X of int * int
| Y of string
let f A.X(a, b) = a + b // Error: Successive patterns
// should be separated by spaces or tupled
// EDIT, integrating the answer:
let f (A.X(a, b)) = a + b // correct
f (A.X(7, 4))
Is pattern matching as part of the function call limited to tuples? Is there a way to do it with discriminated unions?
You need extra parens:
let f (A.X(a, b)) = a + b