Purescript compiles but js throws error : component.initialState is not a function - web-frontend

I'm building a simple video player component using purescript-halogen. The component is supposed to show a blank div with only an input button for the user to select a local file which will then act as a source URL for the video element.
I completed a working model in plain javascript and wanted to port it to purescript/halogen. I got the purescript version to compile but the web console gives me an error message Uncaught TypeError: component.initialState is not a function and points me to this MDN reference.
This would suggest an issue with how I defined my initialState function but it's fairly standard:
component :: forall q i o m. MonadAff m => H.Component q i o m
component =
H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
}
type State =
{ videoUrl :: Maybe String }
initialState :: forall i. i -> State
initialState _ =
{ videoUrl : Nothing
}
Is it possible that the function is called at the wrong time during its lifecycle, hence making it undefined?
The code for the component is hosted on this Github gist. And I've been following this Github issue as reference.
My package.json :
"private": true,
"devDependencies": {
"parcel": "1.12.3",
"purescript": "^0.14.0",
"spago": "^0.19.1"
},
"scripts": {
"build": "spago build",
"test": "spago test",
"serve": "parcel dev/index.html --open",
"build-prod": "mkdir -p prod && cp dev/index.html prod/ && rm -rf dist && spago bundle-app --to prod/index.js && parcel build prod/index.html"
},
"dependencies": {
"node": "^15.12.0"
}
}
I compiled using purescript v0.14.0, spago v0.20.0, node v16.3.0 and tested on Firefox v89.0 in Ubuntu 20.04.2 LTS.

Seems the bug is from a pesky line in the code:
HH.input
[ ...
, HP.value "Select file"
...]
According to MDN, the value property of an input element is used to hold the path to the selected files. Therefore, setting it during element creation is not a well defined action.
The final code (which runs as expected) is:
module App.Video (component) where
import DOM.HTML.Indexed.InputAcceptType (InputAcceptType, InputAcceptTypeAtom(..))
import Data.Foldable (traverse_)
import Data.Maybe (Maybe(..))
import Data.MediaType (MediaType(..))
import Data.Traversable (for_)
import Effect.Aff.Class (class MonadAff)
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
import Prelude (Unit, bind, const, discard, pure, unit, ($), (=<<), (>>=))
import Web.Event.Event as Event
import Web.File.File as File
import Web.File.FileList as FileList
import Web.File.FileReader.Aff as FileReaderAff
import Web.HTML.HTMLInputElement as InputElement
data Action = HandleFileUpload Event.Event
type State =
{ videoUrl :: Maybe String }
component :: forall q i o m. MonadAff m => H.Component q i o m
component =
H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
}
initialState :: forall input. input -> State
initialState inp =
{ videoUrl : Nothing
}
render :: forall m slots. State -> H.ComponentHTML Action slots m
render state =
case state.videoUrl of
Nothing -> blank_player
Just url -> video_player url
supported_formats :: InputAcceptType
supported_formats =
HP.InputAcceptType
[ AcceptMediaType (MediaType "video/mp4")
, AcceptMediaType (MediaType "video/webm")
]
blank_player :: forall w. HH.HTML w Action
blank_player =
HH.div_
[ HH.span_ [HH.text "Choose file to upload"]
, HH.input
[ HP.type_ HP.InputFile
, HP.accept supported_formats
, HE.onChange HandleFileUpload
]
]
video_player :: forall w i. String -> HH.HTML w i
video_player url =
HH.div_ [
HH.video [HP.src url] []
]
handleAction :: forall m slots o. MonadAff m => Action -> H.HalogenM State Action slots o m Unit
handleAction = case _ of
HandleFileUpload ev → do
traverse_ handleFileUpload (InputElement.fromEventTarget =<< Event.target ev)
handleFileUpload :: forall m slots o. MonadAff m => InputElement.HTMLInputElement -> H.HalogenM State Action slots o m Unit
handleFileUpload inputEl = do
H.liftEffect (InputElement.files inputEl) >>= traverse_ \files ->
for_ (FileList.item 0 files) \file → do
video_url ← H.liftAff $ FileReaderAff.readAsDataURL (File.toBlob file)
H.modify_ (const { videoUrl : Just video_url})
pure unit

