简体   繁体   English

泛型类型的F#问题

[英]F# Problems With Generic Types

I'm trying to convert some C# code to F# and have encountered a slight problem. 我正在尝试将一些C#代码转换为F#,并且遇到了一个小问题。 Here is the F# code that I have already: 这是我已经拥有的F#代码:

open System
open System.Collections
open System.Collections.Generic

type Chromosome<'GeneType>() =
    let mutable cost = 0
    let mutable (genes : 'GeneType[]) = Array.zeroCreate<'GeneType> 0
    let mutable (geneticAlgorithm : GeneticAlgorithm<'GeneType>) = new GeneticAlgorithm<'GeneType>()

    /// The genetic algorithm that this chromosome belongs to.
    member this.GA
        with get() = geneticAlgorithm
        and set(value) = geneticAlgorithm <- value

    /// The genes for this chromosome.
    member this.Genes
        with get() = genes
        and set(value) = genes <- value

    /// The cost for this chromosome.
    member this.Cost
        with get() = cost
        and set(value) = cost <- value

    /// Get the size of the gene array.
    member this.Size = genes.Length

    /// Get the specified gene.
    member this.GetGene(gene:int) =
        genes.[gene]

    member this.GeneNotTaken(source:Chromosome<'GeneType>, taken:IList<'GeneType>) =
        let geneLength = source.Size
        for i in 0 .. geneLength do
            let trial = source.GetGene(i)
            if(not (taken.Contains(trial))) then
                taken.Add(trial)
                trial

Everything was going fine until I started on the Gene not taken method. 一切都进行得很好,直到我开始使用不采用基因的方法。 Here is the C# code for that method (I also need help with returning the default type as well, but just didn't make it that far yet): 这是该方法的C#代码(我也需要帮助以返回默认类型,但还没有达到如此程度):

private GENE_TYPE GetNotTaken(Chromosome<GENE_TYPE> source,
            IList<GENE_TYPE> taken)
    {
        int geneLength = source.Size;

        for (int i = 0; i < geneLength; i++)
        {
            GENE_TYPE trial = source.GetGene(i);
            if (!taken.Contains(trial))
            {
                taken.Add(trial);
                return trial;
            }
        }

        return default(GENE_TYPE);
    }

Compiler errors I'm seeing include: 我看到的编译器错误包括:

"The generic member 'GeneNotTaken' has been used at a non-uniform instantiation prior to this program point. Consider reordering the members so this member occurs first. Alternatively, specify the full type of the member explicitly, including argument types, return type and any additional generic parameters and constraints." “在此程序之前,通用成员'GeneNotTaken'已在非统一实例中使用。请考虑对成员重新排序,以便该成员首先出现。或者,显式指定成员的完整类型,包括参数类型,返回类型和任何其他通用参数和约束。”

and

"This code is less generic than required by its annotations because the explicit type variable 'GeneType' could not be generalized. It was constrained to be 'unit'." “由于无法将显式类型变量'GeneType'通用化,因此该代码的通用性低于其注释的通用性。它被约束为'unit'。”

You would think the first error would be crystal clear, except as you can see I didn't use the GeneNotTaken member prior to that point, which is why I don't know what the problem is. 您会认为第一个错误将非常清楚,除非您可以看到在那之前我没有使用GeneNotTaken成员,这就是为什么我不知道问题出在哪里的原因。

The second part of my question is how to add the return default('GeneType) at the end of the method. 我的问题的第二部分是如何在方法末尾添加return default('GeneType)。

If you have some other suggestions for improvement of my code in general, please feel free to share them. 如果您总体上对我的代码有其他建议,请随时分享。

The reason for the error message is that your implementation of GeneTaken is not actually returning the trial value. 出现错误信息的原因是您的GeneTaken实现实际上未返回trial值。 The problem is that F# doesn't have imperative return statement. 问题在于F#没有命令式return语句。

In F#, if .. then .. is treated as an expression that evaluates and gives some result. 在F#中, if .. then ..视为评估并给出某些结果的表达式。 For example, you can write let a = if test then 10 else 12 . 例如,您可以编写let a = if test then 10 else 12 When you omit the else branch, the body of the statement must be some imperative action that returns unit (a type representing no return value). 省略else分支时,语句的主体必须是一些命令unit ,该unit必须返回unit (一种表示无返回值的类型)。 You cannot write let a = if test then 42 - what would be the value of the result if test = false ? 您不能写出let a = if test then 42如果test = false ,结果的值是什么?

You can fix it by writing the method using a recursive looping - then you have a method that actually returns trial and so the F# type checker isn't confused: 您可以通过使用递归循环编写方法来解决此问题-然后您有了一个实际上返回trial的方法,因此F#类型检查器不会引起混淆:

member this.GeneNotTaken
    (source:Chromosome<'GeneType>, taken:IList<'GeneType>) : 'GeneType =
  let geneLength = source.Size
  let rec loop i =
    if i >= geneLength then Unchecked.defaultof<'GeneType> // Return default
    let trial = source.GetGene(i)
    if (not (taken.Contains(trial))) then
      // Gene was found, process it & return it
      taken.Add(trial)
      trial
    else 
      // Continue looping
      loop (i + 1)
  loop 0

An alternative (maybe nicer) implementation using Seq.tryPick function: 使用Seq.tryPick函数的另一种(也许更好)的实现:

member this.GeneNotTaken
    (source:Chromosome<'GeneType>, taken:IList<'GeneType>) : 'GeneType =
  let geneLength = source.Size
  // Find gene that matches the given condition
  // returns None if none exists or Some(trial) if it was found
  let trial = [ 0 .. geneLength - 1 ] |> Seq.tryPick (fun i ->
    let trial = source.GetGene(i)
    if (not (taken.Contains(trial))) then Some(trial) else None) 
  match trial with 
  | Some(trial) ->
      // Something was found
      taken.Add(trial)
      trial
  | _ -> 
      Unchecked.defaultof<'GeneType> // Return default

To give some general hints, I probably wouldn't use Unchecked.defaultof<'GeneType> Instead, you should use option type when you're dealing with situation where a value may be missing. 为了提供一些一般性提示,我可能不会使用Unchecked.defaultof<'GeneType>而在处理可能缺少值的情况时,应该使用option类型。 The result type of GeneNotTaken would then be option<'GeneType> . 然后, GeneNotTaken的结果类型将为option<'GeneType> Instead of match you could write: 除了match您可以这样写:

  trial |> Option.map (fun actualTrial ->
      taken.Add(actualTrial)
      actualTrial )

Also, your code uses a lot of mutation, which may not be the best thing to do when writing functional code in F#. 另外,您的代码使用了大量的变异,这在用F#编写功能代码时可能不是最好的选择。 However, if you're just learning F# then it's probably good to start by rewriting some C# code into F#. 但是,如果您只是在学习F#,那么最好将一些C#代码重写为F#开始。 As you learn more, you should look for ways to avoid mutation, because it will make your F# code more idiomatic (and it will be more fun to write it too!) 随着学习的深入,您应该寻找避免突变的方法,因为这会使您的F#代码更加惯用(并且编写它也会更加有趣!)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM