buildQueryString 函数的纯脚本类型

Purescript types for buildQueryString function

我是 Purescript 的新手,我正在尝试编写一个函数 可以获取任何记录值并迭代字段和值并构建 查询字符串。

我在想:

buildQueryString :: forall a. PropertyTraversible r => r -> String

我想这样使用:

buildQueryString {name: "joe", age: 10}      -- returns: "name=joe&age=10"

有没有办法用现有的习语在 Purescript 中编写类似的东西,或者我是否必须为此创建自己的自定义类型 Class?

这对 purescript-generics 是可能的,但它只适用于名义类型,不适用于 任何记录。但它节省了你的样板,因为你可以只派生 Generic 的实例,所以它可以与任何 datanewtype 一起工作而不用进一步修改。

缺点是,你必须对类型做一些假设:比如它只包含一条记录并且该记录不包含数组或其他记录。

这是一个如何运作的 hacky 演示:

data Person = Person 
            { name   :: String
            , age    :: Int
            }

derive instance genericPerson :: Generic Person

joe = Person { name: "joe", age: 10 }

build :: GenericSpine -> String
build (SRecord arr) = intercalate "&" (map (\x -> x.recLabel <> "=" <> build (x.recValue unit)) arr)
build (SProd _ arr) = fromMaybe "TODO" $ map (\f -> build (f unit)) (head arr)
build (SString s)   = s
build (SInt    i)   = show i
build _             = "TODO"

test = build (toSpine joe)

purescript-generics-rep 较新,因此可能有更好的解决方案,甚至可能在任何记录上。我还没有尝试过。

我确定它可以更短,但这是我基于 purescript-generic-rep (inspired by genericShow) 的实现。此解决方案使用类型类 - 它似乎是 generic-rep:

的标准方法
module Main where

import Prelude

import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Data.Foldable (intercalate)
import Data.Generic.Rep (class Generic, Constructor(..), Field(..), Product(..), Rec(..), from)
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)

class EncodeValue a where
  encodeValue ∷ a → String

instance encodeValueString ∷ EncodeValue String where
  encodeValue = id

instance encodeValueInt ∷ EncodeValue Int where
  encodeValue = show

class EncodeFields a where
  encodeFields :: a -> Array String

instance encodeFieldsProduct
  ∷ (EncodeFields a, EncodeFields b)
  ⇒ EncodeFields (Product a b) where

  encodeFields (Product a b) = encodeFields a <> encodeFields b

instance encodeFieldsField
  ∷ (EncodeValue a, IsSymbol name)
  ⇒ EncodeFields (Field name a) where

  encodeFields (Field a) =
    [reflectSymbol (SProxy :: SProxy name) <> "=" <> encodeValue a]

buildQueryString
  ∷ ∀ a l n.
    Generic n (Constructor l (Rec a))
  ⇒ (EncodeFields a)
  ⇒ n
  → String
buildQueryString n =
  build <<< from $ n
 where
  build (Constructor (Rec fields)) = intercalate "&" <<< encodeFields $ fields

newtype Person =
  Person
    { name   ∷ String
    , age    ∷ Int
    }
derive instance genericPerson ∷ Generic Person _

joe ∷ Person
joe = Person { name: "joe", age: 10 }

main :: forall e. Eff (console :: CONSOLE | e) Unit
main = do
  log <<< buildQueryString $ joe

buildQueryString 期望具有包含记录(可能只是 newtype)的单个构造函数的类型的值,因为不可能为 "unwrapped" 派生 Generic 实例 Record类型。

如果您还想处理 Array 值等,那么 encodeValue 可能 return 类型 Array String 的值。