Related

How to define a shake rule to build docker images?

I have a shake build (version 0.16.4) that builds a lot of docker images out of Dockerfiles and other support files. I would like to factor out all those build in a single "rule" whose output will be a docker image. I have read How to define a timer rule in Shake about custom rules in shake but this does not explain how to define one's own output.
I would like to express something like
Image "foo" "latest" %> \ img -> do
need [ ... ]
buildDockerImage "docker/foo" img
Image "bar" "latest" %> \ img ->
needImage "foo" "latest"
...
and then have shake track the image as a dependency. I have already implemented that kind of stuff in an older version of shake but I am clueless about how to do it in > 0.16.
Update:
I have tried this, following the guidelines from https://hackage.haskell.org/package/shake-0.17.3/docs/Development-Shake-Rule.html
newtype Repo = Repo String
deriving (Show, Eq, Hashable, Binary, NFData)
newtype Tag = Tag String
deriving (Show, Eq, Hashable, Binary, NFData)
newtype SHA256 = SHA256 String
deriving (Show, Eq, Hashable, Binary, NFData)
newtype Image = Image (Repo,Tag)
deriving (Show, Eq, Hashable, Binary, NFData)
type instance RuleResult Image = SHA256
data ImageRule = ImageRule Image (Action ())
imageRule :: (Repo, Tag) -> Action () -> Rules ()
imageRule k a = addUserRule $ ImageRule (Image k) a
needImage :: (Repo,Tag) -> Action SHA256
needImage = apply1 . Image
toBytes :: String -> BS.ByteString
toBytes = encodeUtf8 . Text.pack
fromBytes :: BS.ByteString -> String
fromBytes = Text.unpack . decodeUtf8
addBuiltinDockerRule :: Rules ()
addBuiltinDockerRule = addBuiltinRule noLint imageIdentity run
where
imageIdentity _ (SHA256 v) = toBytes v
imageSha (Image (Repo r,Tag t)) = do
Stdout out <- cmd "docker" [ "images", "--no-trunc", "-q", r <> ":" <> t ]
pure $ BS.unpack out
run :: BuiltinRun Image SHA256
run key old mode = do
current <- imageSha key
liftIO $ putStrLn ("current:" <> show current)
if mode == RunDependenciesSame && fmap BS.unpack old == Just current then
return $ RunResult ChangedNothing (BS.pack current) (SHA256 $ fromBytes $ BS.pack current)
else do
(_, act) <- getUserRuleOne key (const Nothing) $ \(ImageRule k act) -> if k == key then Just act else Nothing
act
current <- imageSha key
return $ RunResult ChangedRecomputeDiff (BS.pack current) (SHA256 $ fromBytes $ BS.pack current)
And then using it in a Build.hs file:
main :: IO ()
main = shakeArgs options $ do
addBuiltinDockerRule
want [ ".haskell.img" ]
imageRule (Repo "haskell", Tag "latest") $ do
need [ "docker/haskell/Dockerfile" ]
cmd "docker" [ "build", "-t", "haskell:latest", "-f", "docker/haskell/Dockerfile" ]
".haskell.img" %> \ fp -> do
needImage (Repo "haskell", Tag "latest")
Stdout out <- cmd "docker" [ "images", "--no-trunc", "-q", "haskell:latest" ]
writeFile' fp out
This seems to work but one shortcoming is that it's not possible to want an image: I have to add a rule with file to make it work.
So the proposed solution works. One can add a "phony" target to ensure the images are built on demand:
"haskell.img" ~> void (needImage "haskell")

Dynamic Chart - Fable

