Skip to content

seongkyu-sim/curves_of_elm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 

Repository files navigation

elm ver0.18 학습 기록


공식문서

  1. 튜토리얼
  2. 가이드

Functional Programming에대한 사전 지식이 부족하면 튜토리얼 을 먼저 읽기 권함

참고자료

  1. Why You Should Give Elm a Try : 왜 Elm을 써야하는가에대한 간략한 설명과 3개의 유투브 영상이 링크되어 있음
  2. 케빈TV : '나는 프로그래머다' 엠씨로 활동중이신 개발자분, 중간중간 잡담이 있지만 같이 공부하는 기분이 들게함 :) 강추!
  3. Let's be mainstream! User focused design in Elm - Curry On: elm개발한 Evan Czaplicki의 소개영상

todos

연습용 프로젝트 생성

  1. 프로젝트 폴더 생성
  2. 터미널에서 프로젝트 폴더로 이동후 elm package install elm-lang/html
  3. 프로젝트 루트패스에 Hello.eml파일 생성
  4. 파일에 "Hello"메세지를 위한 코드 작성
module Hello exposing (..) -- (..) == all
import Html exposing (..) -- Html 렌더링용

main = 
    text "Hello"
  1. 터미널에서 elm-reactor 실행

구조

언어자체에 웹앱을 만들수 있는 프레임웍이 내장되어 있음 model, view, update가 작동하는 방식이 프레임웍에 의존해서 처리됨

functions

오브젝트나 값만을 주고 받는게 아니라 함수(처리 알고리즘)을 주고-받음(λ : 람다)으로서 구조를 간결하고 유연하게 만든다

-- ver. Anonymous functions
\ a b -> a / b
> (\ a b -> a / b) 3 2 -- 이름이 없는 함수 일때는 함수가 어디서 끝나는지 알려주기 위해 '()'로 감싼다
1.5 : Float

-- ver. Named functions
> divide : Float -> Float -> Float
> divide a b= a / b
<function> : Float -> Float -> Float
> divide 3 2
1.5 : Float

-- 'divide 3' 하나의 파라미터만 보내면 어떠한 결과가 나올까?
> divide 3
<function> : Float -> Float -- 함수를 반환함, 함수를 변수에 넣어보자
> divdeThreeBy = divide 3
<function> : Float -> Float
> divideThreeBy 2
1.5 : Float
> divideThreeBy 3
1 : Float

-- ver. Actual
> divide x y = x / y
<function> : Float -> Float -> Float

> divide x = \y -> x / y
<function> : Float -> Float -> Float

> divide = \x -> (\y -> x / y)
<function> : Float -> Float -> Float
  • 실제와 좀 다르지만 이해하기 쉬운 설명 : 마지막 -> Float 부분이 리턴.
  • 좀더 어려운 설명 : 'divide : Float -> Float -> Float'은 실제로는 두개의 함수로 이루어져 있으며 맨뒤의 함수부터 앞쪽의 함수에 함수 자체를 반환 시켜서 연산을 함.

record 생성방법

type alias Animal = 
	{ species : String
	, age : Int
	}
m = { species = "dog", age = 4 } -- 새로운 record를 생성한다
m2 = { m | age = 2 } -- m record를 카피해서 age 값만 변경한다
m3 = Animal "cat" 3 -- 새로운 record를 간소화한 문법으로 생성한다

Union Types

Algebraic data type

DOC

OOP개념의 enum과 유사한 개념, 단순히 타입뿐아니라 각 타입함수가 값을 받을 수 있다.

type User = Anonymous | Named String


userPhoto : User -> String
userPhoto user =
  case user of
    Anonymous ->
      "anon.png"

    Named name ->
      "users/" ++ name ++ ".png"


activeUsers : List User
activeUsers =
  [ Anonymous, Named "catface420", Named "AzureDiamond", Anonymous ]


photos : List String
photos =
  List.map userPhoto activeUsers
-- [ "anon.png", "users/catface420.png", "users/AzureDiamond.png", "anon.png" ]

Swift와 비교

Generic Data Structures

데이터의 형태를 정하지 않고 처리하기위한 패턴

> type List a = Empty | Node a (List a)

> ns = Node 1 (Node 2 (Node 3 Empty))
-- Node 1 (Node 2 (Node 3 Empty)) : Repl.List number
> nil = Empty
-- Empty : Repl.List a


> isEmpty list = \
|   case list of\
|     Empty -> True\
|     Node _ _ -> False
-- <function> : Repl.List a -> Bool

> isEmpty ns
-- False : Bool
> isEmpty nil
-- True : Bool


> length list = \
|   case list of \
|     Empty -> 0 \
|     Node _ next -> 1 + length next
<function> : Repl.List a -> number

> length nil
-- 0 : number

> length ns
-- 3 : number


> reverse list = \
|   let \
|     reverseP xs acc = \
|       case xs of \
|         Empty -> acc \
|         Node a next -> reverseP next (Node a acc) \
|   in \
|     reverseP list Empty
-- <function> : Repl.List a -> Repl.List a

> reverse nil
-- Empty : Repl.List a
> reverse ns
-- Node 3 (Node 2 (Node 1 Empty)) : Repl.List number


> member a list = \
|   case list of \
|     Empty -> False \
|     Node x next -> if a == x then True else member a next 
-- <function> : a -> Repl.List a -> Bool

> member 2 ns
-- True : Bool
> member 4 ns
-- False : Bool
  • _(underscore) 해당 값을 사용하지않음(무시)
  • List a: a를 써도 되고 어떤 String값이든 쓸수 있음 단 컨벤션은 lowercase로 시작.
  • type 생성시 위 예제에서 List가 타입의 역할을 하고 오른쪽의 (Empty, Node a)부분이 데이터 역할을 하게됨.

Error Handling

  • Maybe: 타언어(Java, Javascript, Ruby, Python)에 있는 null에 해당하는 상황을 처리하기위해 만들어짐. Swift의 optional과 비슷한 개념
  • Result: exeption을 처리하기 위해 만들어짐.
  • Task: Result와 비슷하나 asynchronos상황에 특화되어 만들어짐.

Maybe

옵셔널이 필요한이유: 유저에게 정보입력 요구를 분할하라. ex)처음가입할때 이메일만 요구하고 사용하면서 이름, 성별등의 정보를 분할 요청하라 --> UX관점에서도 중요한 포인트

> type Maybe a = Nothing | Just a

> Nothing
Nothing : Repl.Maybe a
> Just
<function> : a -> Repl.Maybe a
> Just 3
Just 3 : Repl.Maybe number
> Just "frank"
Just "frank" : Repl.Maybe String -- 아마 스트링이 있을걸

Optional Fields

Record 생성시 Maybe 사용

type alias User =
  { name : String
  , age : Maybe Int
  }  

sue : User
sue =
  { name = "Sue", age = Nothing }  

tom : User
tom =
  { name = "Tom", age = Just 24 }


canBuyAlcohol : User -> Bool
canBuyAlcohol user =
  case user.age of -- 옵셔널로 설정된 값을 사용하려면 case문으로 풀어서 사용해야한다.
    Nothing ->
      False

    Just age ->
      age >= 21  
  • Swift비교: optional 사용은 Swift가 편하다고 느껴짐, 하지만 sue.age! 처럼 강제로 무시가능(nil이 없는 것이 아님)

###Result 결과가 성공일수도 있고 실패일수도 있는 상황에서 사용됨, Error가 데이터라는것이 타언어와 다른특징.

> String.toInt
<function> : String -> Result.Result String Int

type Result error value
  = Err error
  | Ok value


> import String

> String.toInt "128"
Ok 128 : Result String Int

> String.toInt "64"
Ok 64 : Result String Int

> String.toInt "BBBB"
Err "could not convert string 'BBBB' to an Int" : Result String Int


view : String -> Html msg
view userInputAge =
  case String.toInt userInputAge of
    Err msg ->
      span [class "error"] [text msg]

    Ok age ->
      if age < 0 then
        span [class "error"] [text "I bet you are older than that!"]

      else if age > 140 then
        span [class "error"] [text "Seems unlikely..."]

      else
        text "OK!"

Interop > Json

Decoder

string : Decoder String -- string을 담고 있는 UnionType
int : Decoder Int
float : Decoder Float
bool : Decoder Bool

-- Json data를 Decoder 타입에 따라 변환해줌
decodeString : Decoder a -> String -> Result String a
> import Json.Decode exposing (..)

> decodeString int "42"
Ok 42 : Result String Int

> decodeString float "3.14159"
Ok 3.14159 : Result String Float

> decodeString bool "true"
Ok True : Result String Bool

> decodeString int "true" -- int 포멧으로 변환하려는데 Bool에 해당하는 스트링을 입력하니 컴파일 에러남.
Err "Expecting an Int but instead got: true" : Result String Int

Combining Decoders

> import Json.Decode exposing (..)

> int -- 인트 디코더
<decoder> : Json.Decode.Decoder Int
> list -- 리스트 디코더
<function> : Json.Decode.Decoder a -> Json.Decode.Decoder (List a)
> list int - 인트를 가지고있는 리스트 디코더
<decoder> : Json.Decode.Decoder (List Int)

> decodeString (list int) "[1, 2, 3]"
Ok [1,2,3] : Result.Result String (List Int)

-- triple quote 사용 가능
> decodeString (list string) """["hi", "yo"]"""
Ok ["hi","yo"] : Result.Result String (List String)

-- 리스트안에 리스트안까지 쉽게
> decodeString (list (list int)) "[ [0], [1,2,3], [4,5] ]"
Ok [[0],[1,2,3],[4,5]] : Result String (List (List Int))

Decoding Object

커스텀 decoder를 생성할 때 사용한다.

field "x" int

  • x: 필드명
  • int: int로 변환 가능한 데이터
> field
<function> : String -> Json.Decode.Decoder a -> Json.Decode.Decoder a

Decode_json.elm

module Decode_json exposing (..)

import Json.Decode exposing (..)

nameExtractor : Decoder String
nameExtractor = field "name" string

me = """
    { "id": 1
    , "name": "Frank"
    }
"""
> import Decode_json exposing (..)
> import Json.Decode exposing (..)

> decodeString
<function> : Json.Decode.Decoder a -> String -> Result.Result String a

> decodeString nameExtractor me
Ok "Frank" : Result.Result String String

Combine Decoder

map2 함수를 이용해서 두개의 디코더를 합성보자

> import Decode_json exposing (..)

> type alias Person = { id : Int, name : String }
<function> : Int -> String -> Repl.Person

> personDecoder = map2 Person (field "id" int) (field "name" string)
<decoder> : Json.Decode.Decoder Repl.Person

> map2
<function>
    : (a -> b -> value)
      -> Json.Decode.Decoder a
      -> Json.Decode.Decoder b
      -> Json.Decode.Decoder value

> decodeString personDecoder me
Ok { id = 1, name = "Frank" } : Result.Result String Decode_json.Person

Combine Decoder with Pipeline

2개가 아닌 더 많은 갯수의 디코더를 NoRedInk/elm-decode-pipeline로 합성해 보자

먼저 NoRedInk/elm-decode-pipeline를 설치해줘야 한다.

  1. elm-package.json > dependencies"NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0" 추가
  2. elm-stuff 폴더 삭제
  3. terminal에서 elm-make

위는 elm-package installer를 사용하지않고 수동으로 한것 아래처럼 터미널에서 자동으로 설치하자

$ elm-package install NoRedInk/elm-decode-pipeline
import Json.Decode exposing (..)
import Json.Decode.Pipeline exposing (decode, required)

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

pointDecoder : Decoder Point
pointDecoder =
  decode Point
    |> required "x" int
    |> required "y" int


pointJsonString = """
    { "x": 23
    , "y": 78
    }
"""

> decodeString pointDecoder pointJsonString
Ok { x = 23, y = 78 } : Result.Result String Decode_json.Point

Auto generate code for decode/incode

더 쉽게 해보자! json_to_elm에서 제공하는 웹페이지에서 Json 스트링을 넣어주면 자동으로 incode/decode를 할 수 있는 elm code를 생성해 준다

  1. http://noredink.github.io/json-to-elm/ 에서 코드를 생성후 프로젝트에 붙여 넣는다
  2. 생성된 코드에서 json-extra를 사용한다고 경고가 나온다
  3. elm-package.json > elm-community/json-extra": "2.1.0 <= v < 3.0.0" 추가
$ elm-package install elm-community/json-extra
module JsonToElm exposing (..)

import Json.Encode
import Json.Decode
-- elm-package install -- yes noredink/elm-decode-pipeline
import Json.Decode.Pipeline

type alias User =
    { id : Int
    , email : String
    , name : String
    }

decodeUser : Json.Decode.Decoder User
decodeUser =
    Json.Decode.Pipeline.decode User
        |> Json.Decode.Pipeline.required "id" (Json.Decode.int)
        |> Json.Decode.Pipeline.required "email" (Json.Decode.string)
        |> Json.Decode.Pipeline.required "name" (Json.Decode.string)

encodeUser : User -> Json.Encode.Value
encodeUser record =
    Json.Encode.object
        [ ("id",  Json.Encode.int <| record.id)
        , ("email",  Json.Encode.string <| record.email)
        , ("name",  Json.Encode.string <| record.name)
        ]

--------------

frank = """
    { "id": 1
    , "email": "1@colavo.kr"
    , "name": "Frank"
    }
"""
> import JsonToElm exposing (..)
> frank
"\n    { \"id\": 1\n    , \"email\": \"1@colavo.kr\"\n    , \"name\": \"Frank\"\n    }\n"
    : String
> decodeString
<function> : Json.Decode.Decoder a -> String -> Result.Result String a
> decodeString decodeUser frank
Ok { id = 1, email = "1@colavo.kr", name = "Frank" }
    : Result.Result String JsonToElm.User

Interop > Javascript

Inveding

Spelling.elm -> Spelling.js 로 컴파일 후 Html에 임베딩한후 다른 자바스크립트 코드와 통신하도록 하는 구조

아래 예제는 유저가 텍스트 필드에 입력했을때 스펠링을 체크(Javascript code)해서 후보 단어들을 텍스트 필드 아래에 보여주는 앱.

  • index.html
<div id="spelling"></div>
<script src="spelling.js"></script>
<script>
    var app = Elm.Spelling.fullscreen();

    app.ports.check.subscribe(function(word) {
        var suggestions = spellCheck(word);
        app.ports.suggestions.send(suggestions);
    });

    function spellCheck(word) {
        // dummy implementation
        if (word == "helo") {
            return ["hello"]
        }else if (word == "hell") {
            return ["hello", "hell"]
        }

        return [];
    }
</script>
  • Spelling.elm
port module Spelling exposing (..)

import Html exposing (..)
import Html.Events exposing (..)
import String



main : Program Never Model Msg
main =
  Html.program
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    }


-- MODEL

type alias Model =
  { word : String
  , suggestions : List String
  }

init : (Model, Cmd Msg)
init =
  (Model "" [], Cmd.none)


-- UPDATE

type Msg
  = Change String
  | Check
  | Suggest (List String)


port check : String -> Cmd msg

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Change newWord ->
      ( Model newWord [], Cmd.none )

    Check ->
      ( model, check model.word )

    Suggest newSuggestions ->
      ( Model model.word newSuggestions, Cmd.none )


-- SUBSCRIPTIONS

port suggestions : (List String -> msg) -> Sub msg

subscriptions : Model -> Sub Msg
subscriptions model =
  suggestions Suggest


-- VIEW

view : Model -> Html Msg
view model =
  div []
    [ input [ onInput Change ] []
    , button [ onClick Check ] [ text "Check" ]
    , div [] [ text (String.join ", " model.suggestions) ]
    ]
  • compile Spelling.elm file
elm-make Spelling.elm --output=spelling.js

Flag

elm앱이 시작(init)할때 특정값(ex. user)을 받고 싶을때 사용함. elm앱 생성시 program 대신 programWithFlags 사용

type alias Flags =
  { user : String
  , token : String
  }

init : Flags -> ( Model, Cmd Msg )
init flags =
  ...

main =
  programWithFlags { init = init, ... }
var app = Elm.MyApp.fullscreen({
    user: 'Tom',
    token: '12345'
});

var node = document.getElementById('my-app');
var app = Elm.MyApp.embed(node, {
    user: 'Tom',
    token: '12345'
});

About

records_training_elm

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published