Este artigo foi traduzido por máquina.

O programador Polyglot

Combinação E correspondência de idiomas

Ted Neward

Conteúdo

Programação polyglot
Polyglot na prática
O custo por trás as opções

Tempo antes da Web e cliente-servidor programação, era comum para um aplicativo inteiro ser gravados em um único idioma na parte superior de um única plataforma ou sistema. Considere, por exemplo, a plataforma de FoxPro muito difundida, um grampo de programação de aplicativo há anos. Ele fornecido um idioma de formato da interface de usuário e biblioteca, um acesso a dados e formato de armazenamento, além um conjunto de rotinas para oferecer suporte a biblioteca tradicionais, como matemática e estatísticas. Um idioma e uma plataforma empacotada em um ambiente único, FoxPro foi um exemplo clássico de como um programador pode aprender uma linguagem/plataforma e permanecem bem-sucedido e, mais importante, gainfully empregado.

Em seguida, veio especialização — novas linguagens e ferramentas, cada uma com uma finalidade específica e específica. Bancos de dados relacionais tornou-se a norma e SQL tornou-se a linguagem usada para acessá-los e modificá-los. Desenvolvimento do cliente GUI inicialmente deslocado para o procedimentos linguagens como C e Pascal e, com o advento do objeto-orientação fornecida C++ e Delphi. Idiomas, como Perl e Python tornou-se ferramentas poderosas para administrar um sistema operacional baseado em console, sistemas com mais freqüência baseados em UNIX, estender os recursos encontrados no shells de comandos, como os shells bash e Korn. Em seguida, veio da e com ele HTML, CSS e JavaScript ficou os idiomas de escolha para exibição de interface do usuário.

O mundo de linguagem de programação nunca viu um momento silencioso, mas nos últimos anos, um grande número de idiomas tem detalhados para a cena de programação, incluindo idiomas procedimentos como Ruby, o Windows PowerShell e Python e linguagens funcionais, tais como F # e Scala. Idiomas existentes estão obtendo novos recursos, como os dados consultar recursos Microsoft adicionou para C# com o LINQ e agora uma nova fase de desenvolvimento de linguagem de programação foi iniciada em que novas linguagens personalizadas, conhecidas como linguagens específicas de domínio (DSLs), são desenvolvidas para tarefas específicas ou aplicativos. Para obter um exemplo de uma DSL, consulte a coluna Estação de serviço a edição de inicialização do Visual Studio 2008 do MSDN Magazine (msdn.microsoft.com/magazine/cc164250).

Em todos os lugares em que você ativa, novos idiomas são popping backup. Enquanto os programadores multilíngües certamente predate a, o termo "polyglot programação" foi originada com do Neal Ford dezembro de 2006 blog lançar (memeagora.blogspot.com/2006/12/polyglot-programming.html), (não surpreendentemente) intitulado "Polyglot programação".

Programação polyglot

Tudo bem, para nós já estabelecido que não haja muita idiomas por aí e não existe é provavelmente um idioma especialmente adequado para cada problema, que você precisará do endereço. O desenvolvedor de .NET experientes precisa compreender como todas elas se combinam. Que será o foco desta coluna.

Considere um dos problemas mais comuns hoje em dia. Os desenvolvedores que está sendo solicitados a escala de seus programas — seus sites e serviços, em particular, a maior e maior número de usuários que nunca. Os clientes que está sendo têm acesso a suas próprias informações de conta diretamente. Isso significa que em que um aplicativo pode ter tinha seja dimensionado para algumas centenas de usuários (digamos, aumentar o número de usuários de funcionário do Centro de chamada durante uma spurt crescimento), agora o mesmo aplicativo deve dimensionado para potencialmente milhares, se não milhões de usuários.

