Print elm-syntax
declarations as F# code.
To try it out, you can
run this script.
import Elm.Parser
import ElmSyntaxToFsharp
"""module Sample exposing (..)
plus2 : Int -> Int
plus2 n =
n + ([ 2 ] |> List.sum)
"""
|> Elm.Parser.parseToFile
|> Result.mapError (\_ -> "failed to parse elm source code")
|> Result.map
(\syntaxModule ->
[ syntaxModule ]
|> ElmSyntaxToFsharp.modules
|> .declarations
|> ElmSyntaxToFsharp.fsharpDeclarationsToModuleString
)
-->
Ok """module Elm
let Sample_plus2 (n: int64) : int64 =
(+) n (List.sum [ 2L ])
..and some default declarations..
"""
- not supported are
- ports that use non-json values like
port sendMessage : String -> Cmd msg
, glsl elm/file
,elm/http
,elm/browser
,elm-explorations/markdown
,elm-explorations/webgl
,elm-explorations/benchmark
Task
,Process
,Platform.Task
,Platform.ProcessId
,Platform.Router
,Platform.sendToApp
,Platform.sendToSelf
,Random.generate
,Time.now
,Time.every
,Time.here
,Time.getZoneName
,Bytes.getHostEndianness
,Math.Matrix4.inverseOrthonormal
,Math.Matrix4.mulAffine
- extensible record types outside of module-level value/function declarations. For example, these declarations might not work:
Allowed is only record extension in module-level value/functions, annotated or not:
-- in variant value type Named rec = Named { rec | name : String } -- in let type, annotated or not let getName : { r | name : name } -> name
In the non-allowed cases listed above, we assume that you intended to use a regular record type with only the extension fields which can lead to F# compile errors if you actually pass in additional fields.userId : { u | name : String, server : Domain } -> String
- ports that use non-json values like
- elm-exploration/linear-algebra's
Vec2
,Vec3
,Vec4
,Mat4
components have 64-bit precision but their F# counterparts only have 32 - dependencies cannot internally use the same module names as the transpiled project
- the resulting code might not be readable or even conventionally formatted and comments are not preserved
Please report any issues you notice <3
- it runs decently fast (remember to enable AOT compilation) and can even compile further into Wasm or using fable into languages like rust
- it's pretty much a superset of elm which makes transpiling easy
An example can be found in example-hello-world/
.
Add a project config yourProjectName.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>yourProjectName</RootNamespace>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<Compile Include="src/Elm.fs" />
<Compile Include="src/Program.fs" />
</ItemGroup>
</Project>
Add a file src/Program.fs
that uses src/Elm.fs
module Program
[<EntryPoint>]
let main args =
let output = Elm.YourModule_yourFunction yourInput
0
where Elm.YourModule_yourFunction
is the transpiled elm function Your.Module.yourFunction
. (If the value/function contains number
type variables or extensible records, search for Elm.YourModule_yourFunction__
to see the different specialized options)
You will find these types:
- elm
Float
→ F#float
,Bool
→bool
,()
→unit
(create and match with()
),List Float
->List<float>
,Array Float
→array<float>
,Set Float
->Set<float>
,Dict Float Char
→Map<float, char>
,Result Float Char
→Result<char, float>
,Order
→Elm.Basics_Order
(enum) - elm
Int
s will be of typeint64
. You can create them explicitly by appending L to an int literal (42L
) or simply using anyint
(F# implicitly converts them) and unwrap them to anint
withint yourInt64
- elm
String
s will be of typeElm.StringRope
. You can create them withElm.StringRope.fromString yourFsharpString
and unwrap them withElm.StringRope.toString yourTranspiledString
- elm
Char
s will be of typeint
. You can create them with e.g.int 'a'
or the char code. They represent the value of a unicode scalar/"rune" which is comprised of 1-2 UTF-16char
s - elm records like
{ a : Float, b : Float }
will be provided as constructed type aliases for each field combination:Elm.Generated_A_B<float, float>
. While the type might look weird, values can be created and used like any regular record with uppercase field names, e.g.{ A = 1.1; B = 2.2 }
- elm tuples/triples like
( Float, Float )
will be of type(struct( float * float ))
. You can create them withstruct( 1.1, 2.2 )
und destructure them withstruct( a, b )
- elm
Maybe.Maybe
s will be of typeValueOption
. You can create and match them withValueSome
forJust
andValueNone
forNothing
- elm
Json.Encode.Value
/Json.Decode.Value
will be of typeSystem.Text.Json.Nodes.JsonNode
. Encode and decode them like you would in elm, likeElm.JsonEncode_float 2.2
- a transpiled elm app does not run itself.
An elm main
Platform.worker
program type will literally just consist of fieldsInit
,Update
andSubscriptions
where subscriptions/commands are returned as a list ofElm.PlatformSub_SubSingle
/Elm.PlatformCmd_CmdSingle
with possible elm subscriptions/commands in a choice type. It's then your responsibility as "the platform" to perform effects, create events and manage the state. For an example see example-worker/ - elm
Regex
will be of typeSystem.Text.RegularExpressions.Regex
. Create them like you would in elm withElm.Regex_fromString
,Elm.Regex_fromStringWith
orElm.Regex_never
- elm-exploration/linear-algebra's
Math.Vector2.Vec2
,Math.Vector3.Vec3
,Math.Vector4.Vec4
,Math.Matrix4.Mat4
will be of typeSystem.Numerics.Vector2
,System.Numerics.Vector3
,System.Numerics.Vector4
,System.Numerics.Matrix4x4
Compile the resulting F# to an executable:
dotnet publish --self-contained -c release
The built executable can now be found at /bin/release/net9.0/yourArchitecture/native/yourProjectName
.
Or build and run it once:
dotnet run
append --no-restore
on consecutive runs to make it faster.
If something unexpected happened, please report an issue.