2015-01-11 9 views
7

が、私は次のタイプと私のPureScriptコード内のユーザーレコードを持っていると仮定オブジェクト。エクスポートされたユーザー関連の関数は、外部のJavaScriptコードから呼び出されます。作成PureScriptレコードが

JavaScriptコードでは、 "ユーザ" は、のように表すことができる。

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

emailnullであってもよい。

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

emailを省略してもよい。

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

isActive省略されてもよく、その場合にはtrueをsumed:

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

id残念ながら、時には数値文字列である:

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

上記5つのJavaScript表現は同等であり、同等のPureScriptレコードを生成する必要があります。

JavaScriptオブジェクトを取得してユーザーレコードを返す関数を作成するにはどうすればよいですか? null /省略されたオプションフィールドのデフォルト値を使用し、文字列idを数値に強制変換し、必要なフィールドがない場合や値が間違ったタイプの場合はスローします。

私が見ることができる2つのアプローチは、PureScriptモジュールでFFIを使用するか、外部JavaScriptコードで変換関数を定義することです。後者は毛深いようです:

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'); 
    } 
    // ... 
} 

FFIのバージョンがどのように機能するかわかりません。私はまだ効果を持っていません。

この問題はあまり明確ではありません。私はそれが私が知りたいことが何であるかを正確に知るための十分な理解をまだ持っていません。

答えて

7

私は解決策をまとめました。私は、toUserのタイプをJson -> Either String Userに変更し、エラー情報を保存するなど、多くを改善できると確信しています。このコードを改善する方法があればコメントを残してください。 :)

このソリューションは、いくつかのコアモジュールに加えてPureScript-Argonautを使用します。

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 
     } 

アップデート:私は上記のベンKoleraからgistに基づいてコードに改良を加えました。ギガバイトとして

3

purescript-foreignhttps://github.com/purescript/purescript-foreign)を見ましたか?私はあなたがここで探しているものだと思います。

+0

[例/ Objects.purs](https://github.com/purescript/purescript-foreign/blob/v0.3.0/examples/Objects.purs)に最も近いと思われます私がやろうとしていること。どのようにしてこの例題を修正して、 'x'を数字や数字の文字列にすることができますか? – davidchambers

+2

1つの方法は、 'data SoN = S String |のようなタイプを作成することです。その後、N Number' と二つの選択肢を組み合わせること '<|>'演算子を使用して 'SoN'タイプのために' IsForeign'インスタンスを書く: が '<|> N <$> readNumberが –

1

ただ、もう少しFFI

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

をF' fは、それが良いことだ= S <$> readString fを読みます比較のために純粋なFFIバージョンを参照してください。 – davidchambers

0

。これはまさにForeignのデータ型のために作られたものです。私の頭の上から:

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 } 
関連する問題