I have a project with model update view architecture using fable-elmish. And I have to download files every minute and read those files. How can I download in the update function and how can I read and parsing to Json?
I need to create dynamic charts using Fable too. Someone knows how?
I have part of my code here:
let update (msg : Msg) (model : Model) =
match msg with
| GetData ->
model,
Cmd.ofPromise
(fun () ->
promise {
let wc = new WebClient()
wc.DownloadData("https://www.quandl.com/api/v1/datasets/LBMA/SILVER.json", "SILVER.json")
wc.DownloadData("https://www.quandl.com/api/v1/datasets/LBMA/GOLD.json", "GOLD.json")
// Read 2 files
// Return 2 Json.Object
})
()
(fun silver gold -> GotData silver gold)
(fun e -> GotError e.Message)
| GotData silver gold ->
(Model.SilverData silver, Model.GoldData gold), // I think this doesn't work
Cmd.ofPromise
(fun () -> Promise.sleep 60000)
()
(fun () -> GetData)
(fun e -> GetData)
If you have a periodic event which should cause some action in your Elmish application I would use a subscription. The following code snippet shows a function which sets an interval that causes a command dispatch every 10 minutes.
let timer initial =
let sub dispatch =
window.setInterval(fun _ -> dispatch LoadDataSet; console.log("Timer triggered")
, 1000 * 60 * 10) |> ignore
Cmd.ofSub sub
You would use the Program.withSubscription function to add the subscription to your main dispatch loop.
I would use the Fable PowerPack package for its fetch and promise support to get the datasets. The following code would fetch the documents from your specified endpoints, parse them as values of the DataSet type and return them as a value of the SilverAndGold model type on the successful path of the promise.
type DataSet =
{ column_names : string list
data : (string * float * float * float) list }
type SilverAndGold =
{ Silver : DataSet
Gold : DataSet }
...
let fetchDataSets () = promise {
let! silverData = Fetch.fetchAs<DataSet> "https://www.quandl.com/api/v1/datasets/LBMA/SILVER.json" []
let! goldData = Fetch.fetchAs<DataSet> "https://www.quandl.com/api/v1/datasets/LBMA/GOLD.json" []
return { Silver = silverData; Gold = goldData }
}
In the update function of the Elmish app you can see how the promise execution is triggered. On every LoadDataSet message dispatched by our subscription we create a command of the promise which either results in a DataSetLoaded message containing the datasets or in an Error.
let update (msg:Msg) (model:Model) =
match msg with
| LoadDataSet ->
model, Cmd.ofPromise fetchDataSets () DataSetLoaded Error
| DataSetLoaded silverGold ->
// here you could process you silver and gold datasets
console.log silverGold
Some silverGold, Cmd.none
| Error e -> model, Cmd.none
We can use the Fable bindings for the Recharts library to plot our datasets. The following code shows how we transform and trim the datasets (rendering all datapoints would be quite taxing in the browser) and display them as line charts in the view function.
type ChartDataPoint =
{ Date : string
Usd : float
Gbp : float
Euro : float }
let toChartData (dataSet : DataSet) =
dataSet.data
|> List.map (fun (dt, usd, gbp, eur) ->
{ Date = dt; Usd = usd; Gbp = gbp; Euro = eur } )
|> Array.ofList
|> Array.take 1000
|> Array.rev
let priceChart (chartData : ChartDataPoint[]) =
lineChart
[ Chart.Data chartData
Chart.Width 600.
Chart.Height 500. ] [
xaxis [ Cartesian.DataKey "Date" ] []
yaxis [] []
tooltip [] []
legend [] []
line [ Cartesian.Type "monotone"; Cartesian.DataKey "Gbp" ] []
line [ Cartesian.Type "monotone"; Cartesian.DataKey "Euro" ] []
line [ Cartesian.Type "monotone"; Cartesian.DataKey "Usd" ] []
]
let view (model : SilverAndGold option ) dispatch =
div [ ] [
match model with
| Some sets ->
yield h2 [] [ str "Silver" ]
yield priceChart (toChartData sets.Silver)
yield h2 [] [ str "Gold" ]
yield priceChart (toChartData sets.Gold)
| None ->
yield h2 [] [ str "No data :("]
]
I cooked up a very little Elmish app which includes all these topics. You can find it here here and adapt it according to your needs.

