2015-01-11 12 views
7

Giả sử tôi có hồ sơ tài trong mã PureScript của tôi với các loại sau đây:Tạo hồ sơ PureScript từ không phù hợp các đối tượng JavaScript

{ id  :: Number 
, username :: String 
, email  :: Maybe String 
, isActive :: Boolean 
} 

Một CommonJS mô-đun có nguồn gốc từ mã PureScript. Các hàm liên quan đến người dùng được xuất sẽ được gọi từ mã JavaScript bên ngoài.

Trong đoạn mã JavaScript, một "người sử dụng" có thể được biểu diễn như:

var alice = {id: 123, username: 'alice', email: '[email protected]', isActive: true}; 

email có thể null:

var alice = {id: 123, username: 'alice', email: null, isActive: true}; 

email có thể được bỏ qua:

var alice = {id: 123, username: 'alice', isActive: true}; 

isActive có thể được bỏ qua, trong trường hợp đó là sumed true: đôi khi

var alice = {id: 123, username: 'alice'}; 

id là tiếc một chuỗi số:

var alice = {id: '123', username: 'alice'}; 

Năm đại diện JavaScript trên là tương đương và nên sản xuất hồ sơ PureScript tương đương.

Tôi làm cách nào để viết một hàm lấy đối tượng JavaScript và trả về bản ghi Người dùng? Nó sẽ sử dụng giá trị mặc định cho trường tùy chọn rỗng/bỏ qua, buộc một chuỗi id vào một số và ném nếu trường bắt buộc bị thiếu hoặc nếu giá trị sai loại.

Hai cách tiếp cận tôi có thể thấy là sử dụng FFI trong mô-đun PureScript hoặc để xác định hàm chuyển đổi trong mã JavaScript bên ngoài. Loại sau có vẻ như lông:

function convert(user) { 
    var rec = {}; 
    if (user.email == null) { 
    rec.email = PS.Data_Maybe.Nothing.value; 
    } else if (typeof user.email == 'string') { 
    rec.email = PS.Data_Maybe.Just.create(user.email); 
    } else { 
    throw new TypeError('"email" must be a string or null'); 
    } 
    // ... 
} 

Tôi không chắc chắn về cách thức phiên bản FFI hoạt động. Tôi chưa làm việc với các hiệu ứng.

Tôi rất tiếc vì câu hỏi này không rõ ràng. Tôi chưa có đủ hiểu biết để biết chính xác những gì tôi muốn biết.

Trả lời

7

Tôi đã kết hợp một giải pháp. Tôi chắc chắn nhiều thứ có thể được cải thiện, chẳng hạn như thay đổi loại toUser thành Json -> Either String User và bảo tồn thông tin lỗi. Vui lòng để lại nhận xét nếu bạn có thể thấy bất kỳ cách nào mà mã này có thể được cải thiện. :)

Giải pháp này sử dụng PureScript-Argonaut ngoài một vài mô-đun cốt lõi.

module Main 
    (User() 
    , toEmail 
    , toId 
    , toIsActive 
    , toUser 
    , toUsername 
) where 

import Control.Alt ((<|>)) 
import Data.Argonaut ((.?), toObject) 
import Data.Argonaut.Core (JNumber(), JObject(), Json()) 
import Data.Either (Either(..), either) 
import Data.Maybe (Maybe(..)) 
import Global (isNaN, readFloat) 

type User = { id :: Number 
      , username :: String 
      , email :: Maybe String 
      , isActive :: Boolean 
      } 

hush :: forall a b. Either a b -> Maybe b 
hush = either (const Nothing) Just 

toId :: JObject -> Maybe Number 
toId obj = fromNumber <|> fromString 
    where 
    fromNumber = (hush $ obj .? "id") 
    fromString = (hush $ obj .? "id") >>= \s -> 
     let id = readFloat s in if isNaN id then Nothing else Just id 

toUsername :: JObject -> Maybe String 
toUsername obj = hush $ obj .? "username" 

