2010-05-19 3 views
2

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

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 

ответ

7

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

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, что позволяет скрыть передачу состояния. Используя реализацию from this question, вы могли бы написать что-то вроде:

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

+1

Это полезно. Вы видите какой-либо способ сделать это без объявления изменяемого состояния? – Daniel

+0

Одно можно добавить: вы можете использовать 'incr count' вместо' count: =!count + 1' – Daniel

+0

Я понимаю, что должно быть изменчивое состояние _somewhere_, но мне нравится, чтобы он был скрыт в предопределенном шаблоне (например, ленивый, seq). – Daniel

2

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

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

Спасибо, но я надеялся на какую-то функциональную лаконичность. – Daniel

+0

@ Даниэль обновил его с функциональным ответом, а также – JaredPar

+0

Теперь это круто. Я все еще не прошел мимо неуловимости CPS. – Daniel

0

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

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

let alias = 
    Seq.unfold (fun count -> Some(sprintf "t%i" count, count+1)) 0 
+0

Это не будет поддерживать состояние между вызовами, если вы не реализуете нечто похожее на мой оригинальный пост. – Daniel

+0

Верно, но я бы использовал созданную последовательность как состояние. – Huusom

Смежные вопросы