cv.cm

Lập trình · Ngôn ngữ lập trình

Hướng dẫn học lập trình Julia cơ bản cho người mới

7 phút đọc

Trả lời nhanh

Julia là ngôn ngữ bậc cao, kiểu động, được thiết kế cho tính toán số và khoa học nhanh. Nó đọc giống Python hoặc MATLAB nhưng biên dịch mỗi hàm ra mã native ngay lần gọi đầu tiên, nên vòng lặp có thể chạy với tốc độ gần C. Ý tưởng trung tâm là multiple dispatch: một hàm chọn phương thức dựa trên kiểu của tất cả các đối số. Cài bằng juliaup và chạy một tệp bằng julia hello.jl.

Julia là gì và ai dùng nó

Julia do Jeff Bezanson, Stefan Karpinski, Viral Shah và Alan Edelman tạo ra, và công bố năm 2012. Phiên bản 1.0 ra mắt năm 2018, và dòng 1.x giữ tương thích ngược. Julia nhắm tới việc giải quyết "bài toán hai ngôn ngữ": tạo mẫu bằng một ngôn ngữ chậm mà dễ dùng, rồi viết lại các phần nóng bằng C hoặc Fortran.

Julia được các nhà khoa học, kỹ sư và nhà phân tích dữ liệu dùng cho mô phỏng, phương trình vi phân, tối ưu hóa, thống kê, học máy và tính toán hiệu năng cao. Hệ sinh thái gói của nó bao gồm data frame, vẽ đồ thị, tính toán GPU và đạo hàm tự động.

Cài Julia và chạy Hello, world

Trình cài đặt được khuyên dùng là juliaup, cài và cập nhật các phiên bản Julia trên Windows, macOS và Linux (xem julialang.org/downloads). Sau khi cài, julia mở REPL và julia file.jl chạy một script.

# hello.jl
println("Hello, world!")
julia hello.jl
# Hello, world!
julia
# julia> 1 + 2
# 3

Trong REPL, nhấn ] để vào chế độ gói (ví dụ add DataFrames), ? để xem trợ giúp về một tên, và ; để vào shell. Nhấn Backspace để quay về dấu nhắc thông thường.

Biến và các kiểu dữ liệu cơ bản

Biến không cần khai báo, nhưng mọi giá trị đều có một kiểu cụ thể, bạn có thể xem bằng typeof. Bạn có thể thêm chú thích kiểu cho đối số hàm và trường struct khi muốn.

KiểuVí dụGhi chú
Int6442Số nguyên mặc định trên hệ 64 bit; tràn số thì quay vòng
Float643.14, 1e-3Số thực dấu phẩy động mặc định
Rational1//3Phân số chính xác
String"hello"UTF-8; nội suy bằng "$name"
Char'a'Một ký tự Unicode đơn
Booltrue, falseGiá trị logic
Symbol:nameMột tên được intern, thường dùng làm khóa hoặc tùy chọn
Nothingnothing"Không có giá trị", được trả về bởi hàm không có kết quả
name = "Ana"
age = 30
println("$name is $age, next year $(age + 1)")
# Ana is 30, next year 31
typeof(age)   # Int64
7 / 2         # 3.5  (always a float)
7 ÷ 2         # 3    (integer division, also div(7, 2))
2^10          # 1024

Julia chấp nhận tên Unicode như π hoặc α; trong REPL và hầu hết trình soạn thảo bạn gõ \pi rồi nhấn Tab để có π.

Điều khiển luồng

Các khối kết thúc bằng từ khóa end; thụt lề chỉ để dễ đọc. if, for và while hoạt động như ở hầu hết ngôn ngữ, và comprehension tạo mảng chỉ trong một dòng.

function sign_name(n)
    if n > 0
        "positive"
    elseif n < 0
        "negative"
    else
        "zero"
    end
end
for i in 1:3
    println("$i squared is $(i^2)")
end
squares = [x^2 for x in 1:5 if isodd(x)]   # [1, 9, 25]
x = 5
result = x > 3 ? "big" : "small"           # "big"

1:3 là một khoảng bao gồm cả hai đầu. Biểu thức cuối cùng trong hàm hoặc khối if chính là giá trị của nó, nên sign_name(-2) trả về "negative" mà không cần return tường minh.

Hàm

add(x, y) = x + y                 # one-line definition
function greet(name = "world"; punctuation = "!")
    return "Hello, $name$punctuation"
end
greet()                           # "Hello, world!"
greet("Ana", punctuation = ".")   # "Hello, Ana."
square = x -> x^2                 # anonymous function
map(square, [1, 2, 3])            # [1, 4, 9]
v = [3, 1, 2]
sort!(v)                          # v is now [1, 2, 3]

Đối số vị trí đứng trước dấu chấm phẩy và đối số từ khóa đứng sau. Theo quy ước, hàm có tên kết thúc bằng ! sẽ thay đổi một trong các đối số của nó, như sort! hay push!, còn sort trả về một mảng mới.

Mảng, tuple và dictionary

