ในเอล์ม, ฉันไม่สามารถคิดออกเมื่อเป็นคำหลักที่เหมาะสมกับtype
type alias
เอกสารประกอบดูเหมือนจะไม่มีคำอธิบายเกี่ยวกับเรื่องนี้และฉันไม่พบเอกสารในบันทึกประจำรุ่น เอกสารนี้อยู่ที่ไหน
ในเอล์ม, ฉันไม่สามารถคิดออกเมื่อเป็นคำหลักที่เหมาะสมกับtype
type alias
เอกสารประกอบดูเหมือนจะไม่มีคำอธิบายเกี่ยวกับเรื่องนี้และฉันไม่พบเอกสารในบันทึกประจำรุ่น เอกสารนี้อยู่ที่ไหน
คำตอบ:
ฉันคิดอย่างไร:
type
ใช้สำหรับกำหนดประเภทสหภาพใหม่:
type Thing = Something | SomethingElse
ก่อนหน้านี้คำจำกัดความSomething
และSomethingElse
ไม่ได้มีความหมายอะไรเลย ตอนนี้ทั้งสองประเภทThing
ซึ่งเราเพิ่งกำหนดไว้
type alias
ใช้สำหรับตั้งชื่อให้กับประเภทอื่นที่มีอยู่แล้ว:
type alias Location = { lat:Int, long:Int }
{ lat = 5, long = 10 }
มีประเภท{ lat:Int, long:Int }
ซึ่งเป็นประเภทที่ถูกต้องอยู่แล้ว แต่ตอนนี้เราสามารถพูดได้ว่ามีประเภทLocation
เพราะเป็นนามแฝงสำหรับประเภทเดียวกัน
เป็นที่น่าสังเกตว่าต่อไปนี้จะรวบรวมและแสดงผล"thing"
ได้ดี แม้ว่าเราจะระบุthing
เป็นString
และaliasedStringIdentity
ใช้เวลาAliasedString
ที่เราจะไม่ได้รับข้อผิดพลาดที่ไม่ตรงกันระหว่างประเภท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
{ lat:Int, long:Int }
ไม่ได้กำหนดประเภทใหม่ นั่นเป็นประเภทที่ถูกต้องอยู่แล้ว type alias Location = { lat:Int, long:Int }
ยังไม่ได้กำหนดประเภทใหม่ แต่จะให้ชื่ออื่น (อาจสื่อความหมายได้มากกว่า) เป็นประเภทที่ถูกต้องอยู่แล้ว type Location = Geo { lat:Int, long:Int }
จะกำหนดประเภทใหม่ ( Location
)
alias
ที่สำคัญคือคำว่า ในระหว่างการเขียนโปรแกรมเมื่อคุณต้องการจัดกลุ่มสิ่งที่อยู่ด้วยกันคุณจะใส่มันลงในบันทึกเช่นในกรณีของจุด
{ x = 5, y = 4 }
หรือบันทึกของนักเรียน
{ name = "Billy Bob", grade = 10, classof = 1998 }
ตอนนี้หากคุณต้องการส่งต่อบันทึกเหล่านี้คุณจะต้องสะกดทั้งประเภทเช่น:
add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
{ a.x + b.x, a.y + b.y }
หากคุณสามารถใช้นามแฝงว่าลายเซ็นจะเขียนง่ายกว่ามาก!
type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
{ a.x + b.x, a.y + b.y }
ดังนั้นนามแฝงจึงเป็นชวเลขสำหรับอย่างอื่น นี่คือชวเลขสำหรับประเภทบันทึก คุณอาจคิดว่าเป็นการตั้งชื่อให้กับประเภทบันทึกที่คุณจะใช้บ่อยๆ นั่นเป็นเหตุผลที่เรียกว่านามแฝง - เป็นอีกชื่อหนึ่งของประเภทบันทึกเปล่าที่แสดง{ x:Int, y:Int }
ในขณะที่type
แก้ปัญหาที่แตกต่างกัน หากคุณมาจาก OOP นั่นคือปัญหาที่คุณแก้ด้วยการสืบทอดการทำงานมากเกินไปของตัวดำเนินการและอื่น ๆ - บางครั้งคุณต้องการถือว่าข้อมูลเป็นเรื่องธรรมดาและบางครั้งคุณก็ต้องการที่จะปฏิบัติเหมือนสิ่งที่เฉพาะเจาะจง
สิ่งที่พบเห็นได้ทั่วไปคือเมื่อส่งข้อความไปทั่วเช่นระบบไปรษณีย์ เมื่อคุณส่งจดหมายคุณต้องการให้ระบบไปรษณีย์ถือว่าข้อความทั้งหมดเป็นสิ่งเดียวกันดังนั้นคุณต้องออกแบบระบบไปรษณีย์เพียงครั้งเดียว และนอกจากนี้งานกำหนดเส้นทางข้อความควรเป็นอิสระจากข้อความที่อยู่ภายใน เฉพาะเมื่อจดหมายถึงปลายทางคุณสนใจว่าข้อความนั้นคืออะไร
ในทำนองเดียวกันเราอาจกำหนดtype
ว่าเป็นการรวมกันของข้อความประเภทต่างๆทั้งหมดที่อาจเกิดขึ้น สมมติว่าเรากำลังใช้ระบบส่งข้อความระหว่างนักศึกษากับผู้ปกครอง ดังนั้นจึงมีเพียงสองข้อความที่เด็กวิทยาลัยสามารถส่ง: 'ฉันต้องการเงินค่าเบียร์' และ 'ฉันต้องการกางเกงใน'
type MessageHome = NeedBeerMoney | NeedUnderpants
ตอนนี้เมื่อเราออกแบบระบบการกำหนดเส้นทางประเภทของฟังก์ชันของเราสามารถส่งผ่านไปMessageHome
มาได้แทนที่จะกังวลเกี่ยวกับข้อความประเภทต่างๆทั้งหมดที่อาจเป็นได้ ระบบเส้นทางไม่สนใจ เพียงแค่ต้องรู้ว่ามันเป็นไฟล์MessageHome
. ก็ต่อเมื่อข้อความไปถึงปลายทางนั่นคือบ้านของผู้ปกครองเท่านั้นที่คุณต้องหาว่ามันคืออะไร
case message of
NeedBeerMoney ->
sayNo()
NeedUnderpants ->
sendUnderpants(3)
หากคุณรู้จักสถาปัตยกรรม Elm ฟังก์ชันการอัปเดตเป็นคำสั่งกรณีขนาดใหญ่เนื่องจากเป็นปลายทางของการกำหนดเส้นทางข้อความและด้วยเหตุนี้จึงถูกประมวลผล และเราใช้ประเภทยูเนี่ยนเพื่อให้มีประเภทเดียวในการจัดการเมื่อส่งข้อความไปรอบ ๆ แต่สามารถใช้คำสั่งกรณีเพื่อล้อเลียนว่าข้อความนั้นคืออะไรเพื่อให้เราจัดการกับมันได้
ให้ฉันเติมเต็มคำตอบก่อนหน้านี้โดยเน้นไปที่กรณีการใช้งานและให้บริบทเล็กน้อยเกี่ยวกับฟังก์ชันตัวสร้างและโมดูล
type alias
สร้างนามแฝงและฟังก์ชันตัวสร้างสำหรับเร็กคอร์ด
นี่คือกรณีการใช้งานที่พบบ่อยที่สุด: คุณสามารถกำหนดชื่อทางเลือกและฟังก์ชันตัวสร้างสำหรับรูปแบบระเบียนเฉพาะ
type alias Person =
{ name : String
, age : Int
}
การกำหนดนามแฝงประเภทจะแสดงถึงฟังก์ชันตัวสร้างต่อไปนี้โดยอัตโนมัติ (รหัสเทียม):
Person : String -> Int -> { name : String, age : Int }
สิ่งนี้มีประโยชน์เช่นเมื่อคุณต้องการเขียนตัวถอดรหัส Json
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.map2 Person
(Json.Decode.field "name" Json.Decode.String)
(Json.Decode.field "age" Int)
ระบุฟิลด์ที่จำเป็น
บางครั้งพวกเขาเรียกว่า "บันทึกที่ขยายได้" ซึ่งอาจทำให้เข้าใจผิดได้ ไวยากรณ์นี้สามารถใช้เพื่อระบุว่าคุณกำลังคาดหวังว่าจะมีบางระเบียนที่มีฟิลด์เฉพาะอยู่ เช่น:
type alias NamedThing x =
{ x
| name : String
}
showName : NamedThing x -> Html msg
showName thing =
Html.text thing.name
จากนั้นคุณสามารถใช้ฟังก์ชันด้านบนเช่นนี้ (ตัวอย่างเช่นในมุมมองของคุณ):
let
joe = { name = "Joe", age = 34 }
in
showName joe
คำพูดของ Richard Feldman เกี่ยวกับ ElmEurope 2017อาจให้ข้อมูลเชิงลึกเพิ่มเติมว่าเมื่อใดควรใช้สไตล์นี้
การเปลี่ยนชื่อสิ่งต่างๆ
คุณอาจทำได้เนื่องจากชื่อใหม่อาจให้ความหมายเพิ่มเติมในภายหลังในรหัสของคุณเช่นในตัวอย่างนี้
type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
บางทีอาจจะเป็นตัวอย่างที่ดีของชนิดของการใช้งานในหลักนี้อยู่Time
การเปิดเผยประเภทจากโมดูลอื่นอีกครั้ง
หากคุณกำลังเขียนแพ็คเกจ (ไม่ใช่แอปพลิเคชัน) คุณอาจต้องใช้ประเภทในโมดูลเดียวบางทีอาจอยู่ในโมดูลภายใน (ไม่เปิดเผย) แต่คุณต้องการแสดงประเภทจาก โมดูลอื่น (สาธารณะ) หรือคุณต้องการเปิดเผยประเภทของคุณจากหลายโมดูล
Task
ในหลักและHttp.Request ใน Httpเป็นตัวอย่างสำหรับครั้งแรกในขณะที่Json.Encode.ValueและJson.Decode.Valueคู่เป็นตัวอย่างของในภายหลัง
คุณสามารถทำได้ก็ต่อเมื่อคุณต้องการให้ประเภททึบแสง: คุณจะไม่เปิดเผยฟังก์ชันตัวสร้าง ดูรายละเอียดการใช้งานtype
ด้านล่าง
เป็นที่น่าสังเกตว่าในตัวอย่างข้างต้นมีเพียง # 1 เท่านั้นที่มีฟังก์ชันตัวสร้าง หากคุณแสดงนามแฝงประเภทของคุณใน # 1 เช่นmodule Data exposing (Person)
นั้นจะแสดงชื่อประเภทเช่นเดียวกับฟังก์ชันตัวสร้าง
type
กำหนดประเภทสหภาพที่ติดแท็ก
นี่คือกรณีการใช้งานที่พบบ่อยที่สุดตัวอย่างที่ดีคือMaybe
ประเภทในแกน :
type Maybe a
= Just a
| Nothing
เมื่อคุณกำหนดประเภทคุณยังกำหนดฟังก์ชันตัวสร้างด้วย ในกรณีที่อาจจะเป็น (รหัสหลอก):
Just : a -> Maybe a
Nothing : Maybe a
ซึ่งหมายความว่าหากคุณประกาศค่านี้:
mayHaveANumber : Maybe Int
คุณสามารถสร้างได้โดย
mayHaveANumber = Nothing
หรือ
mayHaveANumber = Just 5
Just
และNothing
แท็กไม่เพียง แต่ทำหน้าที่เป็นฟังก์ชั่นคอนสตรัคพวกเขายังทำหน้าที่เป็น destructors หรือรูปแบบในcase
การแสดงออก ซึ่งหมายความว่าการใช้รูปแบบเหล่านี้คุณสามารถเห็นได้ภายใน a Maybe
:
showValue : Maybe Int -> Html msg
showValue mayHaveANumber =
case mayHaveANumber of
Nothing ->
Html.text "N/A"
Just number ->
Html.text (toString number)
คุณสามารถทำได้เนื่องจากโมดูลอาจถูกกำหนดไว้เช่น
module Maybe exposing
( Maybe(Just,Nothing)
มันยังสามารถพูดได้
module Maybe exposing
( Maybe(..)
ทั้งสองมีความเท่าเทียมกันในกรณีนี้ แต่การพูดอย่างชัดเจนถือเป็นคุณธรรมใน Elm โดยเฉพาะอย่างยิ่งเมื่อคุณเขียนแพ็คเกจ
การซ่อนรายละเอียดการใช้งาน
ตามที่ระบุไว้ข้างต้นเป็นตัวเลือกโดยเจตนาที่Maybe
จะมองเห็นฟังก์ชันตัวสร้างสำหรับโมดูลอื่น ๆ
อย่างไรก็ตามมีกรณีอื่น ๆ เมื่อผู้เขียนตัดสินใจที่จะซ่อนไว้ หนึ่งในตัวอย่างนี้ในหลักคือ Dict
ในฐานะผู้ใช้แพ็กเกจคุณไม่ควรเห็นรายละเอียดการใช้งานอัลกอริทึมต้นไม้สีแดง / ดำที่อยู่เบื้องหลังDict
และยุ่งกับโหนดโดยตรง การซ่อนฟังก์ชันตัวสร้างจะบังคับให้ผู้บริโภคของโมดูล / แพ็กเกจของคุณสร้างเฉพาะค่าประเภทของคุณ (จากนั้นแปลงค่าเหล่านั้น) ผ่านฟังก์ชันที่คุณเปิดเผย
นี่คือเหตุผลว่าทำไมบางครั้งสิ่งนี้จึงปรากฏในโค้ด
type Person =
Person { name : String, age : Int }
ซึ่งแตกต่างจากtype alias
คำจำกัดความที่ด้านบนของโพสต์นี้ไวยากรณ์นี้จะสร้างประเภท "union" ใหม่โดยมีฟังก์ชันตัวสร้างเพียงฟังก์ชันเดียว แต่ฟังก์ชันตัวสร้างนั้นสามารถซ่อนจากโมดูล / แพ็กเกจอื่น ๆ ได้
หากประเภทถูกเปิดเผยเช่นนี้:
module Data exposing (Person)
เฉพาะโค้ดในData
โมดูลเท่านั้นที่สามารถสร้างค่าบุคคลและมีเพียงรหัสนั้นเท่านั้นที่สามารถจับคู่รูปแบบได้
ความแตกต่างหลักอย่างที่ฉันเห็นคือตัวตรวจสอบประเภทจะตะโกนใส่คุณหรือไม่ถ้าคุณใช้ประเภท "synomical"
สร้างไฟล์ต่อไปนี้วางไว้ที่ไหนสักแห่งแล้วเรียกใช้elm-reactor
จากนั้นไปhttp://localhost:8000
ดูความแตกต่าง:
-- 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
หากคุณไม่2.
แสดงความคิดเห็นและแสดงความคิดเห็น1.
คุณจะเห็น:
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
An alias
เป็นเพียงชื่อที่สั้นกว่าสำหรับประเภทอื่นที่คล้ายกันclass
ใน OOP ประสบการณ์:
type alias Point =
{ x : Int
, y : Int
}
type
(ไม่มีนามแฝง) จะช่วยให้คุณกำหนดประเภทของคุณเองเพื่อให้คุณสามารถกำหนดประเภทเช่นInt
, String
... สำหรับคุณแอป สำหรับ exmaple ในกรณีทั่วไปสามารถใช้สำหรับคำอธิบายสถานะของแอป:
type AppState =
Loading --loading state
|Loaded --load successful
|Error String --Loading error
ดังนั้นคุณสามารถจัดการมันได้อย่างง่ายดายในview
เอล์ม:
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
ฉันคิดว่าคุณทราบความแตกต่างระหว่างและtype
type alias
แต่ทำไมและวิธีการใช้งานtype
และtype alias
มีความสำคัญกับelm
แอปพวกคุณสามารถอ้างอิงบทความจาก Josh Clayton