Получите MethodInfo функции F#

Я знаю Эммануэля Делогета из GameDev.net, но я не уверен, что выбрал бы его иерархию! Слишком много наследства, недостаточно гибкости.

Если бы я писал текстовую RPG (как я это делал в прошлом), она бы выглядела примерно так (хотя, к сожалению, у меня нет времени на ее составление):

  • Существа, Комнаты и Предметы, полученные из WorldEntity, с объектами WorldEntity, расположенными в составной структуре, так что предметы могут жить в других предметах, переносимых существами, которые существуют в комнатах. Реализация шаблона посетителя для WorldEntities может работать хорошо.
  • Классы CreatureType и ItemType, которые содержат данные «class» для отдельных экземпляров Creature и Item, которые ссылаются на соответствующий им объект «type». (например, базовые хитпоинты и статистика в первом, текущие хитпоинты и переходные эффекты во втором). Я мог бы реализовать их в виде прототипов списков свойств, которые копируются в экземпляры Creature или Item при их создании. Вы можете реализовать наследование свойства с помощью свойства «parent», чтобы конкретный экземпляр существа-гоблина мог относиться к CreatureType «воина-гоблина», который содержит родительскую ссылку на CreatureType «универсального гоблина». И так далее.
  • Выходы, которые принадлежат их комнате, и являются односторонними, и которые детализируют направление движения, различные условия прохождения и т. Д.
  • Области, которые содержат группы комнат, связанных некоторой логической организацией.
  • Класс Spawn для указания, где создаются экземпляры Существа и Предмета (например, в какой комнате или в каких координатах), когда они создаются и с какой периодичностью, а также из каких CreatureTypes и ItemTypes. У вас может быть какая-то логика, чтобы немного рандомизировать вещи.
  • Заклинания, умения, способности и т. Д. Все получены из базового класса действий или интерфейса, который определяет предпосылки (например, текущее положение, очки маны, некоторая степень изучения навыка и т. Д.). Нормальные команды и действия также могут быть здесь, поскольку они часто имеют какие-то требования (например, команда 'sleep' требует, чтобы вы еще не спали.)
  • Класс FutureEvent, который по сути является обратным вызовом, который вы нажимаете на приоритетную очередь для выполнения в будущем. Вы можете использовать их, чтобы планировать боевые раунды, время успокоения заклинаний, ночные / дневные циклы, что угодно.
  • Хэш / карта / словарь пар имя-> значение для статистики игрока и предмета. Не безопасно для типов, но вы по достоинству оцените гибкость позже. По моему опыту создание переменных-членов stats работоспособно, но негибко, а наличие специализированных классов атрибутов становится сложным кошмаром при отладке.
  • Тип модификатора, который содержит имя статистики и значение модификатора (например, +10, + 15%). Они добавляются к вашим существам по мере их использования (например, с помощью эффекта заклинания или с использованием зачарованного оружия) и позже удаляются с помощью синхронизированного FutureEvent или другого события, такого как выполняемая команда.
  • Специфичные для игры классы, такие как PlayerClass или PlayerRace, каждый из которых описывает класс игрока (например, воин, волшебник, вор) или расу (человек, эльф, гном) и задает начальные значения и пределы статов, списки доступности навыков, специальные полномочия и т. д.
  • Основные классы интерфейса игрока, которые будут различаться в зависимости от вашего реального типа игры. У вас могут быть классы рендеринга для графической игры, или в MUD у вас может быть класс Connection, отражающий TCP-соединение с клиентом игрока. Старайтесь не допускать в этом всей игровой логики.
  • Интерфейс сценариев. Большинство ваших команд, заклинаний и ИИ существа могут быть реализованы быстрее с приличным интерфейсом сценариев, и это также сокращает время компиляции. Он также позволяет использовать некоторые отличные возможности отладки и диагностики в игре.

Это была бы базовая структура высокого уровня, которую я бы использовал.

11
задан svick 14 September 2016 в 18:52
поделиться

4 ответа

Итак, я наконец нашел решение. Очень хакерский, но привет! Оно работает! (править: только в режиме отладки.)

let Foo (f:S -> A[] -> B[] -> C[] -> D[] -> unit) =
    let ty     = f.GetType()
    let argty  = [|typeof<S>; typeof<A[]>; typeof<B[]>; typeof<C[]>;typeof<D[]>|]
    let mi     = ty.GetMethod("Invoke", argty)
    let il     = mi.GetMethodBody().GetILAsByteArray()
    let offset = 9//mi.GetMethodBody().MaxStackSize

    let token  = System.BitConverter.ToInt32(il, offset)    
    let mb     = ty.Module.ResolveMethod(token)

    match Expr.TryGetReflectedDefinition mb with
    | Some ex -> printfn "success %A" e
    | None ->  failwith "failed"

Он работает хорошо, даже если f определен в другой сборке (.dll) или в том же месте, где происходит вызов Foo. Это еще не совсем общий подход, поскольку мне нужно определить, что такое argty, но я уверен, что смогу написать функцию, которая это сделает.

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

РЕДАКТИРОВАТЬ: Итак, вот пример использования:

open System
open Microsoft.FSharp.Quotations