Cấu trúcCú pháp literalGhi chú
Vector[1, 2, 3]Chỉ số từ 1, khả biến, tăng bằng push!
Matrix[1 2; 3 4]Dấu cách ngăn cách cột, ; ngăn cách hàng
Tuple(1, "a")Bất biến, độ dài cố định
Tuple có tên(name = "Ana", age = 30)Truy cập trường bằng .name
DictDict("a" => 1)Bảng băm khả biến
SetSet([1, 2, 2])Các giá trị duy nhất
v = [10, 20, 30]
v[1]          # 10   (first element)
v[end]        # 30
push!(v, 40)
A = [1 2; 3 4]
A * A         # matrix product: [7 10; 15 22]
A .* A        # element-wise:   [1 4; 9 16]
v .+ 1        # broadcasting:   [11, 21, 31, 41]
sqrt.([1, 4, 9])   # [1.0, 2.0, 3.0]

Cú pháp dấu chấm gọi là broadcasting: f.(x) áp dụng f cho từng phần tử, còn .+, .* hoạt động theo từng phần tử. Julia gộp chuỗi các phép toán có dấu chấm thành một vòng lặp duy nhất.

Tính năng đặc trưng: multiple dispatch

Một hàm Julia có thể có nhiều phương thức. Khi bạn gọi nó, Julia chọn phương thức cụ thể nhất cho kiểu của tất cả các đối số. Điều này thay thế phương thức của lớp: kiểu giữ dữ liệu, còn hành vi được thêm bằng cách định nghĩa phương thức cho chúng, kể cả từ một gói khác.

abstract type Shape end
struct Circle <: Shape
    r::Float64
end
struct Rect <: Shape
    w::Float64
    h::Float64
end
area(c::Circle) = π * c.r^2
area(r::Rect) = r.w * r.h
shapes = [Circle(1.0), Rect(2.0, 3.0)]
println(round.(area.(shapes), digits = 2))   # [3.14, 6.0]

Vì Julia biên dịch một phiên bản chuyên biệt của hàm cho các kiểu đối số mà nó gặp, lần gọi đầu tiên của hàm chậm hơn (gồm cả biên dịch) và các lần sau thì nhanh. Dùng mutable struct khi các trường cần thay đổi sau khi tạo.

Xử lý lỗi

Julia dùng ngoại lệ. Hãy ném một kiểu ngoại lệ dựng sẵn như ArgumentError hoặc DomainError, hoặc tự định nghĩa một struct là kiểu con của Exception. tryparse trả về nothing thay vì ném lỗi.

function parse_age(s)
    n = tryparse(Int, s)
    n === nothing && throw(ArgumentError("not a number: $s"))
    n < 0 && throw(DomainError(n, "age must not be negative"))
    return n
end
try
    parse_age("abc")
catch e
    if e isa ArgumentError
        println("Error: ", e.msg)
    else
        rethrow()
    end
end
# Error: not a number: abc

Một chương trình hoàn chỉnh nhỏ: tần suất từ

text = "the cat and the hat and the bat"
counts = Dict{String,Int}()
for w in split(text)
    counts[w] = get(counts, w, 0) + 1
end
top = sort(collect(counts), by = last, rev = true)
for (word, n) in first(top, 2)
    println(word, " ", n)
end
# the 3
# and 2

split tách chuỗi theo khoảng trắng, get trả về giá trị mặc định 0 cho từ mới, collect biến dictionary thành vector các cặp word => count, và by = last sắp xếp theo số lần.

Học tiếp ở đâu

  • Sách hướng dẫn Julia chính thức tại docs.julialang.org, bắt đầu từ "Getting Started".
  • Trang học tập tại julialang.org, liệt kê các hướng dẫn, sách và khóa học.
  • Đọc chương "Performance Tips" trong sách hướng dẫn trước khi tối ưu mã.
  • Khám phá các gói như DataFrames.jl, Plots.jl và DifferentialEquations.jl.

Câu hỏi thường gặp

Julia có nhanh hơn Python không?

Với mã có nhiều vòng lặp và tính toán số, Julia thường nhanh hơn nhiều so với Python thuần vì biên dịch ra mã native. Python cùng các thư viện như NumPy có thể nhanh với các phép toán vector hóa, nên sự khác biệt tùy thuộc vào tác vụ.

Vì sao Julia đếm từ 1?

Julia theo quy ước của toán học và của các ngôn ngữ như MATLAB, R và Fortran, nơi phần tử đầu tiên của mảng có chỉ số 1.

Vì sao lần chạy đầu của mã Julia chậm?

Julia biên dịch mỗi hàm cho các kiểu đối số ngay lần gọi đầu tiên. Các lần gọi sau dùng lại mã đã biên dịch, nên hãy đo hiệu năng sau một lần gọi khởi động hoặc bằng một gói đo hiệu năng.

Julia có miễn phí không?

Có. Julia là mã nguồn mở theo giấy phép MIT, và gần như mọi gói của nó cũng là mã nguồn mở.

Tất cả hướng dẫn lập trình