How to get the selected options of a multiselect in Elm?

I've seen what is required for a getting the selected index of a single select but I'm interested in getting all of the selected options from a multi select. I haven't been able to work out how to do this.
I've attempted the following but I suspect the Json decoder is failing. I'm not 100% sure of that though, because the decoding happens in the virtual dom code and any errors there are thrown away.
type Msg
= SetMultipleInts (List Int)
-- I'm not seeing the SetMultipleInts message when I click on the multiselect
view model =
div []
[ select (onSelect SetMultipleInts) (List.map myOption [1..4]) ]
myOption : Int -> Html Msg
myOption id =
option [ value (toString id) ] [ text <| "Option " ++ (toString id) ]
-- I'm not seeing anything happen in the onchange
onMultiSelect : (List Int -> msg) -> List (Html.Attribute msg)
onMultiSelect msg =
[ on "change" (Json.map msg targetSelectedOptions), multiple True ]
targetSelectedOptions : Json.Decoder (List Int)
targetSelectedOptions =
Json.at [ "target", "selectedOptions" ] (Json.list (Json.at [ "value" ] Json.int))
Can I do this without having to use ports?
The decoder fails because event.target.selectedOptions is not a
javascript array. When you cannot use Json.Decode.list, you
can use Json.Decode.keyValuePairs.
Here is the example how you can use it.
You may want to change extractValues below depending
on how you want to react to empty selection and such.
targetSelectedOptions : Json.Decoder (List String)
targetSelectedOptions =
let
maybeValues =
Json.at [ "target", "selectedOptions" ]
<| Json.keyValuePairs
<| Json.maybe ("value" := Json.string)
extractValues mv =
Ok (List.filterMap snd mv)
in Json.customDecoder maybeValues extractValues
In case someone need a multiselect in Elm, I rewrote a fully working example in Elm 0.19:
https://ellie-app.com/g7WrS9cV4zVa1
module Main exposing (main)
import Browser
import Html exposing (..)
import Html.Attributes
import Html.Events
import Json.Decode
type alias Model =
{ value : List ( String, Maybe String ) }
init : Model
init =
{ value = [] }
type Msg
= SetMultipleInts (List ( String, Maybe String ))
update : Msg -> Model -> Model
update msg model =
case msg of
SetMultipleInts value ->
{ model | value = value }
view : Model -> Html Msg
view model =
div []
[ select
[ Html.Events.on "change"
(Json.Decode.map SetMultipleInts targetSelectedOptions)
, Html.Attributes.multiple True
]
(List.map myOption (List.range 1 4))
, div []
[ text <|
Debug.toString
(model
|> .value
|> List.map Tuple.second
|> List.filterMap identity
)
]
]
targetSelectedOptions : Json.Decode.Decoder (List ( String, Maybe String ))
targetSelectedOptions =
Json.Decode.at [ "target", "selectedOptions" ] <|
Json.Decode.keyValuePairs <|
Json.Decode.maybe (Json.Decode.at [ "value" ] Json.Decode.string)
myOption : Int -> Html Msg
myOption id =
option [ Html.Attributes.value (String.fromInt id) ]
[ text <| "Option " ++ String.fromInt id ]
main : Program () Model Msg
main =
Browser.sandbox
{ init = init
, view = view
, update = update
}

Cassandra CQL Mapping in F#

I am having a problem trying to map a property on my type to a mapping in Cassandra.
I have it working in C#, but i am struggling with the F#
The error i get is: Stack Trace: [System.ArgumentOutOfRangeException: Expression x => new Tuple`2(x.Id, ToFSharpFunc(x => x.WithName("player_id"))) is not a property or field. Parameter name: expression]
on Line 26, if I removed "fun(x:ColumnMap)->x.WithName("player_id")" it compiles but then it will not map to the correct table column. My models property name is "Id" but i need it to map to "player_id"
open Cassandra
open Cassandra.Mapping
open System
type Ranking =
{ Id : Guid
Alias : string
Kills : int
Deaths : int }
type Player = {Id: Guid; Alias: string; Dob: DateTime; FullName: string}
type CassyMappings() =
inherit Cassandra.Mapping.Mappings()
do
base.For<Player>()
.TableName("players")
.PartitionKey("player_id")
.Column(fun(x:Player)-> x.Id)
.Column(fun(x:Player)-> x.Alias)
.Column(fun(x:Player)-> x.FullName)
.Column(fun(x:Player)-> x.Dob) |> ignore
base.For<Ranking>()
.TableName("rankings")
.PartitionKey("player_id")
.Column(fun (x : Ranking) -> x.Id, fun(x:ColumnMap)->x.WithName("player_id"))
.Column(fun (x : Ranking) -> x.Alias)
.Column(fun (x : Ranking) -> x.Kills)
.Column(fun (x : Ranking) -> x.Deaths) |> ignore
MappingConfiguration.Global.Define<CassyMappings>()
printfn "Works fine!"
https://dotnetfiddle.net/8IiYhg
here is a link to the C# http://www.datastax.com/dev/blog/csharp-driver-cassandra-new-mapper-linq-improvements
You're missing parentheses.
The expression on line 26 is getting compiled as:
.Column(fun (x : Ranking) -> (x.Id, fun(x:ColumnMap)->x.WithName("player_id")) )
That is, as an expression that takes a Ranking as parameter and produces a tuple of int and a function as result. Just to be more clear, here's a more expanded version of the same code:
.Column(
fun (x : Ranking) ->
let id = x.Id
let f = fun (x:ColumnMap)->x.WithName("player_id")
id, f )
The result of such expression is a Tuple<_,_>, and CQL interpreter expects a plain property access instead, and this is exactly what it tells you in the error message.
But what you really (apparently) intended to do was to call a different overload of .Column, which takes two parameters, one expression and one function. To prevent the compiler from considering the second function a part of the previous function's body, you just need to add parentheses around both functions:
.Column(
(fun (x : Ranking) -> x.Id),
(fun (x:ColumnMap)->x.WithName("player_id")) )
Or to put it on one line:
.Column( (fun (x : Ranking) -> x.Id), (fun (x:ColumnMap)->x.WithName("player_id")) )

Problems with instance arguments in Agda

I'm trying to follow the code for McBride's How to Keep Your Neighbours in Order, and can't understand why Agda (I'm using Agda 2.4.2.2) gives the following error message:
Instance search can only be used to find elements in a named type
when checking that the expression t has type .T
for function _:-_ . The code is given bellow
data Zero : Set where
record One : Set where
constructor <>
data Two : Set where tt ff : Two
So : Two -> Set
So tt = One
So ff = Zero
record <<_>> (P : Set) : Set where
constructor !
field
{{ prf }} : P
_=>_ : Set -> Set -> Set
P => T = {{ p : P }} -> T
infixr 3 _=>_
-- problem HERE!
_:-_ : forall {P T} -> << P >> -> (P => T) -> T
! :- t = t
Any help is highly appreciated.
There was a recent email by Nils Anders Danielsson at the agda-dev mailing list precisely about this. I cannot find it online, so here is a quote:
Conor uses lots of instance arguments in "How to Keep Your Neighbours
in Order". However, his code was written using the old variant of the
instance arguments, and fails to check now. I managed to make the code
work again using some small tweaks, and wonder if we could get away
with even less:
I replaced
record One : Set where constructor it
with
record One : Set where
instance
constructor it.
This seems fine with me.
I replaced
_:-_ : forall {P T} -> <P P P> -> (P => T) -> T
! :- t = t
with
_:-_ : forall {P T} -> <P P P> -> (P => T) -> T
! {{prf = p}} :- t = t {{p = p}},
because "Instance search can only be used to find elements in a
named type". Similarly, in two cases I replaced a module parameter
(L : REL P)
with
(L' : REL P) (let L = Named L'),
where Named is a named type family:
data Named {P : Set} (A : REL P) : REL P where
named : forall {x} -> A x -> Named A x

Resources