Replies: 17 comments 5 replies
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
-
Type solver fails to infer the method return value with regard to base-overloads taking callbacks// Dummy, these are actually defined by Akka
[<AllowNullLiteral>]
type IUntypedActorContext = interface end
type ReceiveActor() =
static member Context : IUntypedActorContext = null
member x.Receive<'T>(cb:'T->bool) = ()
member x.Receive(cb:'T->unit, ?pred:'T->bool) = ()
// my types
type IReplyChannel<'T> =
abstract member ReplyWithValue : 'T -> unit
abstract member ReplyWithException : exn -> unit
type InternalCallbackActorMessage<'T> =
| GetSelfInstance of IReplyChannel<CallbackActor<'T>>
| GetSelfContext of IReplyChannel<IUntypedActorContext>
and CallbackActor<'T>(cb:'T->unit) as self =
inherit ReceiveActor()
do base.Receive<'T>(cb)
do base.Receive<InternalCallbackActorMessage<'T>>(self.OnInternalMsg)
member self.OnInternalMsg(msg) =
match msg with
| GetSelfInstance rc -> rc.ReplyWithValue self
| GetSelfContext rc -> rc.ReplyWithValue ReceiveActor.Context Actual:
Workaround: Explicitly annotate the member method as returning unit:
Reasoning: I think it is reasonable to expect this to work, because there is no uncertainty involved in the actual member implementation: it contains a match where both branches contain unit, so it itself must return unit. So it should short-circuit that. And as soon as it knows that the member method returns unit, it can also infer the correct base overload. |
Beta Was this translation helpful? Give feedback.
-
Edited: thanks, updated! I still keep it as an example, because normally you would not expect to need to update the callsite when adding an optional parameter. But this is probably "wont-fix". Applying tupled values to a constructor is inconsistent wrt optional parametersThis is probably according to spec, but it may be worth to improve this, if it is possible without introducing ambiguity.
module Program
type T(x : int*int) = class end
let t = T(1, 2)
type T(x : int*int, ?y : string) = class end
let t = T(1, 2) ❌
Workaround: |
Beta Was this translation helpful? Give feedback.
-
@0x53A That's not the correct declaration for an optional parameter, try this
when this works:
|
Beta Was this translation helpful? Give feedback.
-
Can record type inference be improved in this case? type Record1 = { id: int; title: string }
type Record2 = { id: int; name: string }
let test1 x = // OK
let a = x.id
let b = x.name
()
let test2 x =
let a = x.id
let b = x.title // ERR
()
let test3 x = // OK
let b = x.title
let a = x.id
()
/cc @dsyme |
Beta Was this translation helpful? Give feedback.
-
@gsomix Just use a type annotation in such a case, it's not that bad. |
Beta Was this translation helpful? Give feedback.
-
@dsyme OK, but such behaviour may be confusing, especially for beginners. Can error message be improved instead? Thanks! /cc @forki |
Beta Was this translation helpful? Give feedback.
-
Definitely :) |
Beta Was this translation helpful? Give feedback.
-
@0x53A re this:
Could you give more detail as to why? thanks |
Beta Was this translation helpful? Give feedback.
-
let! binding in CE with extension overloads causes FS0072: Lookup on object of indeterminate typeCopied from dotnet/fsharp#4472. I don't think I have the necessary insight into how type inference works to identify the root cause and create a separate RFC on my own. The following type AsyncResultBuilder() =
member __.Return (value: 'a) : Async<Result<'a, 'b>> =
async { return Ok value }
member __.Bind
(asyncResult: Async<Result<'a, 'c>>,
binder: 'a -> Async<Result<'b, 'c>>)
: Async<Result<'b, 'c>> =
async {
let! result = asyncResult
let bound =
match result with
| Ok x -> binder x
| Error x -> async { return Error x }
return! bound
}
[<AutoOpen>]
module Extensions =
// Having Async<_> members as extensions gives them lower priority in
// overload resolution between Async<_> and Async<Result<_,_>>.
type AsyncResultBuilder with
member this.Bind
(asnc: Async<'a>,
binder: 'a -> Async<Result<'b, 'c>>)
: Async<Result<'b, 'c>> =
let asyncResult = async {
let! x = asnc
return Ok x
}
this.Bind(asyncResult, binder)
let asyncResult = AsyncResultBuilder()
let f () =
asyncResult {
let! str = asyncResult { return "" }
return str.Length
^^^^^^^^^^
} Expected behaviorEverything compiles and works fine. Actual behaviorI get the following error on
Note that intellisense is able to determine the type: Known workaroundsAdd a type annotation: let! (str: string) = asyncResult { return "" } The problem also disappears if you remove the extension overload, but this of course has other undesired consequences. |
Beta Was this translation helpful? Give feedback.
-
@dsyme @0x53A type Class(fileName) =
member this.Resolve(s) =
let c = Class.FromSource "s"
let c = c.T1("s")
let c = c.T2("s")
let c = c.T3("s")
()
member private c.T1(s) = c
member private c.T2(s) = c
member private c.T3(s) = c
static member FromSource(source:string) : Class = Class(source) Which doesn't compile with various
|
Beta Was this translation helpful? Give feedback.
-
Linking this: dotnet/fsharp#7860 We've seen enough bug reports come in about not piping into generic functions needing type annotations that I think it's worth bringing up: type CartItem = {
ItemID: string
ItemDescription: string
ItemPrice: double
Qty: int
}
type OrderHistoryItem = {
SlipNumber: string
OrderDate: DateTime
MealTimeID: string
PickupLocation: string
ItemID: string
ItemDescription: string
ItemPrice: decimal
Qty: int
}
let subtotalItems (items:CartItem list) =
items
|> List.groupBy (fun x -> (x.ItemID, x.ItemDescription, x.ItemPrice) )
|> List.map (fun ((id,desc,price),itms) ->
{
ItemID= id
ItemDescription= desc
ItemPrice= price
Qty= List.sumBy (fun x -> x.Qty) itms // ERROR - 'itms' type mismatch
}) I'd propose that this should not error anymore. It's true that piping makes this problem go away, but it's clearly not discoverable given the bug reports we get. However, the same should not apply to arbitrary members on classes. Although this would be a benefit to the user, the complexity of crawling type heirarchies should be something we're not doing here. |
Beta Was this translation helpful? Give feedback.
-
I'll add one: Overload resolution fails to determine the appropriate base constructor to call based on static member type C() =
inherit ResizeArray<int>(C.N():int) // Required!
let t = ResizeArray<int>(C.N())
member _.M() = ResizeArray<int>(C.N())
static member N() = 4 |
Beta Was this translation helpful? Give feedback.
-
I'd like to see better type inference for record copy-and-update expressions. When there are multiple matching record types in scope, it should default to the latest one, just like when using type Record1 = { X: int; Y: string }
type Record2 = { X: int }
// Successfully infers Record2
let getX a = a.X
// Fails with FS0667: Labels do not uniquely determine a corresponding record type.
// Ideally it should infer Record2.
let setX value a = { a with X = value } |
Beta Was this translation helpful? Give feedback.
-
I'm converting this to a discussion because so many things have been thrown into one thread, and most are not concrete proposals (separately - I checked one case list and it has been fixed and minmized that) There's real value here about where type annotations are needed, but currently each one is by design and I'm not sure what the concrete proposal is except that people "want it to work". |
Beta Was this translation helpful? Give feedback.
-
Honestly I would enjoy seeing type inference everywhere, to the point it feels like you are using reflections when you are writing codes, imo it is way more important than performance, of course we don't have to make it slow, but we can get conveniences of C# dynamic which we can say something is there to pretend it is so even though we are passing something to a function later we can use it now and that saves a lot wiring and doesn't have to make everything slower |
Beta Was this translation helpful? Give feedback.
-
The type inference is one of the awesome parts of F#. Unfortunately, there are cases where it fails in (for someone unfamiliar with the exact spec and algorithm) trivial cases. Even though most can be fixed with simple workarounds, this can be off-putting for a newcomer, and annoying for even a "veteran".
I would like to gather code samples here which you would expect to compile, but which don't, for whatever reason.
From this selection of currently failing code samples, we can then create PRs (if they are bugs), or more specific suggestions and RFCs.
/cc @matthid, I think you also mentioned a few failing cases in a previous issue, please add them here if you can remember them
Beta Was this translation helpful? Give feedback.
All reactions