slamdata / purescript-echarts

Purescript bindings for Baidu's Echarts library
36 stars 16 forks source link

maintaining, splitting, remaking #9

Closed cryogenian closed 8 years ago

cryogenian commented 9 years ago

Here is my suggestion for purescript-echarts

module ECharts where

foreign import data SeriesImpl :: *

instance encodeSeriesImpl :: EncodeJson SeriesImpl where
  encodeJson = justCallJSONstringify

instance decodeSeriesImpl :: DecodeJson SeriesImpl where
  decodeJson = id

runSeries :: Series Unit -> SeriesImpl
runSeries = foreignCodeHere

addSeries :: Series Unit -> Option Unit
addSeries = ...

items :: forall q f d. (OptionLike q, Collection f, DataLike d) =>
         f d -> q Unit
items = ...

instance dataLikeInt :: DataLike Int
instance dataLikeNumber :: DataLike Number

instance foldableCollection :: (Foldable f) => Collection f
instance itemsCollection :: Collection (Items Unit)

radius :: forall f. (Radius r) => Option Unit
radius = ...

option :: Option Unit
option = do
  tooltip triggerItem
  legend do
    vertical
    left
    items ["foo", "bar", "baz"]
  addSeries do
    pie
    itemStyle do
      normal do
        label $ showLabelLine false
    radius do
      start $ percent 30
      end $ percent 40
    center $ Tuple (Percent 50) (Percent 50)
    startAngle 30
    items do
      pure 12
      pure 23
      styled 13 do
        normal do
          color "#444"

-- compare with

option = Option $ optionDefault { tooltip = Just $ Tooltip $
                                            tooltipDefault {trigger = Just TriggerItem}
                                , series = Just $ Just <$> series
                                , legend = Just $ legeend
                                }
  where
  legend =
    Legend legendDefault { "data" = Just $ legendItemDefault <$> ["foo", "bar", "baz"]
                         , orient = Just Vertical
                         , x = Just XLeft
                         }

  series =
    PieSeries {
      common: universalSeriesDefault {
         emphasis: Nothing,
         normal: Just $ IStyle $ istyleDefault {
           label = Just $ ItemLabel $ itemLabelDefault {show = Just false} 
                                               }
                                     },
      pieSeries: pieSeriesDefault {
        startAngle = Just 30,
        center = Just $ Tuple (Percent 50) (Percent 50),
        radius = Just $ Rs (Percent 30) (Percent 40),
        items = Just [ Just $ simpleData 12,
                       Just $ simpleData 23,
                       Just $ dat 13 { itemStyle = { emphasis: Nothing,
                                                     normal: {color: "#444"}
                                                   }
                                     }
                 }

              }

The only bad thing I see in interpreters that one can write

legend = do 
  vertical
  horizontal

Probably it can be solved by phantom types, but I'm not sure.

What do you think about it?

garyb commented 9 years ago

That looks like a nice approach to me, I do worry about whether the typechecker is good enough to infer some of the constraint-heavy stuff without having to use a lot of explicit types. Have you tried mocking up any of this to see how it behaves?

cryogenian commented 9 years ago

No, I haven't. I'm going to make a sketch this night.

jdegoes commented 9 years ago

Probably it can be solved by phantom types, but I'm not sure.

Phantom types cannot be used in conjunction with do-notation.

What do you think about it?

Generally, I like the idea of encoding the charting library as a series of (Free) DSLs. This would play nicely with the new Halogen components and allow you to keep things purer for longer.

However, I do wonder how much time will be required for the project. Ultimately I think we need to build our own charting library, not based on echarts, which limits the amount of work we should spend on the echarts integration. That said, if it's not that much time, I think the benefits could be worth it.

cryogenian commented 9 years ago

:+1: for charting library.

I've made a sketch with newtypes wrapping Writer (List Command). https://gist.github.com/cryogenian/7a3e874f4df67265bdad

jdegoes commented 9 years ago

I've made a sketch with newtypes wrapping Writer (List Command).

Looks nice and simple. Now the million dollar question: how long to implement this approach? :smile:

cryogenian commented 9 years ago

I think it will take 2-3 days to implement features that we currently use in slamdata, and maybe a 7-10 days to fully implement lib (I mean with tests, examples etc).

cryogenian commented 8 years ago

After spending some time on Writer approach I found that it could be implementing with only one interpreter, and there is no need of typeclasses because we have rows in purescript :smile:

So, my current idea is

This way there is no need to make something like ItemStyleable, building option is much more convenient and composable, it has (in my opinion) the same levevl of type safety as records.

Drawbacks

module ECharts.Monad where

import Prelude

import Control.Monad.Writer (Writer, execWriter)
import Control.Monad.Writer.Class (tell)

import Data.Array as Arr
import Data.Foreign (Foreign, toForeign)
import Data.Tuple (Tuple(..), uncurry)
import Data.Foldable as F

import ECharts.Types as T
import ECharts.Internal (unsafeSetField, emptyObject)

import Unsafe.Coerce (unsafeCoerce)

foreign import data I ∷ !

newtype DSL (i ∷ # !) a = DSL (Writer (Array (Tuple String Foreign)) a)
unDSL ∷ ∀ i a. DSL i a → Writer (Array (Tuple String Foreign)) a
unDSL (DSL cs) = cs

instance functorDSL ∷ Functor (DSL i) where
  map f (DSL o) = DSL $ map f o

instance applyDSL ∷ Apply (DSL i) where
  apply (DSL f) (DSL o) = DSL $ apply f o

instance applicativeDSL ∷ Applicative (DSL i) where
  pure = DSL <<< pure

instance bindDSL ∷ Bind (DSL i) where
  bind (DSL o) f = DSL $ o >>= unDSL <<< f

instance monadDSL ∷ Monad (DSL i)

set ∷ ∀ i. String → Foreign → DSL i Unit
set k v = DSL $ tell $ Arr.singleton $ Tuple k v

tooltipF ∷ ∀ i. T.Tooltip → DSL (tooltip ∷ I|i) Unit
tooltipF t = set "tooltip" $ T.unTooltip t

grid ∷ ∀ i. T.Grid → DSL (grid ∷ I|i) Unit
grid g = set "grid" $ T.unGrid g

boo ∷ ∀ i. Boolean → DSL (boo ∷ I|i) Unit
boo b = set "boo" $ toForeign b

shown ∷ ∀ i. Boolean → DSL (show ∷ I|i) Unit
shown b = set "show" $ toForeign b

shownContent ∷ ∀ i. Boolean → DSL (showContent ∷ I|i) Unit
shownContent b = set "showContent" $ toForeign b

trigger ∷ ∀ i. T.TooltipTrigger → DSL (trigger ∷ I|i) Unit
trigger t = set "trigger" $ toForeign $ T.printTooltipTrigger t

type TooltipI =
  ( show ∷ I
  , showContent ∷ I
  , trigger ∷ I
  )

type OptionI =
  ( tooltip ∷ I
  , grid ∷ I
  , legend ∷ I
  , xAxis ∷ I
  , yAxis ∷ I
  , color ∷ I
  , series ∷ I
  )

tooltip
  ∷ ∀ i
  . DSL TooltipI Unit
  → DSL (tooltip ∷ I|i) Unit
tooltip = tooltipF <<< buildTooltip

buildTooltip
  ∷ DSL TooltipI Unit
  → T.Tooltip
buildTooltip (DSL cs) =
  T.Tooltip $ F.foldr foldFn (emptyObject unit) $ execWriter cs

buildOption
  ∷ DSL OptionI Unit
  → T.Option
buildOption (DSL cs) =
  T.Option $ F.foldr foldFn (emptyObject unit) $ execWriter cs

foldFn ∷ Tuple String Foreign → Foreign → Foreign
foldFn opt obj = uncurry (unsafeSetField obj) opt

tst ∷ T.Option
tst = buildOption do
  tooltip do
    shown false
  grid $ unsafeCoerce unit
natefaubion commented 8 years ago

Is there a reason to invoke all the Writer stuff other than syntactic ergonomics? You are effectively only using tell, so why can't we just build the Array ourselves? This is the same trick we do for the indexed HTML DSL in Halogen.

cryogenian commented 8 years ago

I think the only reason why I'd prefer Writer here is that using something like

tooltip do 
  shown false 
  shown true 

looks like sequence of action.

And here it looks like we create datatype instance that has two things inside.

tooltip 
  [ shown false 
  , shown true
  ]
cryogenian commented 8 years ago

see monad branch