Desenvolvedores trabalhando no site da Web devem gerenciar alcançar segurança do thread e bom desempenho. Simplesmente bloqueio cada método para serializar o acesso através de todo o sistema não é uma boa solução porque ele não serão redimensionados. Gerenciar corretamente simultaneidade, especialmente como o aplicativo se adapta, é não feat fácil; mantém os desenvolvedores ainda sênior até mais tarde durante a noite. Mesmo com o lançamento de novas ferramentas como PEX (research.microsoft.com/projects/pex) e CHESS (research.microsoft.com/projects/chess) para executar análise de código estático e executar testes permutações de unidade para descobrir erros multithreading, você ainda é necessários para controlar a concorrência você mesmo e, em um nível muito baixo, usando C# "bloqueio" instruções ou a concorrência-controle várias classes de System.Threading.

Você tem até o desafio? Como boas são suas habilidades de concorrência? Rápido, o que é a diferença entre System.Threading.Monitor, System.Threading.Mutex e System.Threading.Semaphore?

Aqui você deve começar ver o valor de uma preferência de idioma específico. Talvez esse tipo de código complexo seria mais simples gravar e manter se você escolher uma linguagem funcional como F #, cujos tendencies gerais para Imutabilidade e zero efeitos colaterais impedirá a necessidade de controle de simultaneidade explícita, ou um idioma específicos do domínio escrito Ruby, que foi projetado para ocultar os detalhes de concorrência dos desenvolvedores usá-lo.

Para fazer essa idéia mais concrete, imagine que um aplicativo da Web precisa fazer algumas operações síncronas tradicionalmente, como executar algumas E/s com base em arquivo (ou fazer um banco de dados ou serviço da Web chamada). Normalmente, a coisa mais fácil a fazer do código do C# será abrir o arquivo por meio de um tradicional usando instrução, ler o conteúdo, armazená-los em uma matriz de bytes, e feche o arquivo, de maneira semelhante a esta:

byte[] pixels = null
BinaryReader br = 
         new BinaryReader(new FileStream(filename, FileMode.Open)); pixels = br.ReadBytes(4096);

Ele pode ser simples, mas ele também horribly serializado. Nenhum processamento adicional pode ocorrer neste thread enquanto o arquivo de operação de E/s está ocorrendo. (Para operação de um arquivo simples E/s, isso é provavelmente não uma grande preocupação, pelo menos não até o número de obtém essas operações realmente grande, que pode ocorrer como o site tenta escala backup.) Seria melhor ler o arquivo citado usando as operações assíncronas disponíveis por meio do pool de segmentos CLR, como você vê na Figura 1 .

Figura 1 ler arquivo de forma assíncrona

delegate byte[] AsyncOpenMethod(string filename);
static byte[] AsyncOpen(string filename)
{
    byte[] pixels = null;
    using (BinaryReader br =
        new BinaryReader(new FileStream(filename, FileMode.Open)))
    {
        Pixels = br.ReadBytes(4096);
    }
}
static void AsyncOpenTheFile(string filename)
{
    byte[] pixels = null;
    AsyncOpenMethod aom = new AsyncOpenMethod(Class1.AsyncOpen);
    IAsyncResult iar = aom.BeginInvoke(filename, null, null);
    while (iar.IsCompleted == false)
    {
        // Do something?
    }
    pixels = aom.EndInvoke(iar);
}

Mas o código que era simples antes de código agora que apenas mãe de um desenvolvedor pode amor e claramente lida com muito mais do que simplesmente lendo um arquivo. Agora examine escrever uma rotina semelhante em F # aparência:

async {
use inStream = File.OpenRead(filename)
let! pixels = inStream.AsyncRead(4096)
}

Eu não quero mergulhar profundamente em como o código de F # faz isso, mas o permitem! expressão informa o compilador F # para gerar a expressão como uma expressão assíncrona e o método AsyncRead é aquela F # silenciosamente tacks para as classes derivadas System.IO.Stream padrão através de um recurso de linguagem F # chamado métodos de extensão. Ele lê o arquivo de forma assíncrona com eficiência e despeja os resultados para a matriz de bytes no lado esquerdo da expressão, tudo isso sem qualquer código adicional necessário do desenvolvedor.

Vamos inserir isso em um contexto de mundo real. Como parte de um ciclo de manutenção noturna, um serviço da Web deve fazer cópias de um número de arquivos para armazenamento mais fácil offline para fins de backup. O código de F # para executar esta cópia do arquivo, todos os completamente assincronamente, é semelhante a Figura 2 .

