• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Elixir - Hey, two great tastes that go great together!

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 

  这是Elixir的作者 José Valim 参与的一次技术访谈,很有料,我们可以了解Elixir的一些设计初衷,目标等等.

  原文在: http://rubyrogues.com/114-rr-elixir-with-jose-valim/

  Podcast 下载地址:  http://traffic.libsyn.com/rubyrogues/RR114Elixir.mp3

 

     José Valim 谈到了 ‘Seven Languages in Seven Weeks’ 对他的影响,原文是听记的,应该不是做技术的,有些词明显拼写错误,我修改了一下:

JOSE:  Yes, you should definitely do it. I was reading the book and it’s very, very good. It starts with Ruby, and it goes into Io, and all the languages. Then it goes into Prolog, Erlang, and then you have Scala, Clojure, and Haskell. There’s a lot of focus overall on concurrency in the book. It starts with Ruby to tell a little bit about Triads but when it goes into Io, it already starts to talk a little bit about the actual model. And then, you’re going through a little Erlang. Clojure has software transactional memory and then there’s the Haskell stuff where they do crazy stuff there.

Then I read this book and I said the thing I liked the most was not any of the languages in particular, it was the language of the virtual machine and how everything worked, and that was when I fell in love. I was like, “This thing is great. I want to use this virtual machine. I want to use those things they are describing here, those concurrency mechanisms to build the software I have tried everyday.

And that’s when I started to go deeper into virtual machine and study it more, and the way I started writing more link code. The way I like to describe it is that I loved everything I saw but I hated the things I didn’t see. There were things now in the language, in the virtual machine, in the Erlang language that I really miss like having a good construct for doing polymorphism, the ability to meta-program. And that’s how Elixir came to life. And yeah, that’s it.

I know there are people doing really methodical client stuff in Erlang but I’m not one of them. Even after some time, when I get to, let’s say I’m able to view our client’s work with Elixir, I’m very web-focused at it too, right? It will be just a way to deploy in a very powerful web server. And if it has like 50 cores, it’s just going to use the 50 cores, it’s no sweep and the performance is going to be great.

 

取悦开发者?

 Ruby的设计目标之一就是要让开发者爽到,相比之下,Erlang很少取悦开发者,甚至近两年才在报错的时候显示错误代码所在的行号;下面的对话中,Jose表示这只是无心插柳的结果,他关注的目标是开发效率,可扩展性,和兼容性.要提高开发效率,语言层面提供了Macro,以及各种周边工具,效率高了好心情也就随之而来.

KATRINA:  One of the things that anytime I read or hear anything about Elixir, I’m always struck by how optimized it is for happiness. It seems like all of the things that it allows you to do is just making the developer happier. Is that on purpose?

DAVID:  Say ‘yes’.

[Laughter]

JOSE:
  It’s not a direct goal I have in mind because happiness is kind of, I can measure my happiness but it’s kind of hard to see how that’s going to affect other people’s happiness. If people have this impression in general, I’m really, really glad. And the goals we have, and maybe that’s why it affects the happiness is we officially say the goals of the language when I give a talk. I talk about productivity, I talk about extensibility and I talk about compatibility, which is compatibility with the Erlang Virtual machine. So, those are the goals.

With productivity and that’s probably what make people happy because we want to give you two so you can write your code there and be predictive on your code. We want to not only give like language mechanisms like macros, we also want to give you good tools, right? Extensibility is to be able to, if that thing does not have your use case in mind, I want to extend it, I want it to support my use cases.

I think that if we can make people feel productive and they are not fighting against the language and they are not fighting against their own time like in a standard library, I think happiness is going to be a consequence of it.

 

从Erlang获得了什么?

 

JAMES:  Jose, I’ve got a question for you. You’ve said both in talking here and I was watching a video of you explaining Erlang this morning which was good. You said, “The Erlang Virtual Machine is our greatest asset,” and that you really designed Elixir to use that asset optimally. Maybe you could just tell us what is it the things about the Erlang Virtual Machine that you just really want? What makes you think that’s so valuable?

JOSE:  That’s a good question. Let me think. When I give a talk about Elixir and as I said, most of what I do with Elixir is thinking about, “In the future, I’m going to use it for a web application,” right? I want to develop end points. I want to be able to build APIs with it or build systems that are highly connected. There are many things. I will start the story from scratch.

The Erlang Virtual Machine, it was created by Ericsson. At that time, it was the 80′s when they started the virtual machine, they are not really thinking about running many parts because it was not a problem back then, right? But the use case they had, for example, to build telephone switches. So, a telephone switch is a machine somewhere that’s receiving a bunch of connections and it’s juggling all these connections at the same time.

From here, you’ve already got one thing. You need to be able to do a lot of — you need a switch to be able to handle as many connections at the same time. So, it needs to have this ability of handling stuff concurrently. They also wanted robustness because if you need to install a switch in the middle of the forest, you don’t want to go there every two weeks to give maintenance to it. When you have a call, you need a different switch to talk to each other because you need to route to different switches or maybe the person is already busy talking, the connection is already in another switch. So, they also have these distributed aspects. They have different nodes, they need to be able to talk to each other, different information. So they got all these things together and they made it the virtual machine.

The reason why I think Erlang came up is because it’s from the end of the 80′s and in I think 2004/2005, people started to use it a little more. And I think because people realized this thing that I’m describing, this telephone switch is actually what is happening on the web, right? You have a server there. It’s receiving a bunch of connections at the same time. It needs to handle all those connections. There are requests, there are responses. You need to talk to an internal API. So, it has really a bunch of features. Then I said, “Wait, I feel that if I build software here, I will be able to build great software. I’ll be able to build robust software. I’ll be able to build distributed software. I’ll be able to build concurrent software.” Those are all the things I saw in there and that’s where I’m going in the direction of. 

 

模式匹配与Web Routing

  Jose看到了Erlang的模式匹配在Web开发中的强大潜力,看他怎么说:

JAMES:  You mentioned there ‘routing’ just there, that you’re kind of thinking about routing, say for web applications and that was actually a prominent example in the video I watched this morning. It was kind of interesting. Can you talk about why you think that relates to the Erlang Virtual Machine?

JOSE:  That’s more kind of an indirect thing. That’s more of using the features of the language and the features provided by the Erlang Virtual Machine. One of the things in Elixir is that – and most of the time, when I say Elixir, you can also think of Erlang Virtual Machine. The main thing is that you do pattern matching. A function can have many clauses and then you can put guard, you can put specification on those clauses. What you can do is that you can check if a string starts with hello at the function clause. Then if it starts with hello, you can do something. And then if the string does not start with hello, you can have a fall back clause. You are going to do something different.

You can really do a lot of code like stuff. You can match a list, you can match a tuple, extract elements. So, it’s a really powerful construct. This is handled by the Erlang Virtual Machine. So, the virtual machine gets all of those patterns and they need to compile to the fastest way possible, they want to be optimized. They have been optimizing those patterns for 20 years already and it’s like super, super fast.

The thing about the routing like the routing in Rails is that it’s exactly the same stuff you are giving it a bunch of patterns which are URLs and you want to dispatch to an end point. You want to do some specific action where the URLs match. What people do when they are implementing routers is that they go with the most naive solution possible which is if I have a round of routes and I’m going to check those routes one by one until I find one that matches. What it can do, and that’s the simile to a pattern matching. Aaron Patterson, we can search the link later. What Aaron did, for example, in Rails is that he changed the Rails router, I think for Rails 3.2. And he used it as actually the pattern matching theory to compile the routes into a graph. So, you don’t need to match one by one to find one that matches. You go in the graph and then if you have a starting node, you already know where you can go and where you can’t go. You can very quickly say if there is a match or not. If routes, for example, they have the same prefix, you don’t need to match on those seven or eight different prefixes and all the same patterns. You just create a graph of the actions that you can go and cannot go.

What we did for Elixir is that since we have pattern match on the language, and we have this meta-programming ability, what we do is that we get our routes file and we compile this thing into those pattern matching that are highly optimized by the virtual machine and that makes it super, super, super fast. The implementation is 150 lines of code or 100 lines of code of a decent router that’s going to be one of the fastest routers you can think of writing. It’s really nice. It’s just the coincidence of having a feature in the language, having self-conducting virtual machine really knows how to optimize, and putting those things together.

JAMES:  That’s cool.

JOSH:  I like that because it’s the way that — it corresponds to how I like to do stuff in object-oriented languages where you want to get rid of conditionals and case statements and things like that and just use polymorphism and the virtual machine’s super optimized method dispatch mechanisms. You’re doing the same kind of thing by getting rid of those conditionals and case statements by using something highly optimized that’s built into the virtual machine. It’s great when you do that because it’s like you’re going with the grain and the language just helps you do that. Did that make sense?

JOSE:  Yes, that makes total sense.

 

好东西,拿来,拿来

  Elixir不仅借用了Ruby的语法,我们可以从Elixir中看到很多其它语言的影子,比如Lisp的宏,Python的Docstring:

AVDI:  So far, we’ve talked about mostly things that Elixir kind of shares in common with its Erlang base. I have a lot of questions, I’m a big language nerd. I have a lot of questions about your choices for Elixir specifically. First of all, it seems like a lot of the syntax is Ruby-inspired. There are obviously a few other inspirations in there, a little tiny bit of Python and stuff like that.

JAMES:  It’s docstrings.

AVDI:  What’s that?

JAMES:  Docstrings! He took the best part of Python.

[Laughter]

AVDI:  I was actually thinking about triple equals but yeah. Having done some functional programming, it felt kind of weird doing functional programming with a Ruby Style Syntax. I’m curious, is the Ruby Style Syntax basically just there for approachability for people coming from Ruby or Ruby-like languages? Or do you actually feel like that kind of syntax is a good fit for functional programming? It felt kind of strange to me.

JOSE:  Syntax is kind of hard to discuss. Obviously, since I’ve been doing it for five years of my life, I was doing a lot of Ruby. So, it naturally comes out of a lot of inspiration from there. One of the things that I think gives it a lot of Ruby feel, is that I have additional parenthesis. I think this is one of the single things that give it a lot of this Ruby feeling and probably the do/end blocks.

AVDI:  Right. Although there aren’t a lot of mandatory parentheses in Haskell, so that’s Haskell-ish as well.

JOSE:  Yeah, that’s true. I have a couple design rules coming up with the language and the current syntax. Yehuda also helped me in the very first versions which is basically what I wanted to have is that I knew that I wanted to have macros. The way I wanted to approach macros in Elixir, basically I wanted to have meta-programming, let me fix that. I wanted to have meta-programming. One of the ways to have meta-programming is macros so that’s what I’m going to look at. Then, you see the things that allow you to do macros and macros for them to be any kind of way same, you need to have a regular syntax. Having a regular syntax, if you go to the extreme opposite, you have Lisp, right? It’s extremely a regular syntax, Clojure gets a little bit away by a lot of [inaudible], add brackets, curly, and some special stuff, but is extremely a regular syntax.

AVDI:  That’s what makes meta-programming in Lisp so sane is that you can just look at it and see the AST. And so, it’s very easy to understand the transformations that you make when you’re meta-programming.

JOSE:  Exactly. So, I say Lisp is like what you see is what you get, right? What you’re looking at is what you’re getting. I wanted something mid-term, I didn’t want Lisp. We already have two other Lisp implementations in our virtual machine. I’m not particularly a Lisp fan. I find it very elegant but I don’t find it beautiful if it makes sense. So, I said, “Okay, I want to have something else. And then, I needed to have a regular syntax.” That’s why when you go to Elixir, everything has do/end blocks.

So, when you define a module, you need to put do/end because module is not a keyword. Defining a module is not a keyword. It’s like anything else in the language. Everything translates to a very common AST so the way we present operators in that state, even before we have the sign operator notation, it comes with everything else in the language. Our AST, for example [inaudible], the steps that you need to do mentally to go from one step to the other is very few. There are very few translations that you need to do. And that’s one of the first rules of the language.

Even so, when we got that, it was still very, very robust and then it was adding constructs little by little that was getting those common cases and making them easier to write. For example, the do/end, it’s kind of hard to say with words without showing the code. But the do/end in Elixir is the equivalent of putting parenthesis, of wrapping our stuff with parenthesis. It’s a convenience of say, a block of code because in particular, if you have parenthesis there, it would get very weird and clunky easily. So, we say, “Okay, let’s abstract this pattern.” Then we got a lot of stuff out of it and when we were abstracting, it often came, “Well, I am getting this. I want some of this pattern. Let’s look at [inaudible]. Ruby has do/end blocks. Let’s use do/end blocks then.” That’s one of the things we approached it.

I tried to keep the data structure syntax all from Erlang. I tried to also keep a lot from what we have in Erlang. So, tuples, they look the same in Erlang. Lists they look the same as in Erlang. Binaries they are extremely similar, they’re not exactly the same but they are very similar. So, there was also lots to do in Erlang for those data structures, those operators unless they don’t make sense because we have a couple of those in Erlang where they are not making any sense. For example, the less than equal operator in Erlang, is the equal sign first and then the less than sign, it’s the opposite. So, we said, “Okay, we’re not having this.”

Then we make the notation where we have [inaudible]. I’m obviously biased but because we have better matching in tuples. So, if you go to case expressions, if you get a case in Elixir with pattern matching or a receive, it feels functional to me. The only thing that doesn’t feel very functional to me and that’s probably where some of your feeling is coming are the named function definitions, even more because if we show a program line, it’s very common for it to have a bunch of inline definitions one close to another. They don’t look as great in Elixir, in my opinion. It’s not as good as I would get in Haskell, or CAML, or even in Erlang, like a one function line definition in Erlang for me is prettier than the one in Elixir. So, that may give a hint.

......

That was when I decided, “Let me stop whatever I’m doing here.” And if you go to the commit graph, you can see a period of inactivity. And that’s when I really decided to study other languages and how people were solving those problems that I wanted to solve but in functional programming languages. I wanted to have polymorphism. And I said, “Well apparently, Haskell, they’re using this class type thing.” Go has come up with this idea of interfaces, and Clojure has protocols. Then I say, “Well, this protocol thing looks great. The other languages, they are statically typed. Elixir is dynamically typed. Clojure feels closer to Elixir. So, I know I won’t implement protocols in Elixir now.”

Then when you really want to manage a state, we have solution for this in the Haskell community, for example. They have this thing called lenses and transversals that allows you to update deeply nested structures. What I’m getting at is that if you’re adding more semantics, you’re probably coming from the functional side than from anywhere else.

 

梳理统一基础类库

 

AVDI:  In other words, you might add some functional features that aren’t present in base Erlang, but probably not so much stuff from procedural or OO languages?

JOSE:  Yeah. We already do that. One of the things — if we start going through the differences between Elixir and Erlang, one of the things I already talked about metaprogramming is having protocol which is a way to achieve polymorphism. And we have, in general, a very complete standard library.

Now, I’m going to do a little bit of criticism of Erlang. In Erlang for example, you have a list type, you have an array type, you have a dictionary type and if you want to iterate a dictionary, if you want to iterate a list, if you want to iterate an array, if you want to iterate a set, they all have specific functions.

AVDI:  Right, that was exactly the kind of insanity that Rich Hickey was coming out against when he did the protocols in Clojure, right?

JOSE:  Yeah, exactly. It’s very hard and often, they use different names. There’s the fact that you need to know different APIs and there is all the semantic loading of having to know those different names and what they mean in those specific contexts. Then we have the enumerable protocol which is, you can give anything that you teach the language how to reduce, you’re going to be able to use those functions. Then we can keep the set of words the developer needs to know and work everyday, we keep them very small and that’s extremely important.

AVDI:  I guess that’s one of the bonuses of writing a new language is you get to start all over with a standard library and make it the same, make it consistent, I guess, is the big thing.

Speaking of your standard library and the Erlang libraries which are a little different, there’s always a fear when somebody’s getting into a new language that’s built on another language, that at some point, they’re going to start using it more and more, they might start using it in production. And then at some point, they’re going to run into a really thorny issue that requires them to know the base language to resolve it. I guess languages like Ruby, they handle that by being implemented in a language that at least, most people know a little bit of, like C. Is that something that, at this point, people are going to face with Elixir or do you feel like you can get quite a lot done without having to know any Erlang?

 

