【问题标题】:Purescript types for buildQueryString functionbuildQueryString 函数的纯脚本类型
【发布时间】:2017-06-26 10:56:30
【问题描述】:

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

我在想这样的事情:

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

我想这样使用:

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

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

【问题讨论】:

  • 不确定如何对任何记录执行此操作。听起来像是严肃的元编程。如果您想对记录周围的新类型执行此操作,您可以使用 purescript-generics、派生 Generic、调用 toSpine 并根据需要转换 GenericSpine。也许这是一个值得探索的想法。另一种可能性是只使用 FFI。
  • @stholzm:发布问题后,我确实使用了 FFI,并设法通过 FFI 实现了这一点。我将研究 purescript-generics 看看它是否也能工作。不过,我更喜欢不使用 FFI 的解决方案。
  • 啊,我以为你想让buildQueryString 处理任何记录,没有名义类型。不可能为记录 AFAIK 派生实例,因此这将是乏味的。另外,我不确定 purescript-generics 或 purescript-generics-rep 是否是当今的首选库。我只知道纯脚本泛型是可能的。
  • @stholzm:我确实希望它适用于任何记录,但正如您所说,如果我想使用 purescript-generics 之类的东西,那么可以支持的记录有一些限制。一旦我评估了所有可能性,我就会知道限制是什么以及我是否可以忍受它们:)
  • 我今天看了 purescript-generics-rep ,它与我的答案基本相同。但是自定义类型类也不适用于任何记录。所以我认为 FFI 是一种有效的方法。不过,我很想看到另一种解决方案!

标签: purescript


【解决方案1】:

我确信它可以更短,但这是我基于purescript-generic-rep 的实现(灵感来自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),因为不可能为“未包装”Record 类型派生 Generic 实例。

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

【讨论】:

    【解决方案2】:

    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 较新,因此可能有更好的解决方案,甚至可能在任何记录上。我还没有尝试过(还)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多