A Figura 2 Criar backup cópias

#light

open System.IO

let CopyFileAsync filename =
    async {
        use inStream = File.OpenRead(filename)
        let! pixels = inStream.AsyncRead(4096)
        use outStream = File.OpenWrite(filename + ".back")
        do! outStream.AsyncWrite(pixels)
    }

let tasks = [ for i in 1 .. 10 -> CopyFileAsync("data" + i.ToString()) ]
let taskResults = Async.Run (Async.Parallel tasks)

Aqui, tanto a leitura e gravação serão executadas assincronicamente, e toda a coleção das dez tarefas (uma para cada arquivo de dados) também será processada assincronamente. Quando você considera que isso levaria fazer em C#, que você em vez disso escreveria? (Oportunidade Coble vai em consideravelmente mais detalhes sobre F # e assíncronas programação em" Fácil assíncrono: criar aplicativos simultâneos da simples F # expressões"no de 2008 outubro emitir de MSDN Magazine .

Polyglot na prática

Na prática, interoperação entre F # e C# (ou qualquer outra linguagem do CLR) é relativamente simples, depois de "forma" do código (o idioma transforma no IL nível) em ambos os idiomas é bem compreendida. Superficialmente isso parece muito fácil, afinal de contas, como diferentes pode uma chamada de método ser entre os dois idiomas, considerando que o common language specification (CLS) determina muita problemas à; caso contrário complicados, como posicionamento de parâmetro, tipos básicos e ordenação de bytes?

Vamos colocar que para o teste. Levando alguns F simples # código para iniciar com, vamos compilá-lo e ativá-lo em uma DLL e use ILDasm (a linguagem intermediária disassembler ou Reflector, que ferramenta você achar mais confortável com) para examinar sua aparência. Em seguida, você pode se formar a expressões de F # mais complicadas, como o código de fluxo de trabalho assíncrono Coble instância apresentadas em seu artigo de outubro de 2008, que mencionei anteriormente.

Para começar com, se um código de F # simples

let x = 2

Se vamos supor que ele está instalado no padrão F # biblioteca projeto arquivo chamado Module1.fs, o IL em A Figura 3 (com algumas coisas comentadas para manter a listagem de IL breve) será gerado para ela.

Figura 3 O IL atrás Let x = 2

.assembly Library1
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.
  FSharpInterfaceDataVersionAttribute::.ctor(int32, int32,
  int32) = ( 01 00 01 00 00 00 09 00 00 00 06 00 00 00 00 00 ) 

  // ...
}

.class public abstract auto ansi sealed beforefieldinit Module1
       extends [mscorlib]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.
  CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.
  FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) 

  .method public static int32  get_x() cil managed
  {
    // Code size       6 (0x6)
    .maxstack  4
    IL_0000:  ldsfld     int32 '<StartupCode$Library1>'.$Module1::x@3
    IL_0005:  ret
  } // end of method Module1::get_x

  .method private specialname rtspecialname static 
          void  .cctor() cil managed
  {
    // Code size       13 (0xd)
    .maxstack  3
    IL_0000:  ldc.i4.0
    IL_0001:  stsfld     native int '<StartupCode$Library1>'.$Module1::_init
    IL_0006:  ldsfld     native int '<StartupCode$Library1>'.$Module1::_init
    IL_000b:  pop
    IL_000c:  ret
  } // end of method Module1::.cctor

  .property int32 x()
  {
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.
    CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.
    FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 ) 
    .get int32 Module1::get_x()
  } // end of property Module1::x
} // end of class Module1

.class private abstract auto ansi sealed beforefieldinit 
    '<StartupCode$Library1>'.$Module1
       extends [mscorlib]System.Object
{
  .field static assembly native int _init
  .custom instance void [mscorlib]System.Runtime.CompilerServices.
   CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  .field static assembly initonly int32 x@3
  .method private specialname rtspecialname static 
          void  .cctor() cil managed
  {
    // Code size       8 (0x8)
    .maxstack  3
    IL_0000:  nop
    IL_0001:  ldc.i4.2
    IL_0002:  stsfld     int32 '<StartupCode$Library1>'.$Module1::x@3
    IL_0007:  ret
  } // end of method $Module1::.cctor

} // end of class '<StartupCode$Library1>'.$Module1

Se você rastrear o código por meio o IL, você observe duas coisas; em primeiro lugar, se o nome "x" do código de F # está vinculado no nível do CLS como uma propriedade estática de uma classe chamado Módulo1 e o segundo, que o valor inicial de 2 não está ligado como uma constante, mas é inicializado quando o conjunto é carregado, via o construtor de tipo (.cctor) da classe StartupCode $Library1 gerado pelo compilador. Em outras palavras, ao passo que você pode ficar tentado a pensar x como um valor constante que o compilador pode in-line, o compilador escolhe para apresentá-lo como uma propriedade estática.

Isso significa que, depois, que acessar essa ligação, x, exigirá código C# algo como o seguinte:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("F#'s x = {0}", Module1.x);
        }
    }
}

Até o momento, tudo bem. Mas x é uma ligação bastante simples, e assim, você poderia espere que estejam uma simples questão de acessá-lo. Algo um pouco mais complicado poderia apresentar alguns problemas mais complicados, portanto, vamos fazer algo apenas um toque mais complicado para verificar o mapeamento de F # para CLS ainda faz sentido.

Para o código de F #

let add a b = a + b

o compilador gera o IL adicional na classe "Module1" na DLL F # compilado:

.method public static int32  'add'(int32 a,
                                   int32 b) cil managed
{
  // Code size       5 (0x5)
  .maxstack  4
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.1
  IL_0003:  add
  IL_0004:  ret
} // end of method Module1::'add'

Isso é quase exatamente a versão de C# da função aparência, portanto, há não muito que precise ser explicado aqui. Também é trivial chamá-lo.

Mas uma das vantagens do F # é que ele trata funções como valores de primeira classe, e isso se torna mais aparentes (e mais difícil) quando você começar a criar funções que utilizar as funções como argumentos, como a seguinte equivalente a F # biblioteca mapa função interna, que utiliza como argumentos uma lista, junto com uma função para aplicar a cada elemento da lista e retorna uma nova lista contendo os resultados:

let mymap (l : 'a list) (f : 'a -> 'b) = List.map f l

Isso é uma forma particularmente "funcional" de uma lista de processamento: em vez de iteração-item-por item, uma linguagem funcional usa uma função como um parâmetro e aplica a cada elemento na lista, gerar um único resultado que (chamado de uma operação de "dobra") ou uma nova lista contendo os resultados de cada (nosso " mapa").

Observe na Figura 4 como isso transforma em um pouco bastante complexo de IL após a compilação. Novamente, o fato de que ele mapeia para um método estático público na classe Módulo1 não é surpreendente; o que será dificultar isso a interagir de C#; no entanto, é que o método estático usa listas de F # (ou seja, com parâmetros de tipo instâncias Microsoft.FSharp.Collections.List,) como tipos de entrada e retorno, juntamente com uma função (significado, uma instância de uma instância dually-tipo-parametrizados Microsoft.FSharp.Core.FastFunc) como a segunda entrada.

Figura 4 O IL para a função de mapeamento

  .method public static class [FSharp.Core]Microsoft.FSharp.Collections.
       List'1<!!B> 
          mymap<A,B>(class [FSharp.Core]Microsoft.FSharp.Collections.
              List'1<!!A> l, class [FSharp.Core]Microsoft.FSharp.Core.
                         FastFunc'2<!!A,!!B> f)
          cil managed
  {
    // Code size       11 (0xb)
    .maxstack  4
    IL_0000:  nop
    IL_0001:  ldarg.1
    IL_0002:  ldarg.0
    IL_0003:  tail.
    IL_0005:  call       class [FSharp.Core]Microsoft.FSharp.Collections.
       List'1<!!1>
       [FSharp.Core]Microsoft.FSharp.Collections. 
       ListModule::map<!!0,!!1>(class [FSharp.Core]Microsoft.
       FSharp.Core.FastFunc'2<!!0,!!1>,
       class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!!0>)
    IL_000a:  ret
  } // end of method Module1::mymap

Isso será requerem algum código C# sério funcione corretamente. Neste exemplo, quero algum código C# para aceitar um conjunto de números inteiros e convertê-los em seus formulários de seqüência de caracteres correspondente. (O fato de que eu poderia fazer isso facilmente o suficiente de em C# é irrelevante aqui — preciso descobrir como fazer as coisas simples antes que pode lidar com o complexo.) Para chamar isso a partir de C# significa que várias coisas usarão para ocorrer com êxito: a coleção de entrada será precisará ser convertido para um tipo de lista de F #; a função aplicada a cada elemento deve ser convertida em um F # "FastFunc" instância; e a lista de F # retornada precisará ser convertido em um tipo de C# pode usar, ou que usado em sua forma F # nativa diretamente.

O código C# será necessário esses tipos de F #, e portanto, a primeira etapa será adicionar a apropriado F # assembly referência, nesse caso, FSharp.Core.dll. Construir uma lista de F #, no entanto, é não como construir uma lista de C# — em vez de passar na coleção C# por meio de um construtor, F # pressupõe que listas são criadas usando o operador "Cons", que é um método estático na classe < > lista de F #. Em outras palavras, o código de F #

let l1 = [1; 2; 3;]

se transforma em o IL feio em vez disso, mostrado na A Figura 5 .

A Figura 5 que l1 = [1; 2; 3;]

  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.2
  IL_0002:  ldc.i4.3
  IL_0003:  call       class [FSharp.Core]Microsoft.FSharp.Collections.
List'1<!0> class [FSharp.Core]Microsoft.FSharp.Collections.
List'1<int32>::get_uniq_Empty()
  IL_0008:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.
Collections.List'1/_Cons<int32>::.ctor(!0,
              class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!0>)
  IL_000d:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.
Collections.List'1/_Cons<int32>::.ctor(!0,
              class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!0>)
  IL_0012:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.
Collections.List'1/_Cons<int32>::.ctor(!0,
              class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!0>)

Isso significa que, para converter uma matriz de C# em uma lista de F # adequada para passagem para o método mymap, precisa chamar repetidamente o método de contras que se estático na lista, passando a nova sede sempre; Observe que como o item adicionado vai para a cabeça da lista, para manter a lista de F # na mesma ordem como a matriz de C#, eu que comece pelo final da matriz e trabalhar para trás:

int[] scores = {1, 2, 3, 4, 5};

var fs_scores = Microsoft.FSharp.Collections.List<int>.get_uniq_Empty();
for (int i = scores.Length-1; i >= 0; i--)
{
  fs_scores = Microsoft.FSharp.Collections.List<int>.Cons(scores[i],
                                                          fs_scores);                
}

Isso já está ficando algo de uma dificuldade. Obter uma instância do tipo de FastFunc do F # é ainda mais tediosa porque o FastFunc digitar, como o núcleo System.Delegate tipo, não é realmente pretende ser instanciado por programadores, mas em vez disso, tratada pelo compilador F #. Normalmente, quando construir uma instância de isso no código de F #, o compilador na verdade, gera uma classe interna que herda FastFunc, algo que você pode fazer de C#, mas que exigiria muito mais trabalho.

Chamando, de repente, essa função F # parece muito trabalho para o benefício derivado de forma.

O que tudo isso serve para ilustrar é que o mapeamento de um idioma para sua plataforma subjacente é crucial para decidir onde e como usar linguagens de maneira polyglot. Algo que é trivial fazer dentro de F #, por exemplo, pode ativar check-out ser difícil fazer diretamente no C#. Isso não significa desistir da idéia de polyglotism, isso apenas significa que você deve escolher cuidadosamente quais idiomas você usar juntos e que fins. Da perspectiva mais positiva, considere, um dos exemplos do artigo de outubro da instância com código adicional na parte inferior (veja a Figura 6 )

Figura 6 O código mais modificações original

open System
open System.IO
open Microsoft.FSharp.Control.CommonExtensions

#nowarn "057"
let aCountSpace filename size = 
  async {
     let space = Convert.ToByte ' '
     use stream = File.OpenRead (filename)
     let bytes = Array.create size space
     let! nbytes = stream.ReadAsync (bytes,0,size)
     let count = 
       bytes
       |> Array.fold_left (fun acc x -> if (x=space) then acc + 1 else acc) 0
     return count
  }

let aCounted (files : FileInfo array) = 
    files
    |> Array.map (fun f -> aCountSpace (f.FullName) (int f.Length))
    |> Async.Parallel 
    |> Async.Run

Observe que que tenha modificado-lo um pouco. No entanto, a única modificação que eu fiz aqui foi especificar o argumento "arquivos" no método de "aCounted" a ser uma matriz de FileInfo, algo que foi inferido pelo compilador F # no código original da instância. A assinatura de IL para "aCounted" agora se parece com

 .method public static int32[] aCounted(class [mscorlib]System.IO.FileInfo[] files) cil managed

que torna muito trivial para chamar de C#, como em:

int[] fileCounts = 
  Module1.aCounted(new DirectoryInfo(@"C:\Projects\Test").GetFiles("*.txt"));
foreach (var ct in fileCounts)
{
    Console.WriteLine("Count = {0}", ct);
}

Ainda essa nova versão, tão simples quanto chamar, mantém o recurso de execução total assíncrona instância abordado novamente em outubro.

Observe que o uso de C# aqui é puramente arbitrário, com base totalmente no fato de que sinto mais confortável em C# que no Visual Basic ou C + c++ / CLI; ter dito que não deve ser difícil ver como o mesmo código ficaria em um nesses idiomas.

O custo por trás as opções

Sempre há um custo para adotar uma abordagem polyglot, é claro. O primeiro dor de cabeça principal é muito óbvia para ver, desde que já tenha executado nela — desenvolvedores que desejam fazer uso de diferentes idiomas em um projeto deve compreender como os idiomas mapeiam para a plataforma subjacente — e, subseqüentemente, umas às outras. Isso significa que simplesmente contar com o compilador para tornar as coisas direito não funcionará mais.

Os desenvolvedores precisarão ver como o compilador mapeia as construções de linguagem para a plataforma subjacente (no caso, o CLR) e como outros idiomas escolherá até essas construções. A boa notícia deriva o fato de que idiomas não mudam muito longo do tempo, mesmo para idiomas que viu alguns turnos radical bastante sobre a última década, como C# ou Visual Basic, portanto, depois que você aprendeu algumas dos mapeamentos de idioma, essas informações permanecem relativamente constantes.

O segundo problema deriva a primeira — depuração de um programa polyglot pode ser mais difícil do que depuração um monoglot um, owing simplesmente para o fato de que agora o desenvolvedor deve falar dois ou mais idiomas, e não apenas que. No caso do código que apresentei, por exemplo, depuração exigirá definir um ponto de interrupção no código C# e depuração, possivelmente no código de F # e o verso novamente. Em vários aspectos, isso não é novidade — F # é apenas um idioma de mais à lista de idiomas que você precisa saber.

Em algumas lojas de desenvolvimento, os desenvolvedores trabalhando em C# código simplesmente confiança (ou por declaração ou por meio de confiança em testes de unidade) que o código de F # funciona conforme anunciado e passar em vez de etapa para o código de F #, apenas como muitos desenvolvedores de Visual Basic e C# faz quando a depuração através de código que chama em bibliotecas C ou C++ não gerenciadas.

No futuro colunas, examinarei outras maneiras que abordagens polyglot fazer programação mais fácil e em outros idiomas e seus benefícios para o programador polyglot.

Envie suas dúvidas e comentários para Ted para polyglot@Microsoft.com.

Ted Neward é consultor de segurança com ThoughtWorks, uma consultora internacional especializado em sistemas empresariais confiável e ágil. Ele escreveu vários livros, é um Microsoft MVP Architect, alto-falante INETA e instrutor PluralSight. Alcançar Ted em Ted@tedneward.com, ou ler seu blog em blogs.tedneward.com.