Monday, April 18, 2022

Throwing stones from a potentially glass house - proper citations and clarity in code snippets and articles

implicit usings in C# is a terrible idea. It is hard enough to get people writing articles or posting sample code to include the damn using clauses, let alone references or assembly version numbers. lets make it so people are used to looking at code without any and requiring every onlooker to go find the source repo to really be able to replicate the code. VB had it for more than 10 years and there were announcements of feature parity around 10 years ago, why does C# suddenly think it is a good idea now?

I'm repeatedly running into code examples with many major problems for reproduction:
  • the usings/opens are excluded from snippets (and often from the entire article/post/page)
  • the packages needed aren't mentioned
  • the repo doesn't exist or isn't linked
  • the references don't specify a version
  • the article/snippet has no date/time visibility
  • implicit usings are turned on (making even a full repo far less clear unless you download it)
  • there is some implicit context that makes it unclear the true target

    • turning in implicit usings is a special kind of evil for example/sample repos.
    • is this how you write a framework service or a net core service?
    • is this how you use something magical built-in to hangfire to install your hangire app as a service, or is there a dependency some hangfire package depends on that is doing it?

I'm having a really tough time finding an updated article on setting up an app that goes 1 of 4 ways:

  • Run as a console spitting out simple information on the command line arguments
  • Run as a console executing a specific potentially long running job
  • Run as a console app starting up a web server with a dashboard using Hangfire Asp.Net Core
  • Run as a console app to install some part of itself as a windows service (including the Hangfire Asp.Net Core dashboard).
I understand that asking for a simple article or post, up to date, that shows how to do 4 things at once is a lot to ask for. However, the above major problems for reproducing little parts individually combine to form serious frustration.

Friday, July 9, 2021

TypeScript: typeof Foo.Bar

In typescript if you have 2 complex objects and you want to use an instance of one as the other, it might just work. In the case of generic type arguments at least, prepare for potentially really difficult to read tooltip parsing. Or say you are importing a json file, the type it is importing as may be much more generic than you desired. This is especially true if you have any properties that are only allowed to be specific strings instead of any string. Given the limited context here from the typescript package
@types/topojson-specification
type Positions = number[];
type Arc = Positions[];
interface Objects<P> { [ key: string]: GeometryObject<P>}
interface Topology<T extends Objects<GeoJSON.GeoJsonProperties> = Objects<GeoJsonProperties>>{
	type:"Topology",
    arcs:Arc[]
}
import * as globeData from './globedata.json';
We can see the types are quite a mental mess to think about. Using Type alias proxies can work wonders.
type TopKey = keyof Topology
type Top = {
	[K in TopKey]: Topology[K]
}
Now with these defined (that we can remove later once we figure out what all is going on) we can proceed with property checks.
// type is a special case, the types match, but they must be an allowed string, so we do this workaround
var gdt:Top['type'] | undefined = globeData.type == "Topology"? globeData.type : undefined;
var gda:Top['arcs'] = globeData.arcs;
We see here that we can declare a type in the context of another type's property. This greatly slims the surface of the tooltip explaining if something is wrong on that property and what it is. Repeat the breakdown process as necessary.

Thursday, October 11, 2018

Making the Dynamic Operator More ... Dynamic

So in F# you can (and have to if you want it) make a dynamic operator implementation.

