Giter Club home page Giter Club logo

fsharp.compiler.portacode's Introduction

FSharp.Compiler.PortaCode

An F# code format and corresponding interpreter.

  • Currently distributed by source inclusion or 'fslive' tool, no nuget package yet

  • dotnet fslive is a live programming "watch my project" command line tool, e.g.

     dotnet fslive foo.fsx
     dotnet fslive MyProject.fsproj
    
  • Used by Fabulous, DiffSharp and others.

The overall aim of the interpreter is to execute F# code in "unusual" ways, e.g.

  • Live checking - Only executing selective slices of code (e.g. LiveCheck checks, see below)

  • Observed execution - Watch execution by collecting information about the values flowing through different variables, for use in hover tips.

  • Symbolic execution - This is done in cooperation with the target libraries which must allow injection of symbols into the computational structure, e.g. the injection of symbolic shape variables into the shapes of tensors, and the collection and processing of associated constraints on those variables.

  • Execution without Reflection.Emit - Some platforms don't support Reflection.Emit. However be aware that execution on such platforms with this intepreter is approximate with many F# language features not supported correctly.

The interpreter is used for the "LiveUpdate" feature of Fabulous, to interpret the Elmish model/view/update application code on-device.

The interpreter may also be useful for other live checking tools, because you get escape the whole complication of actual IL generation, Reflection emit and reflection invoke, and no actual classes etc are generated.

Code format

The input code format for the interpreter (PortaCode) is derived from FSharp.Compiler.Service expressions, the code is in this repo.

Interpretation

The semantics of interpretation can differ from the semantics of .NET F# code. Perf is not good but in many live check scenarios you're sitting on a base set of DLLs which are regular .NET code and are efficiently invoked.

Library calls are implemented by reflection invoke. It's the same interpreter we use on-device for Fabulous.

Command line arguments

Usage: <tool> arg .. arg [-- <other-args>]
       <tool> @args.rsp  [-- <other-args>]
       <tool> ... Project.fsproj ... [-- <other-args>]

The default source is a single project file in the current directory.
The default output is a JSON dump of the PortaCode.

Arguments:
   --once            Don't enter watch mode (default: watch the source files of the project for changes)
   --send:<url>      Send the JSON-encoded contents of the PortaCode to the webhook
   --send            Equivalent to --send:http://localhost:9867/update
   --projarg:arg     An MSBuild argument e.g. /p:Configuration=Release
   --dump            Dump the contents to console after each update
   --livecheck       Only evaluate those with a LiveCheck attribute. This uses on-demand execution semantics for top-level declarations
                     Also write an info file based on results of evaluation, and watch for .fsharp/foo.fsx.edit files and use the 
                     contents of those in preference to the source file
   <other-args>      All other args are assumed to be extra F# command line arguments, e.g. --define:FOO

LiveChecks

  • A LiveCheck is a declaration like this: https://github.com/fsprojects/TensorFlow.FSharp/blob/master/examples/NeuralStyleTransfer-dsl.fsx#L109 โ€ฆ

  • The attribute indicates the intent that that specific piece of code (and anything it depends on) should be run at development time.

  • An example tool is the "fslive.exe" tool from this repo here https://github.com/fsprojects/FSharp.Compiler.PortaCode/blob/master/src/ProcessCommandLine.fs#L46. Like FsAutoComplete this watches for project changes and then recompiles using FCS and looks for LiveCheck attributes. It then interprets those evaluations using reflection and collects information about the execution. For example, it detects errors and detects when variables have been bound to particular values during interpretation. The tool currently emits a ".fsharp/file.fsx.info" file containing extra information about the file "file.fsx" - extra error messages and extra tooltips. An experimenta FCS modification notices the existence of this file and incorporates the added information into Intellisense results. This keeps the checker tool totally decoupled from the IDE tooling.

  • This functionality may one day be reconfigured to be an F# Analyzer.

fsharp.compiler.portacode's People

Contributors

7sharp9 avatar dsyme avatar timlariviere avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fsharp.compiler.portacode's Issues

Recompile with configured compiler symbols (DEBUG, NETSTANDARD, ...)

Would it be possible to use the same flags, when recompiling, as those defined in the fsproj?
Or at least be able to define custom symbols to use when launching the watcher.

For instance, this code will evaluate differently between MSBuild and the Interpreter when the symbol WITH_TRACE is defined in the fsproj.

let updateData () =
    (...)
#if WITH_TRACE
    do sendTraceToExternalService()
#endif

I know it can be difficult because it depends on the configuration (Debug, Release) and platform (Android, iOS) used when running the project with the IDE.

But without it, it makes using the interpreter really complicated on bigger than small projects.

A better example that made me realize this:

I'm trying to use Fabulous.LiveUpdate with ElmishContacts (a Fabulous app)
The interpreter doesn't like SQLite and prints a compilation error (no member found).
Ok, why not, maybe it's asking too much.

So I made a "design time" mock for data access, switching the 2 relies on a custom symbol (ENABLE_LIVEUPDATE)
But this doesn't work as the interpreter will remove the #if ENABLE_LIVEUPDATE block.
A workaround can be using the negative #if !ENABLE_LIVEUPDATE but for more advanced scenario where an actual symbol is expected, it won't be possible.

Interpreted classes can't inherit from abstract classes

If an interpreted type inherits from a compiled type you can't create an instance of that type

'Cannot create an instance of DiffSharp.Model.Model because it is an abstract class.'

This is because the interpreter doesn't have a "real" class for the interpreted type and instead tries to use delegation to the inherited object.

Only current workaround is to modify code to inherit from a non-abstract type

Cannot build FsLive.Cli

..\FSharp.Compiler.PortaCode\FsLive.Cli>dotnet build
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restoring packages for ..\FSharp.Compiler.PortaCode\FsLive.Cli\FsLive.Cli.fsproj...
  Restore completed in 27,21 ms for ..\FSharp.Compiler.PortaCode\src\FSharp.Compiler.PortaCode.fsproj.
  Generating MSBuild file ..\FSharp.Compiler.PortaCode\FsLive.Cli\obj\FsLive.Cli.fsproj.nuget.g.props.
  Restore completed in 296,85 ms for ..\FSharp.Compiler.PortaCode\FsLive.Cli\FsLive.Cli.fsproj.
  FSharp.Compiler.PortaCode -> ..\FSharp.Compiler.PortaCode\src\bin\Debug\netstandard2.0\FSharp.Compiler.PortaCode.dll
C:\Program Files\dotnet\sdk\2.2.100\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.PackTool.targets(74,5): error NETSDK1054: only supports .NET Core. [..\FSharp.Compiler.PortaCode\FsLive.Cli\FsLive.Cli.fsproj]
  FsLive.Cli -> ..\FSharp.Compiler.PortaCode\FsLive.Cli\bin\Debug\netcoreapp2.1\FsLive.Cli.dll

Build FAILED.

C:\Program Files\dotnet\sdk\2.2.100\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.PackTool.targets(74,5): error NETSDK1054: only supports .NET Core. [..\FSharp.Compiler.PortaCode\FsLive.Cli\FsLive.Cli.fsproj]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:00:03.60

Calling Resolved/Erased function fails at EvalUnionCaseTest

Hi,

Following the guide of Fabulous Live Update and Giraffe.HotReload, I'm trying to implement HotReload for Bolero Elmish. I've been able to successfully resolve the "update" function from the MVU pipeline, but when I call the erased function, the following exception occurs:

Screen Shot 2019-04-07 at 12 13 11 PM

What's interesting is that it's resolving the actual union value, rather than the structure describing it.

The code I'm attempting to hot reload is as follows:

type Model =
    {
        value: int
    }

let initModel =
    {
        value = 0
    }


type Message =
    | Increment
    | Decrement

let update (message : Message) (model : Model) =
    match message with
    | Increment ->
        { model with value = model.value + 1 }, Cmd.none
    | Decrement ->
        { model with value = model.value - 1 }, Cmd.none

let view model dispatch =
    concat [
        button [on.click (fun _ -> dispatch Decrement)] [text "-"]
        span [] [textf " %i " model.value]
        button [on.click (fun _ -> dispatch Increment)] [text "+"]
    ]


type Updater(update) =
    member __.UniqueUpdate message model = update message model

let myHotReload = Updater(update)

And the code that is resolving the myHotReload value:

    let handleUpdate (updater : ProgramUpdater<'msg, 'model>) (files : (string * DFile)[]) =
        printfn "Created interpreter! %A" interpreter

        lock interpreter (fun () ->
            files |> Array.iter (fun (fileName, file) -> interpreter.AddDecls file.Code)
            files |> Array.iter (fun (fileName, file) -> interpreter.EvalDecls (envEmpty, file.Code))
          )

        let mem = tryFindMemberInFiles "myHotReload" files
        match mem with
        | Some (def, expr) ->
            try
                printfn "Found member!"
                let entity = interpreter.ResolveEntity(def.EnclosingEntity)
                printfn "Got entity! %A" entity

                let (def, memberValue) = interpreter.GetExprDeclResult(entity, def.Name)
                printfn "Got member value! %A" memberValue

                let value = getVal memberValue
                printfn "Got Value!: %A" value

                match value with
                | :? ObjectValue as x ->
                    let (ObjectValue v) = x
                    let updater = Map.find "update" v.Value

                    printfn "Found update %A" updater
                    let erasedUpdater = updater :?> obj -> obj -> obj * Cmd<obj>

                    printfn "Successfully cast: %A" erasedUpdater
                    let model = initModel
                    let message = Message.Increment

                    printfn "Calling update"
                    let newSet = erasedUpdater message model
                    printfn "Got new value %A" newSet
                | :? Updater as updater ->
                    printfn "Found Updater!"
                    let model = initModel
                    let message = Message.Increment

                    printfn "calling Updater!"
                    let newSet = updater.UniqueUpdate message model
                    printfn "Got new values! %A" newSet

            with e ->
                printfn "Got exception: %A" e

        | None ->
            printfn "could not find member"

More interesting details, the value here is resolving as an ObjectValue rather than as the Updater type as I expected. I've tried resolving the update function various ways as well, packaged in a type, as a method itself, however I always receive the previously mentioned exception.

I'm not sure if there is an issue in the way I'm resolving the types, or a bug in the interpreter, any advice or help would be greatly appreciated. Thanks :)

EDIT:

Update, the reason the object was resolving as ObjectValue is because of how I set up my projects, I've fixed that now, but the issue still remains.

Model with types in an external lib seem to be null in the environment

I have a model like this:

    type Model =
        {   Player : Sprite option
            Elapsed : TimeSpan
            Game: Game option
            SpriteBatch: SpriteBatch option 
            }

I seem to get to a point in interpreting where the model in null in the env, this is where the view contains access to dotted external types (model.Game) :

    let view (model: Model) (dispatch: Msg -> unit) =
        printfn "updating view 5"
        match model.Game with
        | Some game -> game.GraphicsDevice.Clear Color.Blue
        | _ -> ()

Screenshot 2019-05-24 at 17 45 55

Any pointers?

Update PortaCode to support 4.7 Language Features + Fabulous.LiveUpdate fails

Currently it appears Fabulous.LiveUpdate done not support 4.7 Language Features such as implicit yields from Lists.

When using Fabulous.LiveUpdate, when using implicit yields from (for example) View.StackLayout(, children = []), the LiveUpdate PortaCode returns errors like:

C:\Users....\App\Screen.fs (734,37)-(734,99) typecheck warning This expression returns a value of type 'ViewElement' but is implicitly discarded. Consider using 'let' to bind the result to a name, e.g. 'let result = expression'. If you intended to use the expression as a value in the sequence then use an explicit 'yield'.

