Есть ли в F# языковая конструкция для доступа к лексической области видимости (например, python locals()/globals())

При написании тестов на F# я пытаюсь генерировать полезные сообщения о состоянии, которое вызвало ошибки. В python я бы включил все locals(), чтобы они были легко доступны в тестовой трассировке.

Есть ли аналогичная конструкция в F#?

Я искал в Интернете и на отличном сайте fsharpforfunandprofit, а также просмотрел список зарезервированных ключевых слов.

Вот некоторый код, который я хотел бы сделать.

[<Property>]
let some_test x =
    let example_inner_helper y =
        let z = y + x
        // Example of the meta-construction i am looking for
        let scope = {| 
            local = {| y = y; z = z |}; 
            capture = {| 
                            local = {| x = x |};
                            capture = {| (* ... *) |};
            |};
        |}
        x = z |@ sprintf "require %A=%A (%A)" x y scope
    example_inner_helper (x+x)

Что даст очень полезный результат

 Tests.some_test
   Source: Tests.fs line 105
   Duration: 51 ms

  Message: 
    FsCheck.Xunit.PropertyFailedException : 
    Falsifiable, after 1 test (1 shrink) (StdGen (387696160,296644521)):
    Label of failing property: require 1=2 ({ capture = { capture = {}
                  local = { x = 1 } }
      local = { y = 2
                z = 3 } })
    Original:
    -1
    Shrunk:
    1

Однако мне приходится явно фиксировать информацию, которую может автоматически предоставить такая конструкция, как «область действия». В результате получаются уродливые, подверженные ошибкам вещи, такие как

let ``firstAscendingLastDescendingPartE Prop`` (a: Extent<int>) b =
    let validateT ea eb =
        let checkXbeforeY ex ey headx restx =
            match restx with 
                | ValueNone -> // No rest
                    (ex = headx) |@ sprintf "no rest: ex = headx (%A = %A)" ex headx 
                    .&. ((intersectE ex ey) = ValueNone) |@ sprintf "no rest: ex ∩ ey = ∅ (%A ∩ %A)" ex ey
                | ValueSome rex -> // Some rest -> 
                    // headx and restx combines to ex
                    ((expandE headx rex) = ex) |@ sprintf "rest: headx+rex = ex (%A...%A)" headx rex 
                    .&. (exactlyTouchesE headx rex) |@ sprintf "rest: headx ends where rex starts (ex=%A ey=%A headx=%A restx=%A)" ex ey headx restx
                    .&. (exactlyTouchesE headx ey) |@ sprintf "rest: headx ends where ey starts (ex=%A ey=%A headx=%A restx=%A)" ex ey headx restx
....

(Конечно, меня не волнует фактический тип конструкции "scope").

Что касается отмены кавычек

Я уже посмотрел на unquote, который довольно симпатичный и выглядит примерно так. Но это немного ограничивает код:

  • Ошибка FS3155 Цитата не может включать присвоение или получение адреса захваченной локальной переменной.
  • Ошибка FS1230 Внутренние универсальные функции не разрешены в выражениях в кавычках. Рассмотрите возможность добавления некоторых ограничений типа, пока эта функция не перестанет быть универсальной.

У меня есть код, который выглядит примерно так:

[<Struct>]
type X<'T when 'T: comparison and 'T: equality> =
    val public first: 'T
    val public last: 'T
    new (first: 'T, last: 'T) = {
        first = 
            (if last <= first then 
                invalidOp (sprintf "first < last required, (%A) is not < (%A)" first last) 
             else first)
        last = last;
    }
    // ..

Поэтому у меня есть проблемы с тестами, использующими две приведенные ниже конструкции (вызывающие вышеуказанные ошибки).

let quote_limits () = 
    let x first last = X(first, last)
    let validate (x: X<'a>) = x.first < x.last
    let a = X(0, 1)
    <@ a.first = 0 @> |> test
    <@ validate a @> |> test 

Первый, который я могу обойти с помощью функций для доступа к частям структуры, но ограничение на дженерики — это PITA.


person Helge Jensen    schedule 12.09.2019    source источник


Ответы (1)


Я не думаю, что есть способ сделать это, даже в принципе. Код F# компилируется в байт-код .NET, основанный на стеке, поэтому локальные переменные на самом деле (в общем случае) не существуют в скомпилированном коде. Возможно, вы сможете получить некоторые глобальные переменные (они являются статическими членами) или, возможно, каким-то образом использовать отладочную информацию, но я не думаю, что есть стандартный способ сделать это.

Тем не менее, в вашем конкретном случае у F# действительно есть хороший вариант с использованием среды тестирования unquote. Это позволяет поместить тестовый код в цитаты F#, а затем показать, как эта цитата оценивается. Например:

let some_test x =
    let example_inner_helper y =
        <@ 
          let z = y + x
          x = z 
        @> |> test
    example_inner_helper (x+x)

some_test 10

Здесь я определил локальную переменную z внутри цитаты. Когда вы запустите тест, unquote напечатает отдельные этапы оценки, и вы увидите там значение z:

Test failed:

let z = y + x in x = z
let z = 20 + 10 in x = z
let z = 30 in x = z
false
person Tomas Petricek    schedule 12.09.2019
comment
Спасибо за это. На самом деле я начал с unquote, но у меня были проблемы с ограничениями на выражения, которые можно было заключать в кавычки (см. обновления к вопросу). - person Helge Jensen; 16.09.2019