Like so:
    let (?) (this : 'Source) (prop : string) : 'Result =
        let t : Type = this.GetType()
        let p : PropertyInfo = t.GetProperty(prop)
        if isNull p then failwithf "could not find prop %s" prop
        p.GetValue(this, null) :?> 'Result
Or dynamic setters even:
    let (?<-) (this: 'Source) (prop:string) (value: 'Value) =
        this.GetType().GetProperty(prop).SetValue(this,value,null)
Which are accessed by name not by string (even though the effect is like a magic string):
    type Test(code:string) = 
        member val Code = code with get,set
    let nakedTest : Test = Test("hello")
    let x : obj = box nakedTest
    let value : string = x?Code
    printfn "I found the property and retrieved the value, it was %s" value

Or the setter like: x?Code <- "dynamic"

Now what happens if I want part of that implementation to be variable, I want to pass different combinations of BindingFlag bits, and the keep the rest of the implementation the same?

Perhaps I want to access static or private properties, with different (?) implementations.

Well, it's possible!


    let makePropAccess (flags:BindingFlags) =
        let (?) (this : 'Source) (prop : string) : 'Result =
            let t = this.GetType()
            if isNull t then failwithf "bad getType"
            let p = t.GetProperty(prop,flags)
            if isNull p then failwithf"could not find prop %s" prop
            p.GetValue(this, null) :?> 'Result
        (?)
        
    let (?) = makePropAccess (BindingFlags.Public ||| BindingFlags.Instance) 
    let x = Test("Code")    
    x?Code
full LINQPad code at Github.com/ImaginaryDevelopment/LinqPad/LINQPad Queries/Reflection/F# dynamic operator implementation.linq

I wonder how (or if) you could even access this from C#

Tuesday, May 9, 2017

Where has all the browser javascripting gone?

I guess it's time to admit I'm behind the times. I have little experience with many of the things I'm hearing the web-community talk about like it's something everyone is already using. Webpack, AMD, UMD, Flow, JsDoc, EsLint, Typings. I have some experience with node, but 99% of the code I write/have written is for the browser and blissfully ignorant of server-side JavaScript.

Individual parts of this I imagine I'd have no problem learning. It seems each one of them takes for granted that you already know or are willing to go learn the other system, and then not discuss how you would (as a beginner or intermediate to both systems) integrate them. Also, forget how you would use them on a path that doesn't perfectly match whatever path they choose for you to learn on, especially for any advanced usage.

Motivation

VsCode has a combination platter of lovely features/extensions to enable things like intellisense, auto-completion, and design-time error detection. However, what it seems I'm running into is they rely on using (non-AMD) module systems. I'm having code types/method thrashing in a live personal project (also a similar issue in a non-live non-personal project).

Solutions (from my Understanding/Attempts)

  • JsDoc
    • where it seems I have to redefine the same @typedef MyClass in each and every file rather than somehow referencing the source of an object's shape.
    • There doesn't appear to be anyway to get the annotations out of every file to centralize (which is of dubious benefit in many cases, unless it causes you to have to copy-paste your shape comments around to multiple files.
    • Some of it (no idea how much) isn't supported by VsCode
    • This is the documentation for a callback 
      • notice there is no example that doesn't involve using prototypes or resorting to classes?
      • I can find nothing that talks about a way to reference code in other files
  • Flow
    • all the documentation appears to say it always needs to be babel-ized to be in browser
    • running it, it complains like crazy about react 
      • I added it as a package, most likely to try to get flow or one of these other systems working
      • should the node react module be in the libs section of .flowconfig ?
      • when not in the libs section, and .*/node_modules/.* is in the ignore, it doesn't seem to care, still reports errors in node_modules/React/lib/ReactChildren.js
    • Whereas, I can use my jsx directly in browser via babel, I don't think I can do this with adding in flow-typing.
      • which means I don't have to figure out or take on learning Watcherify or something else to auto-magically detect changes and recompile on the fly.
    • aims at being an amazingly safe type system (vs TypeScript looking to strike a balance between safety and productivity) - see here
  • TypeScript 
    • I have no idea where to even start into this thing. It seems like a beast of changes to tackle to migrate anything into try it.
      • The examples demonstrate how to use either node or Visual Studio. I'm using (and loving) VsCode for all my JavaScript and some of my F#.
    • I suspect, were I to download the typings for react, my project (no TypeScript in it) would suddenly be able to design-time detect all kinds of problems.
    • I have no idea if I could write my own typings files for my non-JavaScript somewhere that would help anything. Furthermore, when I go look at something called DefinitelyTyped it looks like you have to do a lot of installations, and then elsewhere on the web, it's semi-deprecated since there is (or will be?) auto-discovery
  • React PropTypes
    • Everywhere it says move to an NPM package. no indications what to do if you don't develop your JavaScript in node. 
    • No sign, upon searching, of a CDN or other browser-capable file to help find method/object shape/type errors (which wouldn't happen until run time anyhow)
  • Fable - oh my beloved F# - produces so-so JavaScript, always bundled up in webpack that also doesn't seem to appreciate not having everything bundled into their own module system.
  • VsCode - oh so lovingly lightweight
    • doesn't support an unknown percentage of the JsDoc syntax
    • allows you to add ///<reference path="./x.extension"> but apparently only supports typescript files

This is at least partially a rant and venting

I have very little idea of what I'm talking about, having been stuck in a Wpf project for almost 2 years now. This is my way of categorizing and venting while trying to pick the flag back up. Also, a recounting of my experiences having been a cutting edge resource on near every coding-topic on every team I've ever been to now, feeling entirely lost trying to get a few features that seem almost entirely built-in to my IDE of choice for JavaScript. These are features I never even had a taste of before, and now it seems like I'm in instant withdrawals as soon as what I consider to be a near-critical feature is missing. (the features don't work if you aren't developing in node, aren't using webpack, are using AMD, etc..)

The topic

Very little of what I find that people recommend for usage explicitly appears to support JavaScript in browser. Node seems to have come up and choked out search results or the community or both from talking about in-browser or code that doesn't take a dependency in the entire codebase on a module system.

Thursday, January 5, 2017

Run build tasks and targets via F# in VsCode

For developing the client side of a web app, I've switched from the ultra heavy Visual Studio 2015 to Visual Studio Code. It's written in JavaScript and runs pretty smoothly. It has hooks for kicking off a build, and tests.

I hooked up the build kick off to FAKE (an F# build engine) tasks (Via an .ionide file at the project root).

so I just hit Ctrl+Shift+P -> Fake -> and pick the build.fsx target I want to run from the menu (via keyboard, mouse if desired)

Things I have hooked up to be kicked off from Fake


  • Setup/install node (Via installing Chocolatey first)
    • install npm packages
  • Transpile Coffeescripts
  • Transpile Babel (React preset)
  • Transpile Sass
  • List packages installed to this project
  • Kill any running instances of the app
  • Clean the app
  • MsBuild the app
  • Kick off Mocha tests



[Fake]
linuxPrefix = "mono"
command = "build.cmd"
build = "build.fsx"
view raw .ionide hosted with ❤ by GitHub
// include Fake lib #r @"../packages/FAKE/tools/FakeLib.dll" open System open System.Diagnostics open System.IO open Fake // Properties let buildDir = "./bin/" let targetScript = let linqPadQueriesFolder = @"C:\projects\LinqPad\LinqPad\LINQPad Queries\" Path.Combine(linqPadQueriesFolder,@"WIP\runPmWeb.linq") let lprunPath = @"C:\ProgramData\LINQPad\Updates50\510\lprun.exe" let configLpRun (pi: ProcessStartInfo) = trace (sprintf "Target script:%s" targetScript) pi.FileName <- lprunPath pi.Arguments <- sprintf "\"%s\"" targetScript // Helpers let flip f y x = f x y let warn msg = trace (sprintf "WARNING: %s" msg) type System.String with static member Delimit delimiter (items:string seq) = String.Join(delimiter,items |> Array.ofSeq) module Sec = open System.Security.Principal let getIsAdmin() = WindowsIdentity.GetCurrent() |> WindowsPrincipal |> fun wp -> wp.IsInRole(WindowsBuiltInRole.Administrator) let requireAdmin () = let runningAsAdmin = getIsAdmin() if not runningAsAdmin then failwithf "Requested feature is not known to work without administrator permissions" module Proc = //let execCmd prog args timeout = let findCmd cmd = let processResult = ExecProcessAndReturnMessages (fun psi -> psi.FileName <- "where" psi.Arguments <- quoteIfNeeded cmd ) (TimeSpan.FromSeconds 2.) if processResult.OK then // require the result not be a directory let cmdPath = processResult.Messages |> Seq.filter (Directory.Exists >> not) |> Seq.filter (File.Exists) |> Seq.filter (fun x -> x.EndsWith ".bat" || x.EndsWith ".exe" || x.EndsWith ".cmd") |> Seq.tryHead if processResult.Messages.Count > 1 then warn (sprintf "found multiple items matching '%s'" cmd) trace (processResult.Messages |> String.Delimit ";") match cmdPath with | Some path -> trace (sprintf "found %s at %s" cmd path) Some path | None -> warn "where didn't return a valid file" None else None let runWithOutput cmd args timeOut = let cmd = // consider: what if the cmd is in the current dir? where may find one elsewhere first? if Path.IsPathRooted cmd then cmd else match findCmd cmd with | Some x -> x | None -> warn (sprintf "findCmd didn't find %s" cmd) cmd let result = ExecProcessAndReturnMessages (fun f -> //ExecProcessRedirected (fun f -> //f.FileName <- @"gulp" //f. Arguments <- "sass" // why did 'where' with no full path work, but this fails? f.FileName <- cmd f.Arguments <- args ) (TimeSpan.FromMinutes 1.0) result,cmd let showInExplorer path = Process.Start("explorer.exe",sprintf "/select, \"%s\"" path) // wrapper for fake built-in in case we want the entire process results, not just the exitcode let runElevated cmd args timeOut = let tempFilePath = System.IO.Path.GetTempFileName() // could also redirect error stream with 2> tempErrorFilePath // see also http://www.robvanderwoude.com/battech_redirection.php let resultCode = ExecProcessElevated "cmd" (sprintf "/c %s %s > %s" cmd args tempFilePath) timeOut trace "reading output results of runElevated" let outputResults = File.ReadAllLines tempFilePath File.Delete tempFilePath let processResult = ProcessResult.New resultCode (ResizeArray<_> outputResults) (ResizeArray<_>()) (String.Delimit "\r\n" outputResults) |> trace processResult type FindOrInstallResult = |Found |InstalledThenFound let findOrInstall cmd fInstall = match findCmd cmd with | Some x -> Some (x,Found) | None -> fInstall() findCmd cmd |> Option.map (fun x -> (x,InstalledThenFound)) module Node = let npmPath = lazy(Proc.findCmd "npm") // assumes the output is unimportant, just the result code let npmInstall args = let resultCode = let filename, useShell = match npmPath.Value with | Some x -> x, false // can't capture output with true | None -> "npm", true trace (sprintf "npm filename is %s" filename) ExecProcess (fun psi -> psi.FileName <- filename psi.Arguments <- "install" psi.UseShellExecute <- useShell ) (TimeSpan.FromMinutes 1.) resultCode // Targets Target "SetupNode" (fun _ -> // goal: install and setup everything required for any node dependencies this project has // including nodejs, gulp, node-sass // install Choco let chocoPath = let fInstall () = let resultCode = ExecProcessElevated "@powershell" """-NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" """ (TimeSpan.FromMinutes 3.) resultCode |> sprintf "choco install script returned %i" |> trace if resultCode <> 0 then failwithf "Task failed" // choco is installled, we think // probably won't work if it was just installed, the %path% variable given to/used by a process is immutable match Proc.findOrInstall "choco" fInstall with //| Some (x,Proc.FindOrInstallResult.Found) -> x | Some (x,_) -> x | None -> failwithf "choco was installed, in order for choco to be found or used, this has process has to be restarted" // choco install nodeJs let nodePath = let fInstall () = let results = Proc.runElevated "choco" "install nodejs -y" (TimeSpan.FromSeconds 3.) trace (sprintf "%A" results) match Proc.findOrInstall "node" fInstall with | Some (x,_) -> x | None -> failwithf "nodejs was installed, in order for node to be found or used, this process has to be restarted" // node should have installed npm // npm let npmPath = Proc.findCmd "npm" // install all packages that packages.json says this project needs let resultCode = let filename, useShell = match npmPath with | Some x -> x, false // can't capture output with true | None -> "npm", true trace (sprintf "npm filename is %s" filename) ExecProcess (fun psi -> psi.FileName <- filename psi.Arguments <- "install" psi.UseShellExecute <- useShell ) (TimeSpan.FromMinutes 1.) () ) // run node tests and whatever else Target "Test" (fun _ -> let result, _ = Proc.runWithOutput "npm" "test" (TimeSpan.FromSeconds 4.) result.Messages |> Seq.iter (printfn "test-msg:%s") if result.ExitCode <> 0 then result.Errors |> Seq.iter(printfn "test-err:%s") failwithf "Task failed: %i" result.ExitCode ) Target "Coffee" (fun _ -> let coffees = [ "test/test.coffee" ] let compileCoffee relPath = let result,_ = Proc.runWithOutput "node" (sprintf "node_modules/coffee-script/bin/coffee -b -m --no-header -c %s" relPath) (TimeSpan.FromSeconds 2.) if result.ExitCode <> 0 then failwithf "Task failed: %A" result coffees |> Seq.iter compileCoffee ) Target "Babel" (fun _ -> // run jsx compilation let babels = [ "Scripts/pm-era.jsx" "Scripts/pm-era-remitdetail.jsx" ] let babel relPath = let targetPath = let fullPath = Path.GetFullPath relPath Path.Combine(fullPath |> Path.GetDirectoryName, fullPath |> Path.GetFileNameWithoutExtension |> flip (+) ".react.js") let result,_ = Proc.runWithOutput "node" (sprintf "node_modules/babel-cli/bin/babel %s -o %s -s --presets react" relPath targetPath) (TimeSpan.FromSeconds 2.) if result.ExitCode <> 0 then result.Messages |> Seq.iter (printfn "babel-msg:%s") result.Errors |> Seq.iter(printfn "babel-err:%s") failwithf "Task failed: %i" result.ExitCode else result.Messages |> Seq.iter (printfn "babel-msg:%s") babels |> Seq.iter babel ) //node node_modules\coffee-script\bin\coffee -b -c test/test.coffee Target "Clean" (fun _ -> CleanDir buildDir let files = Directory.GetFiles buildDir |> Seq.length let directories = Directory.GetDirectories buildDir |> Seq.length printfn "cleaned directory had %i item(s) remaining after clean" (files + directories) ) Target "BuildApp" (fun _ -> let output = if isNullOrEmpty buildDir then "" else buildDir |> FullName |> trimSeparator let setParams defaults = { defaults with MSBuildParams.Targets= ["Build"] //Verbosity = Some(MSBuildVerbosity.Diagnostic) Properties = [ "Configuration", "Debug" "Platform", "AnyCPU" "DebugSymbols", "True" "OutputPath", buildDir "SolutionDir", ".." |> FullName ] } //https://github.com/fsharp/FAKE/blob/master/src/app/FakeLib/MSBuildHelper.fs build setParams "Pm.Web.fsproj" if isNotNullOrEmpty output then !!(buildDir @@ "/**/*.*") |> Seq.toList else [] |> Log "AppBuild-Output: " ) Target "StartApp" (fun _ -> let proc = new Process() configLpRun proc.StartInfo //proc.StartInfo.UseShellExecute <- false // not using ProcessHelper.Start we don't want fake killing it proc.Start() |> ignore trace (sprintf "started app with pid:%i" proc.Id) ) Target "Fire" (fun _ -> ProcessHelper.fireAndForget configLpRun ) Target "Run" (fun _ -> //C:\projects\LinqPad\LinqPad\LINQPad Queries\WIP\runPmWeb.linq //https://github.com/fsharp/FAKE/blob/master/src/app/FakeLib/ProcessHelper.fs asyncShellExec { Program = lprunPath WorkingDirectory = null CommandLine = sprintf "\"%s\"" targetScript Args = list.Empty } |> Async.RunSynchronously //Shell.Exec (,@"C:\projects\LinqPad\LinqPad\LINQPad Queries\",@"C:\projects\LinqPad\LinqPad\LINQPad Queries\WIP\") |> sprintf "script returned %i" |> trace ) Target "Stop" (fun _ -> killProcess "lprun" ) Target "NodeTasks" (fun _ -> trace "Node Tasks completed" ) Target "Sass" (fun _ -> let result,_ = Proc.runWithOutput "node-sass" "content/site.scss content/site.css" (TimeSpan.FromSeconds 4.) trace "finished attempting sass" printfn "sass:%A" result //result.Messages //|> Seq.iter (printfn "sass:%A") if result.ExitCode <> 0 then failwithf "Task failed" ) Target "NodeList" (fun _ -> let result = Proc.runWithOutput "npm" "list --depth=0" (TimeSpan.FromSeconds 2.0) printfn "npm list -g --depth=0:\r\n%A" result ) Target "Default" (fun _ -> trace "Hello World from FAKE" ) Target "AfterAll" (fun _ -> trace (sprintf "%A" DateTime.Now) ) // Dependencies // this should be NodeTasks depends on Sass, Coffee, and jsx compilation // ==> "Coffee" "Sass" ==> "NodeTasks" "Coffee" ==> "NodeTasks" "Stop" ==> "Run" For "NodeTasks" ["Sass";"Coffee";"Babel";"Test"] // "Stop" ==> "Fire" For "Fire" [ "Stop" ] For "StartApp" [ "Stop" ] For "StartApp" [ "Stop" ] For "Test" [ "Coffee" ] // default doesn't include starting the app "Stop" ==> "Clean" ==> "BuildApp" ==> "StartApp" ==> "Default" ==> "AfterAll" // make sure nodetasks happens before buildapp if nodetasks is executed "NodeTasks" ?=> "BuildApp" "Coffee" ?=> "Test" RunTargetOrDefault "Default"
view raw build.fsx hosted with ❤ by GitHub
{
"name": "pm.web",
"version": "1.0.0",
"description": "Registry additions have been made in order to provide you the best web development experience. See http://bloggemdano.blogspot.com/2013/11/adding-new-items-to-pure-f-aspnet.html for more information.",
"main": "index.js",
"dependencies": {
"requirejs": "^2.3.2"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-preset-react": "^6.16.0",
"coffee-script": "github:jashkenas/coffeescript",
"mocha": "^3.2.0"
},
"scripts": {
"wastest": "echo \"Error: no test specified\" && exit 1",
"test": "mocha"
},
"author": "",
"license": "ISC"
}
view raw package.json hosted with ❤ by GitHub
Version information: