F # Проблемы с общими типами - PullRequest
2 голосов
/ 13 марта 2011

Я пытаюсь преобразовать код C # в F # и столкнулся с небольшой проблемой.Вот код 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

Все шло хорошо, пока я не начал использовать метод Gene not взял.Вот код 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);
    }

Я вижу следующие ошибки компилятора:

"Универсальный член 'GeneNotTaken' использовался при неоднородном создании экземпляра до этой программной точки. Рассмотрите возможность переупорядочения элементов, чтобы этот элемент встречался первым. В качестве альтернативы укажите полный тип элементав явном виде, включая типы аргументов, тип возвращаемых данных и любые дополнительные общие параметры и ограничения. "

и

" Этот код менее универсален, чем требуется для его аннотаций, поскольку явныйпеременная типа 'GeneType' не может быть обобщена. Она была ограничена до 'unit'. "

Вы могли бы подумать, что первая ошибка будет кристально чистой, за исключением того, что, как вы видите, я не использовал член GeneNotTaken до этого момента, поэтому я не знаю, в чем проблема.

Вторая часть моего вопроса состоит в том, как добавить возвращаемое значение по умолчанию ('GeneType) в конце метода.

Если у вас есть другие предложения по улучшению моего кода в целом, пожалуйста, не стесняйтесь делиться ими.

1 Ответ

7 голосов
/ 13 марта 2011

Причина сообщения об ошибке заключается в том, что ваша реализация GeneTaken фактически не возвращает значение trial. Проблема в том, что у F # нет обязательного return оператора.

В F # if .. then .. рассматривается как выражение, которое оценивает и дает некоторый результат. Например, вы можете написать let a = if test then 10 else 12. Когда вы опускаете ветвь else, телом оператора должно быть какое-то императивное действие, которое возвращает unit (тип, не представляющий возвращаемого значения). Вы не можете написать let a = if test then 42 - каково будет значение результата, если test = false?

Вы можете исправить это, написав метод с использованием рекурсивного цикла - тогда у вас есть метод, который фактически возвращает 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

Альтернативная (возможно, более приятная) реализация с использованием функции 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

Чтобы дать некоторые общие подсказки, я бы, вероятно, не использовал Unchecked.defaultof<'GeneType> Вместо этого вы должны использовать тип option, когда имеете дело с ситуацией, когда значение может отсутствовать. Тип результата GeneNotTaken будет тогда option<'GeneType>. Вместо match вы можете написать:

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

Кроме того, ваш код использует много мутаций, что может быть не лучшим решением при написании функционального кода на F #. Однако, если вы только изучаете F #, то, вероятно, неплохо бы начать с переписывания некоторого кода на C # в F #. По мере того, как вы узнаете больше, вы должны искать способы избежать мутации, потому что это сделает ваш код F # более идиоматичным (и будет еще интереснее писать его!)

...