JOSE:  I feel you can get quite a lot done because — I was talking like, you cannot really change the semantics, I was talking. If you are in Elixir, you can stay in Elixir land and then if you need to step a little bit into Erlang, it’s not going to look that foreign anymore. Of course, the syntax is different but at that point, you’ve already absorbed most of the semantics. Even so, let’s suppose you want to use Elixir today, you want to replace your Ruby use for scripting and you want to use Elixir. You’re going to use Elixir and you’ll be fine and you probably won’t have to worry about Erlang and you won’t have occasions to worry about Erlang today.

So, the situation flow that you meet today is that if you want to build — Erlang shifts with this thing called OTP which is kind of the Erlang standard library. OTP has all the patterns that you need to build those distributed systems, those concrete systems we were talking about. And if you want to use those today, you need to call Erlang functions. This is a message that I’m sending since the beginning of Elixir, the Erlang Virtual Machine is our strongest asset and the Erlang OTP, the way they have been building those systems, it’s our asset too. Sometimes people say, “Wait, I think we should wrap this library.” And I would say, “No, we are not wrapping in libraries. You need to be able to learn how to use them.” But it’s just a matter of you opening an Erlang documentation page and then reading all the arguments and passing the arguments to it. But the names, here’s expecting a list, what they’re calling a list there is going to be a list in Elixir. The semantics, the types, they are all the same, so it doesn’t feel like a huge hit.

There is one thing which is a blocker for this error getting into 1.0, and it’s one of the Elixir second articles which is to improve the error messages. The error messages in Erlang, they are very bad, very, very bad. So, it’s one of the goals to have. If something fails, we want to give you a nice message of what failed and how it failed, and what you should do from there. Every time you work with Elixir, you call something and you get a weird message, you can open up a bug. And if we can improve it, we’re definitely going to improve it.

 

KATRINA:  Can I ask a question about that?

JOSE:  Sure.

KATRINA:  Is this something that you can do only for the Elixir standard library or can you also improve the error messages on top of Erlang when you’re delegating to the Erlang standard library?

JOSE:  We can improve many things on that side. It’s funny because there’s Basho, the company that runs Riak. On top of the Erlang Virtual Machine, they created Riak and they have our similar goals. I was talking to them and some of their goals are very similar like they want also to improve the error messages because sometimes people are starting the Riak node and then when they are starting it, they get bad match.

[Laughter]

AVDI:  That’s one of my notes is that when — I like that it’s a neat pattern when you say the tuple of ok and a value, matches against io.open or whatever the call is to open a file. And so, that’s only going to match when it returns a tuple that includes ok rather than error as its first element. That’s kind of a cool pattern. But then, the error that you get out of it is (MatchError) no match of right side of value {:error, :enont} which is a terrible way to say, “File not found.”

JOSE:  Yes, exactly. There’s actually a story about this. I was talking to a friend he was telling me that the first time he went to use Riak, he installed, did a lot of things, and then he tried to run the node and then he got exactly a bad match. He tried to run bad match, bad match, the error was exactly this message {:error, :enont}. And then he said, “Let me try to fix this.” Then he added a stack trace and until like two or three years ago, Erlang did not have line numbers in stack traces but he was trying to start Riak and was getting {:error, :enont} and then we have no lines of stack trace.

He was able to find the module, the function. And then, he figured out what was happening was that they were trying to open to create a log file but they didn’t have permission to write to that directory. What he was getting out of it was a bad match.

The way we were trying to improve this, these in particular we cannot improve it. It’s a bad match that’s coming from inside the Erlang Virtual Machine and we can format it nicely, but we can’t change what is happening. It is how the software is written. The way we tackle this particular issue in Elixir is that in Elixir, the Bang method, the Bang functions, they are for exceptions. Just so you know that function can raise an exception specifically and we followed those.

 

Elixir代码到Erlang代码

  这段要好好读哦,不仅仅是Elixir开发者,对Erlang开发者这一段讨论也是很有信息量的:

CHUCK:  I kind of want to jump in here for a minute because we’ve talked around this a little bit and I’m just not quite clear on it. Does Elixir transpile into Erlang or does it run on the Erlang Virtual Machine with its own syntax tree? Then the other question I have related to that is, can you call into existing Erlang libraries?