[<ReflectedDefinition>]
let F (sv:int) (a:int[]) (b:int[]) (c:int[]) (d:int[]) =
    let temp = a.[2] + b.[3]
    c.[0] <- temp
    ()

let Foo (f:S -> A[] -> B[] -> C[] -> D[] -> unit) =
    let ty     = f.GetType()
    let arr    = ty.BaseType.GetGenericArguments()
    let argty  = Array.init (arr.Length-1) (fun i -> arr.[i])

    let mi     = ty.GetMethod("Invoke", argty)
    let il     = mi.GetMethodBody().GetILAsByteArray()
    let offset = 9
    let token  = System.BitConverter.ToInt32(il, offset)
    let mb     = ty.Module.ResolveMethod(token)
    mb

let main () =
  let mb = Foo F
  printfn "%s" mb.Name

  match Expr.TryGetReflectedDefinition mb with
  | None -> ()
  | Some(e) -> printfn "%A" e

do main ()

Он печатает имя F и его AST, если функция является отраженным определением.

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

Вот IL-код функции FSharpFunc. Метод Invoke в обеих сборках отладки / выпуска:

Режим DEBUG:

.method /*06000007*/ public strict virtual 
        instance class [FSharp.Core/*23000002*/]Microsoft.FSharp.Core.Unit/*01000006*/ 
        Invoke(int32 sv,
               int32[] a,
               int32[] b,
               int32[] c,
               int32[] d) cil managed
// SIG: 20 05 12 19 08 1D 08 1D 08 1D 08 1D 08
{
  // Method begins at RVA 0x21e4
  // Code size       16 (0x10)
  .maxstack  9
  IL_0000:  /* 00   |                  */ nop
  IL_0001:  /* 03   |                  */ ldarg.1
  IL_0002:  /* 04   |                  */ ldarg.2
  IL_0003:  /* 05   |                  */ ldarg.3
  IL_0004:  /* 0E   | 04               */ ldarg.s    c
  IL_0006:  /* 0E   | 05               */ ldarg.s    d
  IL_0008:  /* 28   | (06)000001       */ call       void Program/*02000002*/::F(int32,
                                                                                 int32[],
                                                                                 int32[],
                                                                                 int32[],
                                                                                 int32[]) /* 06000001 */
  IL_000d:  /* 00   |                  */ nop
  IL_000e:  /* 14   |                  */ ldnull
  IL_000f:  /* 2A   |                  */ ret
} // end of method mb@25::Invoke

Режим RELEASE:

method public strict virtual instance class [FSharp.Core]Microsoft.FSharp.Core.Unit 
        Invoke(int32 sv,
               int32[] a,
               int32[] b,
               int32[] c,
               int32[] d) cil managed
{
  // Code size       28 (0x1c)
  .maxstack  7
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldarg.2
  IL_0002:  ldc.i4.2
  IL_0003:  ldelem     [mscorlib]System.Int32
  IL_0008:  ldarg.3
  IL_0009:  ldc.i4.3
  IL_000a:  ldelem     [mscorlib]System.Int32
  IL_000f:  add
  IL_0010:  stloc.0
  IL_0011:  ldarg.s    c
  IL_0013:  ldc.i4.0
  IL_0014:  ldloc.0
  IL_0015:  stelem     [mscorlib]System.Int32
  IL_001a:  ldnull
  IL_001b:  ret
} // end of method mb@25::Invoke

Вы можете видеть, что в режиме выпуска компилятор встраивает код F в метод Invoke, поэтому информация о вызове F ( и возможность получить токен) пропала ..

5
ответ дан 3 December 2019 в 09:41
поделиться

Помогает ли приведенная ниже программа?

module Program

[<ReflectedDefinition>]
let F x =
    x + 1

let Main() =
    let x = F 4    
    let a = System.Reflection.Assembly.GetExecutingAssembly()
    let modu = a.GetType("Program")
    let methodInfo = modu.GetMethod("F")
    let reflDefnOpt = Microsoft.FSharp.Quotations.Expr.TryGetReflectedDefinition(methodInfo)
    match reflDefnOpt with
    | None -> printfn "failed"
    | Some(e) -> printfn "success %A" e

Main()    
3
ответ дан 3 December 2019 в 09:41
поделиться

Это не (легко) возможно. Следует отметить, что когда вы пишете:

let printFunctionName f =
    let mi = getMethodInfo f
    printfn "%s" mi.Name

Параметр 'f' - это просто экземпляр типа FSharpFunc <, >. Итак, все возможные варианты:

printFunctionName (fun x -> x + 1)    // Lambda expression
printFunctionName String.ToUpper      // Function value
printFunctionName (List.map id)       // Curried function
printFunctionNAme (not >> List.empty) // Function composition

В любом случае однозначного ответа на этот вопрос нет

2
ответ дан 3 December 2019 в 09:41
поделиться

Я не знаю, есть ли общий ответ для какой-либо функции, но если ваша функция простая ('a ->' b), вы можете написать

let getMethodInfo (f: 'a ->' b) = (FastFunc.ToConverter f) .Method

1
ответ дан 3 December 2019 в 09:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: