Diferença no Elm entre o tipo e o alias de tipo?

93

Em Elm, eu não consigo descobrir quando typeé o vs. palavra-chave apropriada type alias. A documentação não parece ter uma explicação para isso, nem consigo encontrar nas notas de lançamento. Isso está documentado em algum lugar?

ehdv
fonte

Respostas:

136

Como eu penso sobre isso:

type é usado para definir novos tipos de união:

type Thing = Something | SomethingElse

Antes dessa definição Somethinge SomethingElsenão significava nada. Agora, ambos são do tipo Thing, que acabamos de definir.

type alias é usado para dar um nome a algum outro tipo que já existe:

type alias Location = { lat:Int, long:Int }

{ lat = 5, long = 10 }tem tipo { lat:Int, long:Int }, que já era um tipo válido. Mas agora também podemos dizer que tem tipo Locationporque é um apelido para o mesmo tipo.

É importante notar que o seguinte irá compilar perfeitamente e exibir "thing". Mesmo que especificar thingé um Stringe aliasedStringIdentitytem um AliasedString, não obterá um erro que há uma incompatibilidade de tipo entre String/ AliasedString:

import Graphics.Element exposing (show)

type alias AliasedString = String

aliasedStringIdentity: AliasedString -> AliasedString
aliasedStringIdentity s = s

thing : String
thing = "thing"

main =
  show <| aliasedStringIdentity thing
robertjlooby
fonte
Não tenho certeza sobre seu ponto no último parágrafo. Você está tentando dizer que eles ainda são do mesmo tipo, não importa como você o alias?
ZHANG Cheng
7
Sim, apenas apontando que o compilador considera o tipo de alias igual ao original
robertjlooby
Então, quando você usa a {}sintaxe de registro, está definindo um novo tipo?
2
{ lat:Int, long:Int }não define um novo tipo. Esse já é um tipo válido. type alias Location = { lat:Int, long:Int }também não define um novo tipo, apenas dá outro nome (talvez mais descritivo) a um tipo já válido. type Location = Geo { lat:Int, long:Int }definiria um novo tipo ( Location)
robertjlooby
1
Quando se deve usar o alias de tipo vs. tipo? Onde está a desvantagem de sempre usar tipo?
Richard Haven
8

A chave é a palavra alias. No decorrer da programação, quando você quer agrupar coisas que pertencem umas às outras, você coloca em um registro, como no caso de um ponto

{ x = 5, y = 4 }  

ou um registro do aluno.

{ name = "Billy Bob", grade = 10, classof = 1998 }

Agora, se você precisar passar esses registros, terá que soletrar o tipo inteiro, como:

add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
  { a.x + b.x, a.y + b.y }

Se você pudesse criar um alias para um ponto, a assinatura seria muito mais fácil de escrever!

type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
  { a.x + b.x, a.y + b.y }

Portanto, um alias é uma abreviação para outra coisa. Aqui, é uma abreviatura para um tipo de registro. Você pode pensar nisso como dar um nome a um tipo de registro que usará com frequência. É por isso que é chamado de alias - é outro nome para o tipo de registro simples que é representado por{ x:Int, y:Int }

Considerando que typeresolve um problema diferente. Se você está vindo de OOP, é o problema que você resolve com herança, sobrecarga de operador, etc. - às vezes, você deseja tratar os dados como algo genérico e às vezes deseja tratá-los como algo específico.

Um lugar comum onde isso acontece é ao passar mensagens - como o sistema postal. Ao enviar uma carta, você deseja que o sistema postal trate todas as mensagens como a mesma coisa; portanto, você só precisa projetar o sistema postal uma vez. Além disso, o trabalho de roteamento da mensagem deve ser independente da mensagem contida nela. É apenas quando a carta chega ao seu destino que você se preocupa com a mensagem.

Da mesma forma, podemos definir a typecomo a união de todos os diferentes tipos de mensagens que podem acontecer. Digamos que estejamos implementando um sistema de mensagens entre estudantes universitários para seus pais. Portanto, há apenas duas mensagens que os universitários podem enviar: 'Preciso de dinheiro para cerveja' e 'Preciso de cuecas'.

type MessageHome = NeedBeerMoney | NeedUnderpants

Portanto, agora, quando projetamos o sistema de roteamento, os tipos de nossas funções podem apenas passar MessageHome, em vez de nos preocuparmos com todos os diferentes tipos de mensagens que poderiam ser. O sistema de roteamento não se importa. Ele só precisa saber que é um MessageHome. É apenas quando a mensagem chega ao seu destino, a casa dos pais, que você precisa descobrir o que é.

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

Se você conhece a arquitetura Elm, a função de atualização é uma declaração de caso gigante, porque esse é o destino de onde a mensagem é roteada e, portanto, processada. E usamos tipos de união para ter um único tipo com o qual lidar ao passar a mensagem, mas então podemos usar uma instrução case para descobrir exatamente que mensagem era, para que possamos lidar com isso.

Wilhelm
fonte
5

Deixe-me complementar as respostas anteriores focalizando os casos de uso e fornecendo um pouco de contexto nas funções e módulos do construtor.



Usos de type alias

  1. Criar um alias e uma função construtora para um registro
    Este é o caso de uso mais comum: você pode definir um nome alternativo e uma função construtora para um tipo específico de formato de registro.

    type alias Person =
        { name : String
        , age : Int
        }
    

    Definir o alias do tipo implica automaticamente a seguinte função de construtor (pseudocódigo):
    Person : String -> Int -> { name : String, age : Int }
    Isso pode ser útil, por exemplo, quando você deseja escrever um decodificador Json.

    personDecoder : Json.Decode.Decoder Person
    personDecoder =
        Json.Decode.map2 Person
            (Json.Decode.field "name" Json.Decode.String)
            (Json.Decode.field "age" Int)
    


  2. Especifique os campos obrigatórios.
    Às vezes, eles chamam de "registros extensíveis", o que pode ser enganoso. Esta sintaxe pode ser usada para especificar que você está esperando algum registro com campos específicos presentes. Tal como:

    type alias NamedThing x =
        { x
            | name : String
        }
    
    showName : NamedThing x -> Html msg
    showName thing =
        Html.text thing.name
    

    Em seguida, você pode usar a função acima como esta (por exemplo, em sua visualização):

    let
        joe = { name = "Joe", age = 34 }
    in
        showName joe
    

    A palestra de Richard Feldman no ElmEurope 2017 pode fornecer mais informações sobre quando vale a pena usar esse estilo.

  3. Renomeando coisas
    Você pode fazer isso, porque os novos nomes podem fornecer um significado extra posteriormente em seu código, como neste exemplo

    type alias Id = String
    
    type alias ElapsedTime = Time
    
    type SessionStatus
        = NotStarted
        | Active Id ElapsedTime
        | Finished Id
    

    Talvez um exemplo melhor desse tipo de uso no núcleo sejaTime .

  4. Reexpondo um tipo de um módulo diferente
    Se você estiver escrevendo um pacote (não um aplicativo), pode ser necessário implementar um tipo em um módulo, talvez em um módulo interno (não exposto), mas deseja expor o tipo de um módulo diferente (público). Ou, alternativamente, você deseja expor seu tipo de vários módulos.
    Taskno núcleo e Http.Request em HTTP são exemplos para o primeiro, enquanto o Json.Encode.Value e Json.Decode.Value par é um exemplo do mais tarde.

    Você só pode fazer isso quando, de outra forma, deseja manter o tipo opaco: você não expõe as funções do construtor. Para obter detalhes, consulte os usos typeabaixo.

É importante notar que nos exemplos acima apenas o # 1 fornece uma função de construtor. Se você expor seu alias de tipo em # 1 assim, module Data exposing (Person)irá expor o nome do tipo, bem como a função do construtor.



Usos de type

  1. Defina um tipo de união marcada
    Este é o caso de uso mais comum, um bom exemplo disso é o Maybetipo em núcleo :

    type Maybe a
        = Just a
        | Nothing
    

    Ao definir um tipo, você também define suas funções de construtor. No caso de Maybe estes são (pseudocódigo):

    Just : a -> Maybe a
    
    Nothing : Maybe a
    

    O que significa que se você declarar este valor:

    mayHaveANumber : Maybe Int

    Você pode criá-lo por qualquer

    mayHaveANumber = Nothing

    ou

    mayHaveANumber = Just 5

    As tags Juste Nothingnão servem apenas como funções construtoras, mas também como destruidores ou padrões em uma caseexpressão. O que significa que, usando esses padrões, você pode ver dentro de Maybe:

    showValue : Maybe Int -> Html msg
    showValue mayHaveANumber =
        case mayHaveANumber of
            Nothing ->
                Html.text "N/A"
    
            Just number ->
                Html.text (toString number)
    

    Você pode fazer isso, porque o módulo Maybe é definido como

    module Maybe exposing 
        ( Maybe(Just,Nothing)
    

    Também poderia dizer

    module Maybe exposing 
        ( Maybe(..)
    

    Os dois são equivalentes neste caso, mas ser explícito é considerado uma virtude no Elm, especialmente quando você está escrevendo um pacote.


  1. Ocultando detalhes de implementação
    Como apontado acima, é uma escolha deliberada que as funções do construtor Maybesejam visíveis para outros módulos.

    Há outros casos, porém, em que o autor decide ocultá-los. Um exemplo disso no núcleo éDict . Como consumidor do pacote, você não deve ser capaz de ver os detalhes de implementação do algoritmo de árvore Vermelho / Preto por trás Dicte mexer com os nós diretamente. Ocultar as funções do construtor força o consumidor do seu módulo / pacote a criar apenas valores do seu tipo (e então transformar esses valores) por meio das funções que você expõe.

    Esta é a razão pela qual às vezes coisas assim aparecem no código

    type Person =
        Person { name : String, age : Int }
    

    Ao contrário da type aliasdefinição no início deste post, esta sintaxe cria um novo tipo de "união" com apenas uma função de construtor, mas essa função de construtor pode ser oculta de outros módulos / pacotes.

    Se o tipo for exposto assim:

    module Data exposing (Person)

    Apenas o código no Datamódulo pode criar um valor Person e apenas esse código pode corresponder a um padrão nele.

Gabor
fonte
1

A principal diferença, a meu ver, é se o verificador de tipos gritará com você se você usar o tipo "sinômico".

Crie o seguinte arquivo, coloque-o em algum lugar e execute-o elm-reactor, em seguida, vá para http://localhost:8000para ver a diferença:

-- Boilerplate code

module Main exposing (main)

import Html exposing (..)

main =
  Html.beginnerProgram
    {
      model = identity,
      view = view,
      update = identity
    }

-- Our type system

type alias IntRecordAlias = {x : Int}
type IntRecordType =
  IntRecordType {x : Int}

inc : {x : Int} -> {x : Int}
inc r = {r | x = .x r + 1}

view model =
  let
    -- 1. This will work
    r : IntRecordAlias
    r = {x = 1}

    -- 2. However, this won't work
    -- r : IntRecordType
    -- r = IntRecordType {x = 1}
  in
    Html.text <| toString <| inc r

Se você descomentar 2.e comentar 1., verá:

The argument to function `inc` is causing a mismatch.

34|                              inc r
                                     ^
Function `inc` is expecting the argument to be:

    { x : Int }

But it is:

    IntRecordType
EugZol
fonte
0

Um aliasé apenas um nome mais curto para algum outro tipo, semelhante classem OOP. Exp:

type alias Point =
  { x : Int
  , y : Int
  }

Um type(sem alias) vai deixar você definir seu próprio tipo, para que possa definir tipos, como Int, String... para você aplicativo. Por exemplo, em casos comuns, pode ser usado para a descrição de um estado de aplicativo:

type AppState = 
  Loading          --loading state
  |Loaded          --load successful
  |Error String    --Loading error

Assim, você pode manipulá-lo facilmente no viewolmo:

-- VIEW
...
case appState of
    Loading -> showSpinner
    Loaded -> showSuccessData
    Error error -> showError

...

Acho que você sabe a diferença entre typee type alias.

Mas por que e como usar typee type aliasé importante com o elmaplicativo, vocês podem consultar o artigo de Josh Clayton

Hien
fonte