Compiling, running, debugging the App works fine but in the emulator and on real hardware.

Expected Result: PortaCode should use the same compiler version as being used by the project to compile.

Question about the proper way to resolve functions

Hi, I was working on incorporating this into a Giraffe extension to allow for hot-reload of giraffe views and I was able to get an MVP going very quickly thanks to the examples over on the Fabulous repo. As it is, I've been very successful finding a value of a given name and testing if it meets certain types and coercing that into a shape I need to make a Giraffe HttpHandler. What I'm having trouble with is a more general mechanism to resolve an HttpHandler.

What I'd like to be able to resolve, at minimum, are:

  • value named XXXX of type HttpHandler
  • value/member named XXXX of some F# function type whose final return type is HttpHandler. For these I would like to be able to determine the types of the parameters so I can retrieve them via the Asp.Net Core dependency injection mechanisms and then apply them to the actual function.

For this second case I've had a hard time going from FSharp.Compiler.PortaCode.Interpreter+MethodLambdaValue to some kind of one-level-higher form of the member. Do you have any pointers or resources I can look at to help?

Unexpected member RPrim_float32 at types RTypes [||] RTypes [|System.Double|]'

Exception has occurred: CLR/System.Exception
Exception thrown: 'System.Exception' in FSharp.Core.dll: 'unexpected member RPrim_float32 at types RTypes [||] RTypes [|System.Double|]'
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1647.Invoke(String message)
   at [email protected](ResolvedTypes arg30) in /Users/davethomas/github/MonoGameTest/paket-files/fsprojects/FSharp.Compiler.PortaCode/src/Interpreter.fs:line 988

extension of generic type, needs FCS support: IObservable`1::member Add

Mirroring the issue I logged in FCSWatch for observable support.

humhei/FCSWatch#38

The reason I needed this was a subscription after initialisation is needed to restart the event loop after a hot reload.

Sort of like this:

    let subUpdateLoop (game: ElmishGame) dispatch =
        let msgSender msg = msg |> Tick |> dispatch
        game.UpdateEvent.Add(msgSender)

    let init game = 
        initModel game, Cmd.ofSub (subUpdateLoop game)

Fabulous LiveUpdate + erased type providers (FSharp.Data.JsonProvider)

Right now our team is placing most of the time into developing the UI for our Fabulous app, and the build/deploy time is around 2 minutes, so we are trying to use LiveUpdate. At the same time we are using FSharp.Data JsonProvider across the application, for instance ABCTypeProvider.Root often times ends up in the Model.

The compilation on the build machine goes fine, but when porta code is run by the interpreter on the application host (android emulator) this is the error that happens
ERROR SENDING TO WEBHOOK: "System.Exception: error converting rhs of init System.InvalidOperationException: the type 'Root' does not have a qualified name

I assume the problem is that type gets erased and since it is referenced in the Model it does not work. But it really sucks and I am trying to get it to work

Tried:

  • Moving the TypeProvider definitions to a separate project (did not work, again, it is referenced in the Model and the accessors are used within the update/view functions.

Contemplating:

  1. Try to use --livechecksonly
  2. Somehow prevent the type to be erased
  • Is this even possible? From my limited type provider understanding the designer of the provider makes the decision weather to erase the type or not
  • Maybe there are some interresting [<type: ABCAttrribute>] that will prevent erasure
  • Supply options to the type provider 'constructor'
  1. Choose a different type provider implementation that does not erase the type
  2. Relax the type constraints to obj and have helpers to access the fields of the type (all in the separate util project)

New idea:

If the code is first compiled by FSharp.Compiler.Service maybe figure out why it does not erase the types (assumption) as well as regular VS build process does

I think I will do more research on number 1 and if I fail proceed with 3.

Any suggestions on a good way to troubleshoot this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.