简体   繁体   中英

F# Problems With Generic Types

I'm trying to convert some C# code to F# and have encountered a slight problem. Here is the F# code that I have already:

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):

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."

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'."

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.

The second part of my question is how to add the return default('GeneType) at the end of the method.

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. The problem is that F# doesn't have imperative return statement.

In F#, if .. then .. is treated as an expression that evaluates and gives some result. For example, you can write 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). You cannot write let a = if test then 42 - what would be the value of the result if 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:

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:

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. The result type of GeneNotTaken would then be option<'GeneType> . Instead of match you could write:

  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#. However, if you're just learning F# then it's probably good to start by rewriting some C# code into 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!)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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