четверг, 2 июня 2011 г.

Тестирование библиотек F# в Visual Studio

Когда создаётся новый проект F# Library, то в довесок в *.fs файлу идёт Srcipt.fsx. И в этот скрипт логично бы запилить функционал теста работы библиотеки, причём хорошо бы прогонять этот тест автоматически.

Оказывается это возможно и делается довольно легко и просто.

Итак, по по пунктам:

1. ПКМ на Script.fsx -> Properties -> Copy to Output Directory выставить в Copy Always. Это необходимо для того, чтобы исходный файл скрипта автоматически копировался при сборке в целевую директорию сборки.

2. Идёт в Project -> MyProjectName Properties -> Build Events. Нас интересует Post-build event command line. Прописываем там: fsi Script.fsx. Так же можно выставить по вкусу параметр Run the post-build events.

3. Добавляем в Script.fsx что-то вроде: printfn "Hello, World!", жмём F6 и переходим вкладку Output Visual Studio(View -> Output или Ctrl-W O) и видим примерно следующее:

Build started: Project: MyMathLib, Configuration: Debug Any CPU MyMathLib -> K:\common_projects\MyMathLib\MyMathLib\bin\Debug\MyMathLib.dll
fsi test.fsx
Hello, World!
Build: 1 succeeded or up-to-date, 0 failed, 0 skipped

Для того, чтобы прилинковать либу в скрипте используйте следующую инструкцию:
#r @"MyLibraryName.dll"

среда, 27 апреля 2011 г.

Генератор функций являющихся автоматами

Переоткрыл очередной велосипед.
Меня всегда раздражало то, что для задания автомата приходилось что-то примерно такого вида(mutable можно и на ref заменить, не суть важно):

type Aut(state: 'S) =
  let mutable st = state
  
  member this.Next(x: 'A): 'B =
    ...
    st <- ...
    result

т.е. делать отдельный класс под каждый автомат или класс обёртку. Мне хотелось чего-нибудь более элегантного и удобного, а именно - представления автомата функцией. Вот, сегодня накидал следующее:

let rec create (f: 'S -> 'I -> 'O) (g: 'S -> 'I -> 'S) (state: 'S) =
    let st = ref state
 
    fun (x: 'I) ->
      let r = f !st x
      st := g !st x
      r

т.е. передаём функции создателю 3 параметра: функцию выходов, функцию переходов и начальное состояние. Если передать 2 параметра, то, за счёт карирования получим не инициальный автомат, а если 3(или нач. стостояние в карированную ф-цию) - инициальный автомат.

Ну и пример работы:

let adder = 
  create 
    (fun s x -> 
      match s with
        | 0 -> x+1
        | 1 -> x
        | _ -> s+x
      ) 
    (fun s x -> (s+1) % 10) 0

printfn "%A" [
    for i in 0..20 ->
      adder 1
  ]

результат:
[2; 1; 3; 4; 5; 6; 7; 8; 9; 10; 2; 1; 3; 4; 5; 6; 7; 8; 9; 10; 2]