toEmail :: JObject -> Maybe String 
toEmail obj = hush $ obj .? "email" 

toIsActive :: JObject -> Maybe Boolean 
toIsActive obj = (hush $ obj .? "isActive") <|> Just true 

toUser :: Json -> Maybe User 
toUser json = do 
    obj <- toObject json 
    id <- toId obj 
    username <- toUsername obj 
    isActive <- toIsActive obj 
    return { id: id 
     , username: username 
     , email: toEmail obj 
     , isActive: isActive 
     } 

Cập nhật: tôi đã thực hiện các cải tiến cho các mã trên dựa trên một gist từ Bến Kolera.

3

Bạn đã xem purescript-foreign (https://github.com/purescript/purescript-foreign)? Tôi nghĩ đó là những gì bạn đang tìm kiếm ở đây.

+0

[ví dụ/Objects.purs] (https://github.com/purescript/purescript-foreign/blob/v0.3.0/examples/Objects.purs) có vẻ gần gũi nhất với những gì tôi đang cố gắng làm. Làm thế nào tôi có thể sửa đổi ví dụ đó để cho phép 'x' là một số hoặc một chuỗi số? – davidchambers

+2

Một cách là tạo một loại như 'dữ liệu SoN = S String | N Number' và sau đó viết một 'dụ IsForeign' cho loại' SoN' sử dụng <|> 'điều hành' để kết hợp hai lựa chọn: 'đọc f = S <$> ReadString f <|> N <$> readNumber f' –

1

Chỉ cần một chút ffi hơn

module User where 

import Data.Maybe 
import Data.Function 

foreign import data UserExternal :: * 

type User = 
    { 
    id :: Number, 
    username :: String, 
    email :: Maybe String, 
    isActive :: Boolean 
    } 

type MbUser = 
    { 
    id :: Maybe Number, 
    username :: Maybe String, 
    email :: Maybe String, 
    isActive :: Maybe Boolean 
    } 

foreign import toMbUserImpl """ 
function toMbUserImpl(nothing, just, user) { 
    var result = {}, 
     properties = ['username', 'email', 'isActive']; 

    var i, prop; 
    for (i = 0; i < properties.length; i++) { 
    prop = properties[i]; 
    if (user.hasOwnProperty(prop)) { 
     result[prop] = just(user[prop]); 
    } else { 
     result[prop] = nothing; 
    } 
    } 
    if (!user.hasOwnProperty('id') || isNaN(parseInt(user.id))) { 
    result.id = nothing; 
    } else { 
    result.id = just(user.id); 
    } 
    return result; 
} 
""" :: forall a. Fn3 (Maybe a) (a -> Maybe a) UserExternal MbUser 

toMbUser :: UserExternal -> MbUser 
toMbUser ext = runFn3 toMbUserImpl Nothing Just ext 

defaultId = 0 
defaultName = "anonymous" 
defaultActive = false 

userFromMbUser :: MbUser -> User 
userFromMbUser mbUser = 
    { 
    id: fromMaybe defaultId mbUser.id, 
    username: fromMaybe defaultName mbUser.username, 
    email: mbUser.email, 
    isActive: fromMaybe defaultActive mbUser.isActive 
    } 

userFromExternal :: UserExternal -> User 
userFromExternal ext = userFromMbUser $ toMbUser ext 
+0

Thật tốt khi xem phiên bản FFI thuần túy để so sánh. – davidchambers

0

Như gb. đã viết, đó là chính xác những gì các loại dữ liệu Foreign được xây dựng cho. Off đỉnh đầu của tôi:

convert :: Foreign -> F User 
convert f = do 
    id <- f ! "id" >>= readNumber 
    name <- f ! "name" >>= readString 
    email <- (f ! "email" >>= readNull >>= traverse readString) <|> pure Nothing 
    isActive <- (f ! "isActive" >>= readBoolean) <|> pure true 
    return { id, name, email, isActive } 
Các vấn đề liên quan