I'm trying to write a simple abstract class in F# with some basic parameter validation:
[<AbstractClass>]
type Record(recordType: int16) =
let recordType: int16 = recordType
do
if recordType < 0s then
invalidArg (nameof recordType)
However, I'm getting an error on the last line: 'this if expression is missing an else branch.' I tried adding an else branch that just evaluates to null, but then the type system doesn't agree with my code. Am I doing something wrong? Should I be validating my arguments using some other method?
The problem is that invalidArg requires a parameter name (which you've passed as nameof recordType as well as an error message, which you left off, and your if branch returns a function (string -> 'a as the return type is unknown/unreachable since the exception is thrown).
If you look at the docs for invalidArg, you see this: invalidArg parameter-name error-message-string. Your code is effectively like this:
// This is a function: string -> 'a (as it raises an exception, the return type is unknown)
let partialInvalidArg = invalidArg (nameof recordType)
if recordType < 0s then
partialInvalidArg // You're returning a function, not unit, so the compiler complains
If you include the error message, the function is actually called in the branch, and this will compile fine:
[<AbstractClass>]
type Record(recordType: int16) =
let recordType: int16 = recordType
do
if recordType < 0s then
invalidArg (nameof recordType) "The recordType must be greater or equal to zero"
Related
I am working with F# to develop PowerShell tooling. I am currently running into a block because Async<_>is a generic type that is not derived from a non-generic type, so I can't request an Async<_> or Async as a parameter value - I have to specify the exact generic type parameter.
(For those unfamiliar with the interaction between these two languages, I can write a class in a .NET language such as F#, derive it from a class in the PowerShell library, and give it a specific attribute and when I run PowerShell and import my library, my class is exposed as a command. The command type can't be generic. Properties of the type are exposed as PowerShell parameters.)
As far as I'm aware I can't avoid this by having a generic member on a non-generic type, so ideally I'd have a transformation attribute (for non-PS users, transformation attributes effectively perform type conversion during runtime parameter binding) to turn Async<_> into Async<obj>. For the most part, this would work great for me. However, I can't figure out a way to check if a value is Async<_>, because the check computation :? Async<_> at compile time ends up as computation :? Async<obj>, which is not, unfortunately, the same, and returns false when passed Async<int>.
I ran into a similar issue in C# and was able to leverage the dynamic keyword after running a reflection test, and making the parameter be of the derived base type System.Threading.Tasks.Task, e.g.
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHeirarchy;
var isTaskOf = task.GetType()
.GetProperty("GetAwaiter", flags)
.PropertyType
.GetMethod("GetResult", flags)
.ReturnType != typeof(void);
if (isTaskOf) {
var result = await (dynamic)task;
}
I am willing to do something like this in F# if possible, but:
I have not ben able to successfully get the dynamic lookup operator ? to compile. Specifically, "none of the types 'Async<'a>, string' support the '?' operator". Not sure what I'm doing wrong as the explanations look straightforward and I can't find any other reports of this message or requirements for that operator.
I don't know if that would even work or if that operator is only used to dynamically access a member of an object.
The solutions I have tried are:
/// Transform from Async<_> to Async<obj>
override _.Transform(_, item : obj) : obj =
match item with
// only matches Async<obj>. I get a compiler warning that _ is constrained to obj
| :? Async<_> as computation ->
let boxedComputation : Async<obj> = async { return! computation }
boxedComputation
// if the value is not an async computation, let it pass through. This will allow other transformation or type converters to try to convert the value
| _ -> item
override _.Transform(_, item) =
// no compiler warning about the type being constrained to obj, but the if test does not pass unless item is Async<obj>
if (item :? Async<_>) then async { return! item :?> Async<_> }
else item
The other thing I can think of is to use reflection entirely - get the async type, call all of the AsyncBuilder methods reflectively to create a computation expression, and then cast it to Async. As I'm fairly new to F# I'm not sure how well I'd be able to piece together a computation expression like that, and either way it seems a lot more complicated than it ought to be. I'm hoping there is some better way to identify the return type of an async computation and/or just box the result without caring what type it actually is.
EDIT
After trying something ridiculously complicated using reflection with the AsyncBuilder type I realized I could leverage it a little more simply. Here is my current working solution, but I'm still looking out for any better options.
static let boxAsyncReturnValue v = async { return v :> obj }
static let bindFunctionReflected = typeof<FSharpAsyncObjTransformationAttribute>.GetMethod(
nameof boxAsyncReturnValue,
BindingFlags.NonPublic ||| BindingFlags.Static
)
override _.Transform(engineIntrinsics, item) =
// I need to identify the current return type of the computation, and quit if "item" is not Async<_>
if item = null then item else
let itemType = item.GetType()
if not itemType.IsGenericType then item else
let genericItemType = itemType.GetGenericTypeDefinition()
if genericItemType <> typedefof<Async<_>> then item else
let returnType = itemType.GetGenericArguments()[0]
if returnType = typeof<obj> then item else
bindFunctionReflected.MakeGenericMethod(itemType).Invoke(null, [|item|])
This is how I would do it:
let convert (a: Async<_>) =
async {
let! x = a
return box x
}
And at compile time it behaves as you'd expect:
let a = async { return "hello" }
let o: Async<obj> = convert a
let res = Async.RunSynchronously o
printfn "%s" res // Error: expected type 'string' but is type 'obj'
printfn "%s" (unbox<string> res) // compiles, prints the string
enum Seat: String{
case middle
case aisle
case window
case undefined
}
let s : Seat = Seat(rawValue: Seat.middle)
doing such gives me
error: invalid initializer call with same type 'Seat' as parameter
It seems like a simple error. I searched online but didn't find anything.
The error is quite obvious.
The rawValue parameter expects a String. But you're giving it the enum type itself.
You should either do:
let s: Seat = .middle
let s = Seat(rawValue: "middle") ?? .undefined
let s = Seat(rawValue: "middle")! // Perhaps this is better, per Alexander's comment
or just for demonstration purposes:
let s = Seat(rawValue: Seat.middle.rawValue) ?? .undefined
I created this error by changing the type of my s property from String to Seat. But since the change was made at a file different from the file where the enum was defined...I got confused.
I want to do something like this:
var x = ["10","20",30"]
var y = x.map(Int.init)
but I get this error
ambiguous reference to member 'init()'
I know that I can just write
var y = x.map { Int($0) }
But I was wondering if there is another way.
The problem is that there doesn't exist an Int initialiser that has only a String parameter.
There only exists:
init?(_ text: String, radix: Int = default)
Although the radix: parameter has a default value, this is only "filled in" for you by the compiler at the call-site of the initialiser.
Default parameter values aren't partially applied when getting a reference to the function itself, nor does the compiler generate additional overloads for all possible combinations of default parameter values. Not only would the latter add significant bloat to your code, but both options would break default parameter values that depend on being inserted at the call-site (e.g #file & #line).
To make this work properly, function values themselves would have to support default parameter values, which brings with it quite a bit of complexity. Although it could well be something that a future version of the language supports.
One simple solution to get the behaviour you want is to just write an extension on Int which adds an init(_:) initialiser that then forwards onto init(_:radix:):
extension Int {
/// Creates a new integer value from the given base-10 integer string, returning nil
/// if the string is in an invalid format.
init?(_ string: String) {
// we're saying (_:radix:) in order to disambiguate the call, the compiler
// will insert the default parameter value of radix: for us.
self.init(_:radix:)(string)
}
}
Note in Swift 4, this could be an extension of FixedWidthInteger, which provides the implementation of init(_:radix:) for the standard library's integer types.
Now you can say:
let x = ["10", "20", "30"]
let y = x.map(Int.init) // [Optional(10), Optional(20), Optional(30)]
as Int.init now refers to our extension initialiser.
And this also works just as well with flatMap(_:):
let y = x.flatMap(Int.init) // [10, 20, 30]
which will also filter out the nil elements from the transformation (the strings that weren't convertible to integers).
The issue comes down to inability to assign a function with a defaulted argument (such as Int.init(_ text: String, radix: Int = default)) to a place where it could only "fit" if the defaulted argument "didn't exist".
When you make your own closure, { Int($0) }, you're telling the compiler you want the default value of the radix to apply. This this doesn't happen implicitly, unfortunately.
Note: I'm a rookie in Swift
I'm using Former.
I'm fetching data from a realm model.
let industries = realm.objects(Industry)
Then I try to define a list of InlinePickerItem from it:
$0.pickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, value: industry.id)
})
But XCode keeps saying: Cannot convert value of type 'Int' to expected argument type '_?', pointing to industry.id.
Am I missing something? I don't know if the issue comes from Former or from something that I don't understand in Swift. For example, what kind of type is _??
UPDATE:
After #dfri comment, attempt was unsuccessful. From my small understanding of Swift, I get that Swift gets lost. So I extracted the initialisation of the list of InlinePickerItem from the closure.
let industries = realm.objects(Industry)
let inlinePickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id)
})
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>(instantiateType: .Nib(nibName: "ProfileLabelCell")) {
$0.titleLabel.text = "CATEGORY".localized
}.configure {
$0.pickerItems = inlinePickerItems
}
The error is disappeared when calling InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id) but I get something new when assigning it to $0.pickerItems which now is:
Cannot assign value of type '[InlinePickerItem<Int>]' to type '[InlinePickerItem<String>]'
Hope this will provide you with some helpful hints.
Type mismatch when assigning array to different type array
After the re-factoring of your code (after "update") its now apparent what is the source of error.
Immutable catRow is of type InlinePickerRowFormer<ProfileLabelCell, String>. From the source of [InlinePickerRowFormer] we see the that the class and its property pickerItems is declared as follows
public class InlinePickerRowFormer<T: UITableViewCell, S where T: InlinePickerFormableRow>
: ... {
// ...
public var pickerItems: [InlinePickerItem<S>] = []
// ...
}
The key here is that for an instance InlinePickerRowFormer<T,S> its property pickerItems will be an array with elements of type InlinePickerItem<S>. In your example above S is String
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>
/* |
S = String */
Hence pickerItems is an array of InlinePickerItem<String> instances.
You try, however, to append the immutable inlinePickerItems to pickerItems, which means you're trying to assign an array of InlinePickerItem<Int> instances to an array with elements of type InlinePickerItem<String>; naturally leading to a type mismatch.
You can solve this type mismatch by:
Setting your catRow immutable to be of type InlinePickerRowFormer<ProfileLabelCell, Int>.
Imagine my surprise that this Swift code generates an error in Xcode 6.1.1:
public func unlockNextLevel() -> Int
{
var highest : Int = 123
return (highest < highestUnlockedLevel)
}
More precisely, it tells me that in the return line:
'Int' is not convertible to 'String'
So, since I had some of these weird conversion errors before, I thought I'll try converting both types to Int and see what I get:
public func unlockNextLevel() -> Int
{
var highest : Int = 123
return (Int(highest) < Int(highestUnlockedLevel))
}
I then get the following error on the return line:
Could not find an overload for '<' that accepts the supplied arguments
But when I break it down to just constant values:
return (Int(3) < Int(12))
I get the int not convertible to string error again.
'Int' is not convertible to 'String'
Gnnnnn.... oh-kay, so I give it another shot without the brackets:
return highest < highestUnlockedLevel
This then gives me yet another error message:
Cannot invoke '<' with an argument list of type '(#lvalue Int, #lvalue Int)'
Okay, I get it Swift, I'm stoopid. Or maybe ... hmmm, take this, Swift:
var result = highest < highestUnlockedLevel
return result
Arrrr ... nope. Swift decides that now the return line constitutes yet another error:
'Bool' is not convertible to 'Int'
(... dials number for psych evaluation ...)
So ... could someone explain to me:
how to fix or work around this issue?
and of course: Why?? Oh, why???
Note: This is in a mixed Objc/Swift project if that makes any difference. The highestUnlockedLevel variable is declared in a Swift class with custom setter and getter.
(highest < highestUnlockedLevel) produces Bool not Int (unlike Objective-C where it returns int that can be automatically converted to BOOL), thats why you get the error. But certainly its wrong and misleading error as the problem is that Bool cannot be converted to Int.