Переписываем простой C # вложенный класс - PullRequest
2 голосов
/ 20 мая 2010

Каким будет элегантный способ реализовать функциональность этого вложенного класса в F #?

  private class Aliaser {
     private int _count;
     internal Aliaser() { }
     internal string GetNextAlias() {
        return "t" + (_count++).ToString();
     }
  }

Это была моя первая попытка, но похоже, что для этого должна быть сексуальная однострочная:

let aliases = (Seq.initInfinite (sprintf "t%d")).GetEnumerator()

let getNextAlias() = 
    aliases.MoveNext() |> ignore
    aliases.Current

Ответы [ 3 ]

7 голосов
/ 20 мая 2010

Обычный способ записи - создать функцию с локальным состоянием, захваченным в замыкании:

let getNextAlias = 
  let count = ref 0
  (fun () -> 
     count := !count + 1; 
     sprintf "t%d" (!count))

Тип getNextAlias просто unit -> string, и когда вы вызываете его несколько раз, он возвращает строки "t1", "t2", ... Это зависит от изменяемого состояния, но изменяемое состояние скрыто от пользователя .

Относительно того, можете ли вы сделать это без изменяемого состояния - простой ответ - НЕТ, потому что когда вы дважды вызываете чисто функциональную функцию с одним и тем же параметром, она должна возвращать один и тот же результат. Таким образом, вам нужно написать что-то со следующей структурой:

let alias, state1 = getNextAlias state0
printf "first alias %s" alias
let alias, state2 = getNextAlias state1
printf "second alias %s" alias
// ...

Как видите, вам нужно сохранить некоторое состояние и поддерживать его во всем коде. В F # стандартным способом решения этой проблемы является использование изменяемого состояния. В Haskell вы можете использовать State monad , которая позволяет скрыть прохождение состояния. Используя реализацию из этого вопроса , вы могли бы написать что-то вроде:

let getNextAlias = state { 
  let! n = getState
  do! setState (n + 1)
  return sprintf "t%d" n }

let program =
  state { 
    let! alias1 = getNextAlias()
    let! alias2 = getNextAlias() 
    // ...
  }

execute progam 0 // execute with initial state

Это очень похоже на другие вычисления, такие как lazy или seq, на самом деле - вычисления в блоке state { .. } имеют некоторое состояние, и вы можете выполнить их, указав начальное значение состояния. Однако, если у вас нет веских причин требовать чисто функционального решения, я бы предпочел первую версию для практического программирования на F #.

2 голосов
/ 20 мая 2010

Вот быстрый и грязный перевод

type Aliaser () =
  let mutable _count = 0
  member x.GetNextAlias() = 
    let value = _count.ToString()
    _count <- _count + 1
    "t" + value

Более функциональный подход без состояния - использовать продолжения.

let createAliaser callWithValue = 
    let rec inner count = 
        let value = "t" + (count.ToString())
        callWithValue value (fun () -> inner (count + 1))
    inner 1

Это объявление, которое вызовет функцию callWithValue со значением и функцией, которую необходимо выполнить, чтобы повторить со следующим значением.

А вот пример использования

let main () =
    let inner value (next : unit -> unit )=  
        printfn "Value: %s" value
        let input = System.Console.ReadLine()
        if input <> "quit" then next()
    createAliaser inner     

main()
0 голосов
/ 20 мая 2010

Я бы использовал Seq.unfold : (('a -> ('b * 'a) option) -> 'a -> seq<'b>) для генерации псевдонимов.

Реализовано как:

let alias = 
    Seq.unfold (fun count -> Some(sprintf "t%i" count, count+1)) 0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...