JOSE:  It compiles directly to Erlang byte code and you can call Erlang libraries with no conversion costs whatsoever. If you get two similar modules and you compile them from Elixir and from Erlang, the final byte code is going to be very, very similar.

CHUCK:  Does Erlang compile then to byte code as well?

JOSE:  Yeah. It’s what we call the .beam files, yes.

CHUCK:  Okay.

JOSH:  I got a little bit of a different direction to pursue here and that is the Erlang language is an implementation of the Actor Model. I started off calling it a functional language but really, it’s an object-oriented language. All of these actors are like objects but they can all operate concurrently rather than sequentially which is how the Smalltalk variety of object orientation went.

AVDI:  Can I just contribute a quick quote about that?

JOSH:  Yeah.

AVDI:  I found this the other day. Erlang’s Actor Model gives it the distinction of being the only language that claims not to support object-orientation while actually providing full support for it. Languages that claim to support object-orientation but don’t, are far more common.

[Laughter]

JOSH:  The history of that model was way back in the 70’s when Alan Kay and the Learning Research Group were inventing Smalltalk. They went and travelled around and they showed off Smalltalk and Carl Hewitt was like, “This is amazing! I’m going to do this actor stuff.” The early Smalltalks felt a lot more like JavaScript, I think, in the way that classes were built, et cetera. So, things aren’t as far apart as these people tend to believe these days.

But I’ve never gotten to work in an Actor Model language and I’m curious how it feels in terms of objects and encapsulation and objects all the way down, and everything’s an object. That’s the way we think about things in Ruby. But I’m curious how that feels when you’re working in Elixir. Does it feel like you’re inside an object or an actor versus outside? And how light weight is that mentally when you’re thinking about programming problems?

JOSE:  Well, I’m searching for a link that goes exactly into a little bit of this discussion about object-orientation in Erlang. I don’t think I’ll find it. I can search it later but it’s basically saying, the “Alan Kay” and the whole message passing stuff and they say, “Well, Erlang is actually the only one that’s [inaudible].” Then he goes and explains why people say that, but he says that the way you need to think, you should not come with the thinking that it’s going to work like the other things.

We have those actors in Elixir and you can create a bunch of them, but you don’t want to create really like we create objects in Ruby, for example. You don’t want to go crazy and create a bunch of those. I don’t think it feels similar because the mechanisms they operate is very different. You have an explicit pattern matching. So, when you send a message, the message that is sent is a data structure and then when you are receiving those messages, you are pattern matching those structures and acting on them. You need to do the recursion. So, you are specifically doing the recursion or as your actor, he exits, he gets out of its loop. It doesn’t feel like you are in an object-oriented language if we say that object-oriented language is what you’re doing everyday in Ruby or Python, or whatever. It maps differently.

But your mind is thinking differently also because it doesn’t map correctly. So, you’re not thinking about creating those objects all the time. You are thinking about having, you think, “I’m going to create a process.” The first reason why you want to create an actor in Elixir is because you want to encapsulate state because there is no way for it to have state otherwise. The only way for it to have state is to have those actors and sending messages to them for them to return part of the state or to change their state. It doesn’t come, “Well, I need to create this object because I need to model this thing.” It’s not that. It’s because I need this state or I need to have those things running concurrently and talking to each other. I’m going to a risk in saying less about structure and you think more about communication.

JOSH:  I think that’s pretty cool. When I say object-oriented programming, everything’s an object, really it’s all about messages in the Actor Model in Erlang. And it sounds like in Elixir, the focus is still on that, in the communication, as you said.

JAMES:  Almost message passing is required, right? Because of the way the actors work. The Erlang book from the Pragmatic Programmers has a pretty good quote about this trying to think about it in your mind. It’s basically like how humans interact. You can’t just make another human do something else. Instead, you have to explain to them what you want them to do. You have to communicate and pass those messages across and then that human will do their own thing and accomplish it in their own way. It’s kind of like that.

JOSE:  Yes.

JOSH:  So, are you saying we should treat software like people instead of treating people like software?


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Go语言学习笔记十一--Go语言数组的相关操作发布时间:2022-07-10
下一篇:
https://github.com/golang/crypto/blob/master/bcrypt/bcrypt.go发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap