Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fable implement foreign interface compile errors

Tags:

f#

fable-f#

TL;DR: I am trying to create a foreign interface, but I get compile errors. My F# skills are not yet equal to the task.


I have written a Photoshop script and now I am trying to convert that script to Fable (F#) for my own F# practise.

Sidenote: I am very new to F# but I have a good C# background.

To use the photoshop script library I need to define it as foreign interfaces (http://fable.io/docs/interacting.html). I am trying to implement a small class first for testing purposes. Namely the UnitValue class (see https://wwwimages.adobe.com/content/dam/Adobe/en/products/indesign/pdfs/JavaScriptToolsGuide_CS5.pdf p.230)

My attempt:

#r "node_modules/fable-core/Fable.Core.dll"

open System
open Fable.Core

[<Global>]
module Photoshop =
    [<Erase;StringEnum>]
    type PSUnit =
        | [<CompiledName("in")>] Inches
        | [<CompiledName("ft")>] Feet
        | [<CompiledName("yd")>] Yards
        | [<CompiledName("mi")>] Miles
        | [<CompiledName("mm")>] Millimeters
        | [<CompiledName("cm")>] Centimeters
        | [<CompiledName("m")>] Meters
        | [<CompiledName("km")>] Kilometers
        | [<CompiledName("pt")>] Points
        | [<CompiledName("pc")>] Picas
        | [<CompiledName("tpt")>] TraditionalPoints
        | [<CompiledName("tpc")>] TraditionalPicas
        | [<CompiledName("ci")>] Ciceros
        | [<CompiledName("px")>] Pixels
        | [<CompiledName("%")>] Percent

    type PSUnitValue =
        abstract ``as``: PSUnit -> float
        abstract ``value``: float with get, set
        abstract ``type``: PSUnit with get, set    

    let UnitValue : PSUnitValue = jsNative

open Photoshop

let test = new UnitValue();
test.``value`` = 12.0;
test.``type`` = Unit.Centimeters

Console.WriteLine (test.``as`` PSUnit.Millimeters)

During compilation I get:

C:\PsScripts>fable test.fsx
fable-compiler 0.7.28: Start compilation...
F# project contains errors:
C:\PsScripts\test.fsx(L35,15) : error FSHARP: The type 'UnitValue' is not defined
C:\PsScripts\test.fsx(L36,0) : error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
C:\PsScripts\test.fsx(L37,0) : error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
C:\PsScripts\test.fsx(L37,21) : error FSHARP: The field, constructor or member 'Centimeters' is not defined
C:\PsScripts\test.fsx(L39,19) : error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
C:\PsScripts\test.fsx(L39,0) : error FSHARP: A unique overload for method 'WriteLine' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: Console.WriteLine(buffer: char []) : unit, Console.WriteLine(format: string, [<ParamArray>] arg: obj []) : unit, Console.WriteLine(value: bool) : unit, Console.WriteLine(value: char) : unit, Console.WriteLine(value: decimal) : unit, Console.WriteLine(value: float) : unit, Console.WriteLine(value: float32) : unit, Console.WriteLine(value: int) : unit, Console.WriteLine(value: int64) : unit, Console.WriteLine(value: obj) : unit, Console.WriteLine(value: string) : unit, Console.WriteLine(value: uint32) : unit, Console.WriteLine(value: uint64) : unit

My expected output would be something like:

var test = new UnitValue();
test.value = 12;
test.unit = "cm";
console.log(test.as("mm"));

For those who would like to know how the UnitValue class looks and don't want to look it up in the pdf, here I defined it in C#:

public class UnitValue
{
    // Static baseUnit not yet implemented in Fable
    public static UnitValue baseUnit { get; set; }

    // Constructors not yet implemented in Fable
    public UnitValue(float value, string unit) { }
    public UnitValue(string valueUnit) { }

    public string type { get; set; }
    public float value { get; set; }

    public float @as(string unit) { throw new NotImplementedException(); }

    // I don't use convert in my scripts, so not yet implemented in Fable as well
    public UnitValue convert(string unit) { throw new NotImplementedException(); }
}
like image 368
SynerCoder Avatar asked Sep 07 '26 09:09

SynerCoder


2 Answers

  • You are binding a variable UnitValue, not declaring an alias for a type (not sure if that was the intent).

  • types and values, just like in C# have different scopes, so as far as compiler is concerned the type is not declared.

  • jsNative is just a shorthand for "throw", it's intended for imported JS constructs, but you are not importing any. See http://fable.io/docs/interacting.html

  • since JS lacks types, if the interop is the intent, you actually have to define the type in F# yourself.

  • if interop is not an intent, then write a normal F# code, without any Fable attributes, fable will take care of the rest.

Adding the comment answer here for the formatting: UnitValue needs to be a function (constructor):

let UnitValue : unit->PSUnitValue = jsNative 

then you could call it:

let test = UnitValue() 

Also checkout this post for a bit of a background on bindings.

like image 53
Eugene Tolmachev Avatar answered Sep 11 '26 07:09

Eugene Tolmachev


With the help from Eugene Tolmachev:

Working(ish) code:

#r "node_modules/fable-core/Fable.Core.dll"

open System
open Fable.Core

module Photoshop =
    [<StringEnum>]
    type PSUnit =
        | [<CompiledName("in")>] Inches
        | [<CompiledName("ft")>] Feet
        | [<CompiledName("yd")>] Yards
        | [<CompiledName("mi")>] Miles
        | [<CompiledName("mm")>] Millimeters
        | [<CompiledName("cm")>] Centimeters
        | [<CompiledName("m")>] Meters
        | [<CompiledName("km")>] Kilometers
        | [<CompiledName("pt")>] Points
        | [<CompiledName("pc")>] Picas
        | [<CompiledName("tpt")>] TraditionalPoints
        | [<CompiledName("tpc")>] TraditionalPicas
        | [<CompiledName("ci")>] Ciceros
        | [<CompiledName("px")>] Pixels
        | [<CompiledName("%")>] Percent

    type PSUnitValue =
        abstract ``as``: PSUnit -> float
        abstract ``value``: float with get, set
        abstract ``type``: PSUnit with get, set    
    let [<Global>] UnitValue : unit->PSUnitValue = jsNative

open Photoshop

let test = UnitValue();
test.``value`` <- 12.0;
test.``type`` <- PSUnit.Centimeters

Console.WriteLine (test.``as`` PSUnit.Millimeters)

Output

export var test = UnitValue(null);
test.value = 12;
test.type = "cm";
console.log(test.as("mm"));

I don't know what the export keyword is doing here, or why there isn't a new keyword for the UnitValue, but it compiled and looks in the direction of what I want....

Onwards!


I now have the new keyword in my output:

let [<Global;Emit("new $0($1, $2)")>] UnitValue (v: float) (t: PSUnit): IUnitValue = jsNative

And usage now is:

let test = UnitValue 12.0 PSUnit.Centimeters
Console.WriteLine (test.``as`` PSUnit.Millimeters)
like image 27
SynerCoder Avatar answered Sep 11 '26 07:09

SynerCoder